diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9303db1..f1c1adf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable + # hashgraph-like-consensus's build.rs shells out to protoc via prost-build. + - run: sudo apt-get update && sudo apt-get install -y protobuf-compiler # chat-cli's build.rs unconditionally links liblogosdelivery and requires # LOGOS_DELIVERY_LIB_DIR. The smoketest job builds and exercises it under # Nix; here we keep the toolchain-only job fast by skipping it. @@ -31,6 +33,8 @@ jobs: - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: rustup component add clippy + # hashgraph-like-consensus's build.rs shells out to protoc via prost-build. + - run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - run: cargo clippy --all-targets --all-features --workspace --exclude chat-cli -- -D warnings fmt: diff --git a/Cargo.lock b/Cargo.lock index 7312bf5..f4743ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array 0.14.7", ] @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -58,6 +58,641 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1e915a830ea2ee123c9d3886bfec3302a7ddcd73a973ab165ed45aca2e42c3" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-trie", +] + +[[package]] +name = "alloy-chains" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum 0.27.2", +] + +[[package]] +name = "alloy-consensus" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83447eeb17816e172f1dfc0db1f9dc0b7c5d069bd1f7cecbecceb382bf931015" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "k256", + "once_cell", + "rand 0.8.6", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-consensus-any" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5406343e306856dc2be762700e98a16904de45dee14a07f233e742ce68daff2f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8b60d71b92824e095b4003ff01fd2bc923017b7568997c5f459240e83499c" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-core" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ddde5968de6044d67af107ad835bc0069a7ca245870b94c5958a7d8712b184" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "once_cell", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eips" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca4c89ace90684b4b77366d00631ed498c9af962079af2a5dbc593a0618a77" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "serde", + "serde_with", + "sha2 0.10.9", +] + +[[package]] +name = "alloy-genesis" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e0fe9e6d1120ad7bb9254c3fc2b9bc80a8df42a033fb626be6559c13d5153" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-json-abi" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0a82e56b1843bce483942d54fcadea92e676f1bde162e93c7d3b621fabc4e1" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db7b095b0b1db1d18ce7e91dcd2e82007f2d52bfb8125e6b64633a74a06bc3" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more 2.1.1", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-network-primitives" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd28d9bfd11729037d194f2b1d43db8642eb3f342032691f4ca96bb745479c3c" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 2.1.1", + "foldhash 0.2.0", + "hashbrown 0.17.0", + "indexmap 2.14.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.4", + "rapidhash", + "ruint", + "rustc-hash", + "secp256k1 0.31.1", + "serde", + "sha3", +] + +[[package]] +name = "alloy-provider" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8955ab30418343de57b356de2ea60200f9fb8016a7ea3bc7f5c6176f01a8b1cf" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures", + "futures-utils-wasm", + "lru 0.16.4", + "parking_lot", + "pin-project", + "reqwest 0.13.4", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-rpc-client" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f461f091dc8f657e73b5dea18fd63d5c7049720cd252f1eade4a7ebed6a7e1" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "alloy-transport-http", + "futures", + "pin-project", + "reqwest 0.13.4", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052c031d1f7c5611997056bbcb8814e5cbf20f7efeee8c3de690555172038cf2" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6561ed4759c974d9c144500a59e3fb8c1d87327a12900d5ce455c0cae6dcb6" +dependencies = [ + "alloy-consensus-any", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175a2a5b6017d7f61b5e4b800d21215fe8e94fe729d00828e13bb6d93dcf3492" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc21a8772af7d78bba286726aa245bd2ff81cd9abe230afea2e91578996831c9" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ffbce94c50dd9d4d1f83e044c5595bbd3ada981bd3057ce28b3a5470e77385d" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-signer-local" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48366d2c42b8d95ef951101bafa28486590f21b7a1e68b6b2d069746557bbe3" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.6", + "thiserror", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.14.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86052fdcec72d37ca4aa4b66254601e7453c45a6e1c70aa4561033d002fb80cc" +dependencies = [ + "alloy-json-rpc", + "auto_impl", + "base64", + "derive_more 2.1.1", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b273587487921274f4f5d0ef2c7ef36944dcbb75a4e2318e69eae822bd263f91" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "itertools 0.14.0", + "reqwest 0.13.4", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "nybbles", + "serde", + "smallvec", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a0035943b75fe1e249f52e688492d7a1b1826bc2d19b8e1d5d3c24a2ad8f50" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -143,18 +778,279 @@ dependencies = [ "x11rb", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -173,19 +1069,62 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -197,18 +1136,84 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -220,6 +1225,24 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] [[package]] name = "cassowary" @@ -243,6 +1266,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -266,7 +1291,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -321,13 +1346,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -363,7 +1400,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -381,12 +1418,31 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -406,22 +1462,62 @@ name = "components" version = "0.1.0" dependencies = [ "base64", + "crossbeam-channel", "crypto", "hex", "libchat", - "reqwest", + "reqwest 0.12.28", "serde", "storage", "thiserror", "tracing", ] +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -431,6 +1527,22 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "core-models" version = "0.0.5" @@ -451,6 +1563,30 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + [[package]] name = "crc32fast" version = "1.5.0" @@ -551,7 +1687,7 @@ dependencies = [ "generic-array 1.3.5", "hkdf", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", "thiserror", "x25519-dalek", "xeddsa", @@ -581,6 +1717,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -597,11 +1742,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -614,7 +1759,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -636,8 +1781,9 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -648,7 +1794,40 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "de-mls" +version = "3.0.0" +source = "git+https://github.com/vacp2p/de-mls?branch=develop#d838e832994fd1d14f624783741bc60b31510fa0" +dependencies = [ + "hashgraph-like-consensus", + "indexmap 2.14.0", + "openmls", + "openmls_basic_credential", + "openmls_rust_crypto 0.5.1", + "openmls_traits 0.5.0", + "prost", + "prost-build", + "sha2 0.11.0", + "thiserror", + "tracing", + "uuid", ] [[package]] @@ -657,11 +1836,32 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -670,7 +1870,7 @@ checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -691,8 +1891,18 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.7", ] [[package]] @@ -701,12 +1911,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", +] + [[package]] name = "dispatch2" version = "0.3.1" @@ -725,7 +1946,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -755,6 +1976,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -762,9 +1995,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", + "serdect", "signature", "spki", ] @@ -789,16 +2023,31 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.10.9", "subtle", "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -808,7 +2057,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array 0.14.7", "group", @@ -817,10 +2066,31 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sec1", + "serdect", "subtle", "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -861,6 +2131,28 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "fax" version = "0.2.6" @@ -878,7 +2170,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -912,6 +2204,24 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.6", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -922,12 +2232,24 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -937,6 +2259,33 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -953,6 +2302,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -967,7 +2327,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -994,6 +2354,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1004,6 +2365,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "generic-array" version = "0.14.7" @@ -1114,6 +2481,18 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1122,7 +2501,18 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -1130,6 +2520,28 @@ name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashgraph-like-consensus" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b6328e1ba3b6ff66e24a018b40c6ea61a3105607755ac378003b57de7bc19d" +dependencies = [ + "alloy", + "alloy-signer", + "parking_lot", + "prost", + "prost-build", + "sha2 0.10.9", + "thiserror", + "tracing", + "uuid", +] [[package]] name = "hashlink" @@ -1161,7 +2573,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1183,12 +2595,27 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -1204,7 +2631,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1270,7 +2697,7 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.10.1", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", "subtle", "x25519-dalek", "zeroize", @@ -1315,6 +2742,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" @@ -1374,6 +2810,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1503,6 +2963,37 @@ dependencies = [ "tiff", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1543,7 +3034,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1554,8 +3045,11 @@ dependencies = [ "components", "libchat", "logos-account", + "shared-traits", "storage", "tempfile", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1570,6 +3064,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1594,6 +3097,65 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.95" @@ -1613,9 +3175,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", + "ecdsa", "elliptic-curve", + "once_cell", + "serdect", + "sha2 0.10.9", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "keccak-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1638,24 +3239,29 @@ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" name = "libchat" version = "0.1.0" dependencies = [ + "alloy", "base64", "blake2", "chat-proto", "chat-sqlite", "components", "crypto", + "de-mls", "double-ratchets", + "hashgraph-like-consensus", "hex", "openmls", "openmls_libcrux_crypto 0.3.1", "openmls_memory_storage 0.5.0", "openmls_traits 0.5.0", "prost", + "rand 0.9.4", "rand_core 0.6.4", "shared-traits", "storage", "tempfile", "thiserror", + "tracing", "x25519-dalek", ] @@ -1819,7 +3425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1922,6 +3528,12 @@ dependencies = [ "rand 0.9.4", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libsqlite3-sys" version = "0.33.0" @@ -2006,12 +3618,32 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "macro-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "matchers" version = "0.2.0" @@ -2059,6 +3691,12 @@ dependencies = [ "pxfm", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2078,6 +3716,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.46" @@ -2094,6 +3738,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", ] [[package]] @@ -2205,6 +3895,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "openmls_basic_credential" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983e8be1457dd6f316f409292cec334af3b57b49a19deadc925c83c3c35e15b6" +dependencies = [ + "ed25519-dalek", + "openmls_traits 0.5.0", + "p256", + "rand 0.8.6", + "serde", + "tls_codec", +] + [[package]] name = "openmls_libcrux_crypto" version = "0.2.4" @@ -2295,7 +3999,32 @@ dependencies = [ "rand 0.8.6", "rand_chacha 0.3.1", "serde", - "sha2", + "sha2 0.10.9", + "thiserror", + "tls_codec", +] + +[[package]] +name = "openmls_rust_crypto" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fafcc8a3552b10fbb3ab757cccaf1a34081e826ca819f49aa7e6645b1d95c00f" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "ed25519-dalek", + "hkdf", + "hmac", + "hpke-rs", + "hpke-rs-crypto", + "hpke-rs-rust-crypto", + "openmls_memory_storage 0.5.0", + "openmls_traits 0.5.0", + "p256", + "rand 0.8.6", + "rand_chacha 0.3.1", + "serde", + "sha2 0.10.9", "thiserror", "tls_codec", ] @@ -2308,13 +4037,13 @@ checksum = "46c5984361586c8ef56108664ffec909fa78126be8eef1983723f0aed80a9266" dependencies = [ "ansi_term", "openmls_libcrux_crypto 0.2.4", - "openmls_rust_crypto", + "openmls_rust_crypto 0.4.4", "openmls_traits 0.4.1", "proc-macro2", "quote", "rstest", "rstest_reuse", - "syn", + "syn 2.0.117", ] [[package]] @@ -2337,6 +4066,12 @@ dependencies = [ "tls_codec", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-src" version = "300.6.0+3.6.2" @@ -2368,7 +4103,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2381,6 +4116,34 @@ dependencies = [ "primeorder", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2431,12 +4194,59 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs8" version = "0.10.2" @@ -2472,7 +4282,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2484,7 +4294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2498,6 +4308,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2514,7 +4330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -2526,6 +4342,17 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -2554,7 +4381,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2566,6 +4393,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.14.3" @@ -2576,6 +4422,25 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.117", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.14.3" @@ -2586,7 +4451,16 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", ] [[package]] @@ -2595,6 +4469,12 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-error" version = "2.0.1" @@ -2627,6 +4507,7 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2677,6 +4558,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.6" @@ -2686,6 +4573,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -2696,6 +4584,7 @@ checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", + "serde", ] [[package]] @@ -2754,6 +4643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", + "serde", ] [[package]] @@ -2762,6 +4652,24 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -2775,9 +4683,9 @@ dependencies = [ "indoc", "instability", "itertools 0.13.0", - "lru", + "lru 0.12.5", "paste", - "strum", + "strum 0.26.3", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -2812,6 +4720,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "regex" version = "1.12.3" @@ -2887,6 +4815,43 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -2911,6 +4876,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rstest" version = "0.24.0" @@ -2920,7 +4895,7 @@ dependencies = [ "futures-timer", "futures-util", "rstest_macros", - "rustc_version", + "rustc_version 0.4.1", ] [[package]] @@ -2936,8 +4911,8 @@ dependencies = [ "quote", "regex", "relative-path", - "rustc_version", - "syn", + "rustc_version 0.4.1", + "syn 2.0.117", "unicode-ident", ] @@ -2949,9 +4924,43 @@ checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14" dependencies = [ "quote", "rand 0.8.6", - "syn", + "syn 2.0.117", ] +[[package]] +name = "ruint" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.6", + "rand 0.9.4", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rusqlite" version = "0.35.0" @@ -2972,13 +4981,28 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.28", ] [[package]] @@ -3013,6 +5037,7 @@ version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -3021,6 +5046,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" @@ -3031,12 +5068,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3048,12 +5113,66 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3070,16 +5189,99 @@ dependencies = [ "der", "generic-array 0.14.7", "pkcs8", + "serdect", "subtle", "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.6", + "secp256k1-sys 0.10.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.4", + "secp256k1-sys 0.11.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -3117,7 +5319,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3145,6 +5347,48 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3152,8 +5396,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" +dependencies = [ + "cc", + "cfg-if", ] [[package]] @@ -3215,7 +5490,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -3225,6 +5500,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -3236,6 +5527,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3289,7 +5583,16 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", ] [[package]] @@ -3302,7 +5605,19 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -3311,6 +5626,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -3322,6 +5648,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3339,9 +5677,15 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.27.0" @@ -3372,7 +5716,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3384,6 +5728,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tiff" version = "0.11.3" @@ -3393,11 +5746,42 @@ dependencies = [ "fax", "flate2", "half", - "quick-error", + "quick-error 2.0.1", "weezl", "zune-jpeg", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -3442,7 +5826,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3456,9 +5840,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -3469,6 +5865,31 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -3484,7 +5905,7 @@ version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap", + "indexmap 2.14.0", "toml_datetime", "toml_parser", "winnow", @@ -3563,7 +5984,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3617,6 +6038,30 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -3664,7 +6109,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -3684,6 +6129,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -3700,9 +6146,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3727,6 +6173,25 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3802,7 +6267,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3832,7 +6297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -3845,8 +6310,22 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap", - "semver", + "indexmap 2.14.0", + "semver 1.0.28", +] + +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", ] [[package]] @@ -3869,6 +6348,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.7" @@ -3900,18 +6388,80 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4120,9 +6670,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.14.0", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4138,7 +6688,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4151,7 +6701,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -4170,9 +6720,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.14.0", "log", - "semver", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -4186,6 +6736,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11rb" version = "0.13.2" @@ -4226,7 +6785,7 @@ dependencies = [ "ed25519", "ed25519-dalek", "rand 0.8.6", - "sha2", + "sha2 0.10.9", "x25519-dalek", "zeroize", ] @@ -4250,7 +6809,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -4271,7 +6830,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4291,7 +6850,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -4312,7 +6871,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4345,7 +6904,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/core/conversations/Cargo.toml b/core/conversations/Cargo.toml index 9336240..52caa29 100644 --- a/core/conversations/Cargo.toml +++ b/core/conversations/Cargo.toml @@ -15,17 +15,22 @@ shared-traits = { workspace = true } storage = { workspace = true } # External dependencies (sorted) +alloy = "2.0" base64 = "0.22" chat-proto = { git = "https://github.com/logos-messaging/chat_proto", rev = "37ec98a151f6d50aab2905802ac0a896477e62ea" } +de-mls = { git = "https://github.com/vacp2p/de-mls", branch = "develop" } double-ratchets = { path = "../double-ratchets" } +hashgraph-like-consensus = "0.5.1" hex = "0.4.3" openmls = { version = "0.8.1", features = ["libcrux-provider"] } openmls_libcrux_crypto = "0.3.1" openmls_memory_storage = "0.5.0" openmls_traits = "0.5.0" prost = "0.14.1" +rand = "0.9" rand_core = { version = "0.6" } thiserror = "2.0.17" +tracing = "0.1.44" x25519-dalek = { version = "2.0.1", features = ["static_secrets", "reusable_secrets", "getrandom"] } [dev-dependencies] diff --git a/core/conversations/src/conversation.rs b/core/conversations/src/conversation.rs index 4724955..c8816fa 100644 --- a/core/conversations/src/conversation.rs +++ b/core/conversations/src/conversation.rs @@ -1,4 +1,5 @@ pub mod group_v1; +mod group_v2; mod privatev1; pub use crate::errors::ChatError; @@ -6,10 +7,12 @@ use crate::outcomes::ConvoOutcome; use crate::proto::EncryptedPayload; use crate::service_context::{ExternalServices, ServiceContext}; pub use group_v1::GroupV1Convo; +pub use group_v2::GroupV2Convo; pub use privatev1::PrivateV1Convo; use shared_traits::IdentIdRef; pub type ConversationId = String; +pub type ConversationIdRef<'a> = &'a str; /// Behaviour shared by every conversation kind. pub(crate) trait Convo { @@ -26,13 +29,17 @@ pub(crate) trait Convo { cx: &mut ServiceContext, enc: EncryptedPayload, ) -> Result; + + fn wakeup(&mut self, service_ctx: &mut ServiceContext) -> Result<(), ChatError>; } /// Group-only operations. -pub(crate) trait GroupConvo: Convo { +pub(crate) trait GroupConvo: Convo + std::fmt::Debug + Send { fn add_member( &mut self, cx: &mut ServiceContext, members: &[IdentIdRef], ) -> Result<(), ChatError>; + + fn id(&self) -> ConversationIdRef<'_>; } diff --git a/core/conversations/src/conversation/group_v1.rs b/core/conversations/src/conversation/group_v1.rs index 1570763..0d0d433 100644 --- a/core/conversations/src/conversation/group_v1.rs +++ b/core/conversations/src/conversation/group_v1.rs @@ -39,7 +39,7 @@ impl std::fmt::Debug for GroupV1Convo { impl GroupV1Convo { // Create a new conversation with the creator as the only participant. pub fn new(cx: &mut ServiceContext) -> Result { - let config = Self::mls_create_config(); + let config = Self::mls_create_config(cx); let mls_group = MlsGroup::new( &cx.mls_provider, &cx.mls_identity, @@ -105,9 +105,9 @@ impl GroupV1Convo { Ok(()) } - fn mls_create_config() -> MlsGroupCreateConfig { + fn mls_create_config(cx: &mut ServiceContext) -> MlsGroupCreateConfig { MlsGroupCreateConfig::builder() - .ciphersuite(Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519) + .ciphersuite(cx.mls_provider.crypto().supported_ciphersuites()[0]) .use_ratchet_tree_extension(true) // This is handy for now, until there is central store for this data .build() } @@ -276,6 +276,10 @@ impl Convo for GroupV1Convo { content, }) } + + fn wakeup(&mut self, _: &mut ServiceContext) -> Result<(), ChatError> { + Ok(()) + } } impl GroupConvo for GroupV1Convo { @@ -340,4 +344,8 @@ impl GroupConvo for GroupV1Convo { .publish(env) .map_err(|e| ChatError::Generic(format!("Publish: {e}"))) } + + fn id(&self) -> super::ConversationIdRef<'_> { + &self.convo_id + } } diff --git a/core/conversations/src/conversation/group_v2.rs b/core/conversations/src/conversation/group_v2.rs new file mode 100644 index 0000000..c8a42cb --- /dev/null +++ b/core/conversations/src/conversation/group_v2.rs @@ -0,0 +1,485 @@ +// This Implementation is a Quick and Dirty Integration of DeMLS into libchat. +// DeMLS and Libchat have different execution models, trait definitions and ownership/lifetimes of objects. +// The easies path is to do a Spike to see what it would take, gather the friction points and then iterate. + +use crate::types::AddressedEncryptedPayload; +use crate::{Content, WakeupService}; +use alloy::signers::local::PrivateKeySigner; +use blake2::{Blake2b, Digest, digest::consts::U6}; +use chat_proto::logoschat::encryption::{EncryptedPayload, Plaintext, encrypted_payload}; +use de_mls::core::{ + ConsensusPlugin, ConsensusServiceFor, ConversationEvent, ConversationPluginsFactory, + ScoringConfig, StewardListConfig, +}; +use de_mls::defaults::{ + DefaultConsensusPlugin, DefaultConversationPluginsFactory, MemoryDeMlsStorage, +}; +use de_mls::member_id::MemberId; +use de_mls::mls_crypto::MlsCredentials; +use de_mls::protos::de_mls::messages::v1::{ + AppMessage as AppMessageProto, MemberWelcome, app_message, +}; +use de_mls::session::{Conversation, ConversationConfig, ConversationDeps}; +use hashgraph_like_consensus::signing::EthereumConsensusSigner; +use prost::Message; +use shared_traits::{IdentId, IdentIdRef}; +use std::sync::Arc; +use std::time::Duration; +use tracing::{info, instrument, warn}; + +use crate::IdentityProvider; +use crate::conversation::{ConversationIdRef, ExternalServices, ServiceContext}; +use crate::{ + ConvoOutcome, DeliveryService, RegistrationService, + conversation::{ChatError, Convo, GroupConvo}, +}; + +/// Namespace used for de-mls (GroupV2) keypackages, so they don't collide +/// with the openmls (GroupV1) keypackage registered under the bare account id. +const DEMLS_KEYPACKAGE_NAMESPACE: &str = "demls"; + +/// This is a Test Wrapper of Demls MemberId Trait +/// Libchat has its own trait that will need to be intergrated at somepoint. +pub struct LocalDemlsMember { + name: String, +} + +impl LocalDemlsMember { + pub fn new(name: impl Into) -> Self { + Self { name: name.into() } + } +} + +impl MemberId for LocalDemlsMember { + fn member_id_bytes(&self) -> &[u8] { + self.name.as_bytes() + } + + fn member_id_display(&self) -> &str { + &self.name + } +} + +/// Borrows an existing `IdentityProvider` but reports a namespaced `id()`, +/// so the same identity can register multiple keypackage "flavors" +/// (e.g. openmls vs. de-mls) without colliding in the registry. +struct NamespacedIdentity<'a> { + inner: &'a dyn IdentityProvider, + id: IdentId, +} + +impl<'a> NamespacedIdentity<'a> { + fn new(inner: &'a dyn IdentityProvider, namespace: &str) -> Self { + let id = IdentId::new(Self::prefix(inner.id(), namespace)); + Self { inner, id } + } + + fn prefix(id: &IdentId, namesapce: &str) -> String { + format!("{namesapce}|{id}") + } +} + +impl IdentityProvider for NamespacedIdentity<'_> { + fn id(&self) -> IdentIdRef<'_> { + &self.id + } + + fn display_name(&self) -> String { + self.inner.display_name() + } + + fn sign(&self, payload: &[u8]) -> crypto::Ed25519Signature { + self.inner.sign(payload) + } + + fn public_key(&self) -> &crypto::Ed25519VerifyingKey { + self.inner.public_key() + } +} + +struct DemlsSetup { + member: LocalDemlsMember, + factory: DefaultConversationPluginsFactory, + consensus_storage: ::ConsensusStorage, + consensus_signer: EthereumConsensusSigner, + app_id: Vec, // random bytes; echo-dedup key + config: ConversationConfig, // the ms-scale test timers, as before +} + +impl DemlsSetup { + fn new(identity_name: String) -> Result { + let member = LocalDemlsMember::new(identity_name); + let credentials = Arc::new(MlsCredentials::from_member_id(&member)?); + let factory = DefaultConversationPluginsFactory::new( + Arc::new(MemoryDeMlsStorage::new()), + credentials, + ); + // TODO(config): TEST-ONLY millisecond timers. de-mls deadlines are real + // wall-clock, so the default 60s timers never fire under fast virtual + // time. Production needs a real config injected from the caller, not + // these hardcoded values. + let config = ConversationConfig { + commit_inactivity_duration: Duration::from_millis(50), + freeze_duration: Duration::from_millis(20), + voting_delay: Duration::from_millis(30), + election_voting_delay: Duration::from_millis(30), + consensus_timeout: Duration::from_millis(150), + proposal_expiration: Duration::from_millis(2000), + ..ConversationConfig::default() + }; + Ok(DemlsSetup { + member, + factory, + consensus_storage: DefaultConsensusPlugin::new_storage(), + consensus_signer: EthereumConsensusSigner::new(PrivateKeySigner::random()), + app_id: rand_string(5).as_bytes().to_vec(), + config, + }) + } + + /// Call exactly once per Conversation construction. + fn deps( + &self, + ) -> ConversationDeps<'_, DefaultConsensusPlugin, DefaultConversationPluginsFactory> { + ConversationDeps { + plugins: &self.factory, + consensus: ConsensusServiceFor::::new_with_components( + self.consensus_storage.clone(), + DefaultConsensusPlugin::new_event_bus(), + self.consensus_signer.clone(), + 10, + ), + identity: &self.member, + app_id: Arc::from(self.app_id.as_slice()), + config: self.config.clone(), + scoring_config: ScoringConfig::default(), + steward_list_config: StewardListConfig::default(), + } + } +} + +pub struct GroupV2Convo { + convo_id: String, + setup: DemlsSetup, + conversation: Option>, + /// Member-ids we proposed via add_member. WelcomeReady now fires on + /// every member; we forward a welcome only to joiners WE invited. + pending_invites: Vec>, +} + +impl std::fmt::Debug for GroupV2Convo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GroupV2Convo") + .field("convo_id", &self.convo_id) + .finish_non_exhaustive() + } +} + +fn rand_string(n: usize) -> String { + let bytes: Vec = (0..n).map(|_| rand::random::()).collect(); + hex::encode(bytes) +} + +impl GroupV2Convo { + pub fn new( + service_ctx: &mut ServiceContext, + ) -> Result { + let setup = DemlsSetup::new(service_ctx.mls_identity.display_name())?; + let convo_id = rand_string(5); + let conversation = Conversation::create(&convo_id, setup.deps())?; + let convo = GroupV2Convo { + convo_id, + setup, + conversation: Some(conversation), + pending_invites: vec![], + }; + + convo.init(service_ctx)?; + + Ok(convo) + } + + /// Joiner side: register a fresh key package under the account name, + /// but do NOT start a conversation. `convo_id` stays empty until + /// [`Self::accept_welcome`] fills it. + pub fn new_pending( + service_ctx: &mut ServiceContext, + ) -> Result { + let name = service_ctx.mls_identity.display_name(); + let setup = DemlsSetup::new(name.clone())?; + let kp = setup.factory.generate_key_package()?; + + // TEMPORARY: Demls creates its own Provider which causes keys to be fragmented in different storage providers. + // The key registry does not support a method to namespace keys with the same identity. When the key is pulled down it cannot + // guarentee it was the one created with demls owned provider, resulting in failure. + // This workaround prefixes the ID used to store the keys, such that they do not conflict. + let namespaced = + NamespacedIdentity::new(&*service_ctx.mls_identity, DEMLS_KEYPACKAGE_NAMESPACE); + service_ctx + .registry + .register(&namespaced, kp.as_bytes().to_vec()) + .map_err(ChatError::generic)?; + + Ok(GroupV2Convo { + convo_id: String::new(), + setup, + conversation: None, + pending_invites: vec![], + }) + } + + /// Joiner side: ingest a de-mls welcome handed over the InboxV2 1-1 + /// channel. `from_welcome` attaches MLS and applies the bundled + /// `ConversationSync` in one call; we then subscribe to the + /// conversation address and flush the join broadcast. + #[instrument(name = "groupv2.accept_welcome", skip_all, fields(user_id = %service_ctx.mls_identity.display_name()))] + pub fn accept_welcome( + &mut self, + service_ctx: &mut ServiceContext, + welcome: &MemberWelcome, + ) -> Result<(), ChatError> { + let conv = Conversation::from_welcome(self.setup.deps(), welcome)? + .ok_or_else(|| ChatError::generic("welcome not addressed to this member"))?; + self.convo_id = conv.id().to_string(); + self.conversation = Some(conv); + self.init(service_ctx)?; // subscribe + self.after_op(service_ctx)?; // flush join broadcast + schedule wakeup + Ok(()) + } + + fn delivery_address_from_id(convo_id: &str) -> String { + let hash = Blake2b::::new() + .chain_update("delivery_addr|") + .chain_update(convo_id) + .finalize(); + hex::encode(hash) + } + + fn init( + &self, + service_ctx: &mut ServiceContext, + ) -> Result<(), ChatError> { + // Configure the delivery service to listen for the required delivery addresses. + service_ctx + .ds + .subscribe(&Self::delivery_address_from_id(&self.convo_id)) + .map_err(ChatError::generic)?; + Ok(()) + } + + pub fn id(&self) -> ConversationIdRef<'_> { + &self.convo_id + } +} + +impl Convo for GroupV2Convo +where + S: ExternalServices, +{ + #[instrument(name = "groupv2.send_content", skip_all, fields(user_id = %service_ctx.mls_identity.display_name(), content))] + fn send_content( + &mut self, + service_ctx: &mut super::ServiceContext, + content: &[u8], + ) -> Result<(), ChatError> { + let conv = self + .conversation + .as_mut() + .ok_or_else(|| ChatError::generic("conversation not found"))?; + conv.send_message(content.to_vec())?; + self.after_op(service_ctx)?; + Ok(()) + } + + #[instrument(name = "groupv2.handle_frame", skip_all, fields(user_id = %service_ctx.mls_identity.display_name()))] + fn handle_frame( + &mut self, + service_ctx: &mut super::ServiceContext, + encoded_payload: EncryptedPayload, + ) -> Result { + let bytes = match encoded_payload.encryption { + Some(encrypted_payload::Encryption::Plaintext(pt)) => pt.payload, + _ => { + return Err(ChatError::generic("Expected plaintext")); + } + }; + let frame = GroupV2Frame::decode(bytes.as_ref()).map_err(ChatError::generic)?; + let inner = match frame.payload { + Some(GroupV2Payload::DeMlsWrapper(b)) => b.to_vec(), + _ => return Ok(ConvoOutcome::empty(self.convo_id.clone())), + }; + + let conv = self + .conversation + .as_mut() + .ok_or_else(|| ChatError::generic("no conversation"))?; + conv.process_inbound(&frame.sender_app_id, &inner)?; + conv.poll(); + let events = self.after_op(service_ctx)?; // route + publish + re-arm, returns events + + match self.events_to_content(&events) { + Some(o) => Ok(o), + None => { + warn!("returning None as ConvoOutcome"); + Ok(ConvoOutcome::empty(self.convo_id.to_string())) + } + } + } + + #[instrument(name = "groupv2.wakeup", skip_all, fields(user_id = %ctx.mls_identity.display_name()))] + fn wakeup(&mut self, ctx: &mut ServiceContext) -> Result<(), ChatError> { + info!(convo = %self.convo_id, "Wakeup"); + let Some(conv) = self.conversation.as_mut() else { + return Ok(()); // pending joiner: no deadlines exist yet + }; + let outcome = conv.poll(); + if outcome.leave_requested { + // Commit ejected us (or join expired). Real handling - drops + // this convo from its map; + tracing::warn!(convo = %self.convo_id, "conversation requested teardown"); + } + self.after_op(ctx)?; // publish what poll produced + re-arm alarm + Ok(()) + } +} + +impl GroupConvo for GroupV2Convo +where + S: ExternalServices, +{ + fn id(&self) -> ConversationIdRef<'_> { + &self.convo_id + } + #[instrument(name = "groupv2.add_member", skip_all, fields(user_id = %service_ctx.mls_identity.display_name()))] + fn add_member( + &mut self, + service_ctx: &mut ServiceContext, + members: &[IdentIdRef], + ) -> Result<(), ChatError> { + // Record who WE invited before touching the conversation: after_op + // forwards a welcome only to joiners in pending_invites (member-id + // bytes == account name bytes for LocalDemlsMember). + let mut kps = Vec::with_capacity(members.len()); + for member in members { + let device_id = NamespacedIdentity::prefix(member, DEMLS_KEYPACKAGE_NAMESPACE); + let kp_bytes = service_ctx + .registry + .retrieve(&device_id) + .map_err(ChatError::generic)? + .ok_or_else(|| ChatError::generic("No key package"))?; + self.pending_invites + .push(member.as_str().as_bytes().to_vec()); + kps.push(kp_bytes); + } + + let conv = self + .conversation + .as_mut() + .ok_or_else(|| ChatError::generic("no conversation"))?; + for kp_bytes in &kps { + conv.add_member(kp_bytes)?; + } + self.after_op(service_ctx)?; + Ok(()) + } + + // fn conversation_state(&self) -> Result { + // Ok(self + // .conversation + // .as_ref() + // .map(|c| c.state()) + // .unwrap_or(ConversationState::PendingJoin)) + // } +} + +impl GroupV2Convo { + fn after_op( + &mut self, + service_ctx: &mut ServiceContext, + ) -> Result, ChatError> { + let Some(conv) = self.conversation.as_ref() else { + return Ok(Vec::new()); // still pending join — nothing buffered + }; + // Pull everything first (these are &self, take-all): + let events = conv.drain_events(); + let outbound = conv.drain_outbound(); // Vec + let wakeup = conv.next_wakeup_in(); + + // 1. Route welcomes for joiners WE invited (event fires on every member now). + for evt in &events { + if let ConversationEvent::WelcomeReady { welcome, .. } = evt { + for joiner in &welcome.joiner_identities { + if let Some(i) = self.pending_invites.iter().position(|p| p == joiner) { + self.pending_invites.remove(i); + let name = String::from_utf8(joiner.clone()).map_err(ChatError::generic)?; + crate::inbox_v2::invite_user_v2( + &mut service_ctx.ds, + &IdentId::new(name), + welcome, + )?; + } + } + } + } + + // 2. Publish + for out in outbound { + let frame = GroupV2Frame { + payload: Some(GroupV2Payload::DeMlsWrapper(out.payload.into())), + sender_app_id: out.sender, // was pkt.app_id + }; + let payload = AddressedEncryptedPayload { + delivery_address: Self::delivery_address_from_id(&out.conversation_id), + data: EncryptedPayload { + encryption: Some(encrypted_payload::Encryption::Plaintext(Plaintext { + payload: frame.encode_to_vec().into(), + })), + }, + }; + service_ctx + .ds + .publish(payload.into_envelope(out.conversation_id)) + .map_err(ChatError::generic)?; + } + + // 3. Re-arm the alarm with the conversation's earliest deadline. + if let Some(d) = wakeup { + service_ctx + .wakeup_service + .wakeup_in(d, self.convo_id.clone()); + } + Ok(events) + } + + fn events_to_content(&self, events: &[ConversationEvent]) -> Option { + events.iter().find_map(|evt| match evt { + ConversationEvent::AppMessage(AppMessageProto { + payload: Some(app_message::Payload::ConversationMessage(cm)), + }) => Some(ConvoOutcome { + convo_id: self.convo_id.clone(), + content: Some(Content { + bytes: cm.message.clone(), + }), + }), + _ => None, + }) + } +} + +use prost::{Oneof, bytes::Bytes}; + +#[derive(Clone, PartialEq, Message)] +pub struct GroupV2Frame { + #[prost(oneof = "GroupV2Payload", tags = "2, 3")] + pub payload: Option, + #[prost(bytes = "vec", tag = "4")] + pub sender_app_id: Vec, +} + +#[derive(Clone, PartialEq, Oneof)] +pub enum GroupV2Payload { + #[prost(message, tag = "2")] + DeMlsWrapper(Bytes), + #[prost(message, tag = "3")] + MlsCommitMessage(Bytes), +} diff --git a/core/conversations/src/conversation/privatev1.rs b/core/conversations/src/conversation/privatev1.rs index f946adb..8bb1a1e 100644 --- a/core/conversations/src/conversation/privatev1.rs +++ b/core/conversations/src/conversation/privatev1.rs @@ -275,6 +275,10 @@ impl Convo for PrivateV1Convo { content, }) } + + fn wakeup(&mut self, _: &mut ServiceContext) -> Result<(), ChatError> { + Ok(()) + } } impl Debug for PrivateV1Convo { diff --git a/core/conversations/src/core.rs b/core/conversations/src/core.rs index 103599c..0997afa 100644 --- a/core/conversations/src/core.rs +++ b/core/conversations/src/core.rs @@ -1,8 +1,9 @@ use crate::causal_history::{CausalHistoryStore, MissingMessage}; +use crate::conversation::{ConversationIdRef, GroupV1Convo, GroupV2Convo, PrivateV1Convo}; use crate::service_context::{ExternalServices, ServiceContext}; -use crate::{DeliveryService, IdentityProvider, RegistrationService}; +use crate::{DeliveryService, IdentityProvider, RegistrationService, WakeupService}; use crate::{ - conversation::{Convo, GroupConvo, GroupV1Convo, PrivateV1Convo}, + conversation::{Convo, GroupConvo}, errors::ChatError, inbox::Inbox, inbox_v2::{InboxV2, MlsEphemeralPqProvider, MlsIdentityProvider}, @@ -10,9 +11,11 @@ use crate::{ proto::{EncryptedPayload, EnvelopeV1, Message}, }; use crypto::{Identity, PublicKey}; -use openmls::prelude::GroupId; +use openmls::group::GroupId; use shared_traits::IdentIdRef; +use std::collections::HashMap; use storage::{ChatStore, ConversationKind, ConversationStore}; +use tracing::{info, instrument}; pub use crate::conversation::ConversationId; pub use crate::inbox::Introduction; @@ -27,15 +30,18 @@ pub struct Core { services: ServiceContext, inbox: Inbox, pq_inbox: InboxV2, + // Cache of loaded conversations + cached_convos: HashMap>, } // Constructors live on the `(DS, RS, CS)` form: `S` can't be inferred backwards // through `S::DS`, so the bundle is built from the three args here. -impl Core<(IP, DS, RS, CS)> +impl Core<(IP, DS, RS, WS, CS)> where IP: IdentityProvider + 'static, DS: DeliveryService + 'static, RS: RegistrationService + 'static, + WS: WakeupService + 'static, CS: ChatStore + 'static, { /// Opens or creates a `Core` with the given storage configuration. @@ -46,6 +52,7 @@ where ident: IP, delivery: DS, registration: RS, + wakeup_service: WS, mut store: CS, ) -> Result { let identity = if let Some(identity) = store.load_identity()? { @@ -56,7 +63,14 @@ where identity }; - Self::assemble(ident, identity, delivery, registration, store) + Self::assemble( + ident, + identity, + delivery, + registration, + wakeup_service, + store, + ) } /// Creates a new in-memory `Core` (for testing). @@ -66,10 +80,18 @@ where ident: IP, delivery: DS, registration: RS, + wakeup_service: WS, store: CS, ) -> Result { let identity = Identity::new(ident.id().as_str().to_string()); - let mut core = Self::assemble(ident, identity, delivery, registration, store)?; + let mut core = Self::assemble( + ident, + identity, + delivery, + registration, + wakeup_service, + store, + )?; core.register_keypackage()?; core.register_account_bundle()?; @@ -83,6 +105,7 @@ where identity: Identity, mut delivery: DS, registration: RS, + wakeup_service: WS, store: CS, ) -> Result { let inbox = Inbox::new(&identity); @@ -109,9 +132,11 @@ where mls_provider, causal, identity, + wakeup_service, }, inbox, pq_inbox, + cached_convos: HashMap::new(), }) } } @@ -180,6 +205,13 @@ impl<'a, S: ExternalServices + 'static> Core { pub fn create_group_convo( &mut self, participants: &[IdentIdRef], + ) -> Result { + self.create_group_convo_v2(participants) + } + + pub fn create_group_convo_v1( + &mut self, + participants: &[IdentIdRef], ) -> Result { // TODO: (P1) Ensure errors are handled properly. This is a high chance for // desynchronized state: MlsGroup persistence, conversation persistence, and @@ -193,7 +225,27 @@ impl<'a, S: ExternalServices + 'static> Core { kind: ConversationKind::GroupV1, })?; convo.add_member(&mut self.services, participants)?; - Ok(convo.id().to_string()) + let convo_id = convo.id().to_string(); + + self.register_convo(ConvoTypeOwned::Group(Box::new(convo)))?; + + Ok(convo_id) + } + + pub fn create_group_convo_v2( + &mut self, + participants: &[IdentIdRef], + ) -> Result { + // TODO: (P1) Ensure errors are handled properly. This is a high chance for + // desynchronized state: MlsGroup persistence, conversation persistence, and + // invite delivery all happen separately. + let mut convo = GroupV2Convo::new(&mut self.services)?; + convo.add_member(&mut self.services, participants)?; + let convo_id = convo.id().to_string(); + + self.register_convo(ConvoTypeOwned::Group(Box::new(convo)))?; + + Ok(convo_id) } /// Add members to an existing group conversation. @@ -202,13 +254,38 @@ impl<'a, S: ExternalServices + 'static> Core { convo_id: &str, members: &[IdentIdRef], ) -> Result<(), ChatError> { - let mut convo = self.load_group_convo(convo_id)?; - convo.add_member(&mut self.services, members) + if self.cached_convos.contains_key(convo_id) { + let convo = self + .cached_convos + .get_mut(convo_id) + .ok_or_else(|| ChatError::NoConvo(convo_id.to_string()))?; + + match convo { + ConvoTypeOwned::Group(group_convo) => { + group_convo.add_member(&mut self.services, members) + } + } + } else { + let mut convo = self.load_group_convo(convo_id)?; + convo.add_member(&mut self.services, members) + } } pub fn list_conversations(&self) -> Result, ChatError> { + // Check Legacy load_convo store let records = self.services.store.load_conversations()?; - Ok(records.into_iter().map(|r| r.local_convo_id).collect()) + let mut convos: Vec = + records.into_iter().map(|r| r.local_convo_id).collect(); + + // Add cached mls convos + for convo in self.cached_convos.keys() { + convos.push(convo.to_string()); + } + + // Conversations may use both storage mechanisms. + // Remove duplicates + convos.dedup(); + Ok(convos) } pub fn take_missing_messages(&self) -> Vec { @@ -217,11 +294,20 @@ impl<'a, S: ExternalServices + 'static> Core { /// Encrypt and publish `content` to an existing conversation. pub fn send_content(&mut self, convo_id: &str, content: &[u8]) -> Result<(), ChatError> { - let mut convo = self.load_convo(convo_id)?; - convo.send_content(&mut self.services, content) + if self.cached_convos.contains_key(convo_id) { + let convo = self + .cached_convos + .get_mut(convo_id) + .ok_or_else(|| ChatError::NoConvo(convo_id.to_string()))?; + convo.send_content(&mut self.services, content) + } else { + let mut convo = self.load_convo(convo_id)?; + convo.send_content(&mut self.services, content) + } } // Decode bytes and send to protocol for processing. + #[instrument(name = "core.handle_frame", skip_all, fields(user_id = %self.services.mls_identity.display_name()))] pub fn handle_payload(&mut self, payload: &[u8]) -> Result { let env = EnvelopeV1::decode(payload)?; @@ -230,7 +316,10 @@ impl<'a, S: ExternalServices + 'static> Core { match convo_id { c if c == self.inbox.id() => self.dispatch_to_inbox(&env.payload).map(Into::into), - c if c == self.pq_inbox.id() => self.dispatch_to_inbox2(&env.payload).map(Into::into), + c if c == self.pq_inbox.id() => self.dispatch_to_inbox2(&env.payload), + c if self.cached_convos.contains_key(&c) => { + self.dispatch_to_convo(&c, &env.payload).map(Into::into) + } c if self.services.store.has_conversation(&c)? => { self.dispatch_to_convo(&c, &env.payload).map(Into::into) } @@ -250,8 +339,22 @@ impl<'a, S: ExternalServices + 'static> Core { } // Dispatch encrypted payload to the post-quantum inbox. - fn dispatch_to_inbox2(&mut self, payload: &[u8]) -> Result { - self.pq_inbox.handle_frame(payload, &mut self.services) + fn dispatch_to_inbox2(&mut self, payload: &[u8]) -> Result { + if let Some(convo) = self.pq_inbox.handle_frame(&mut self.services, payload)? { + let convo_id = convo.id().to_string(); + // Cache convos created by InboxV2 + self.register_convo(ConvoTypeOwned::Group(convo))?; + + Ok(PayloadOutcome::Inbox(InboxOutcome { + new_conversation: crate::NewConversation { + convo_id, + class: crate::ConversationClass::Group, + }, + initial: None, + })) + } else { + Ok(PayloadOutcome::Empty) + } } // Dispatch encrypted payload to its corresponding conversation. @@ -261,8 +364,49 @@ impl<'a, S: ExternalServices + 'static> Core { enc_payload_bytes: &[u8], ) -> Result { let enc_payload = EncryptedPayload::decode(enc_payload_bytes)?; - let mut convo = self.load_convo(convo_id)?; - convo.handle_frame(&mut self.services, enc_payload) + + if self.cached_convos.contains_key(convo_id) { + let convo_type = self + .cached_convos + .get_mut(convo_id) + .ok_or_else(|| ChatError::NoConvo(convo_id.to_string()))?; + + convo_type.handle_frame(&mut self.services, enc_payload) + } else { + let mut convo = self.load_convo(convo_id)?; + convo.handle_frame(&mut self.services, enc_payload) + } + } + + pub fn wakeup(&mut self, convo_id: ConversationIdRef) -> Result<(), ChatError> { + info!(convos = ?self.cached_convos.keys().collect::>(), id = ?self.services.mls_identity.id(), "Cached Convos"); + + match convo_id { + c if c == self.pq_inbox.id() => todo!(), + c if self.cached_convos.contains_key(c) => self.wakeup_convo(c), + _ => Ok(()), + } + } + + // Dispatch encrypted payload to its corresponding conversation + fn wakeup_convo(&mut self, convo_id: ConversationIdRef) -> Result<(), ChatError> { + let Some(convo) = self.cached_convos.get_mut(convo_id) else { + return Err(ChatError::generic("No Convo Found")); + }; + let convo = match convo { + ConvoTypeOwned::Group(c) => c.as_mut(), + }; + + convo.wakeup(&mut self.services) + } + + fn register_convo(&mut self, convo: ConvoTypeOwned) -> Result<(), ChatError> { + let res = self.cached_convos.insert(convo.id().to_string(), convo); + + match res { + Some(_) => Err(ChatError::generic("Convo already exists. Cannot save")), + None => Ok(()), + } } /// Rebuilds a conversation from storage — the one site that branches on @@ -319,3 +463,45 @@ impl<'a, S: ExternalServices + 'static> Core { .ok_or_else(|| ChatError::NoConvo(convo_id.into())) } } + +#[derive(Debug)] +enum ConvoTypeOwned { + // Pairwise(Box>), + Group(Box>), +} + +impl<'a, S: ExternalServices> ConvoTypeOwned { + pub fn id(&'a self) -> ConversationIdRef<'a> { + match self { + ConvoTypeOwned::Group(group_convo) => group_convo.id(), + } + } +} + +impl Convo for ConvoTypeOwned { + fn send_content( + &mut self, + cx: &mut ServiceContext, + content: &[u8], + ) -> Result<(), ChatError> { + match self { + ConvoTypeOwned::Group(group_convo) => group_convo.send_content(cx, content), + } + } + + fn handle_frame( + &mut self, + cx: &mut ServiceContext, + enc: EncryptedPayload, + ) -> Result { + match self { + ConvoTypeOwned::Group(group_convo) => group_convo.handle_frame(cx, enc), + } + } + + fn wakeup(&mut self, service_ctx: &mut ServiceContext) -> Result<(), ChatError> { + match self { + ConvoTypeOwned::Group(group_convo) => group_convo.wakeup(service_ctx), + } + } +} diff --git a/core/conversations/src/errors.rs b/core/conversations/src/errors.rs index 5b2bfe4..879e923 100644 --- a/core/conversations/src/errors.rs +++ b/core/conversations/src/errors.rs @@ -1,3 +1,4 @@ +use de_mls::{mls_crypto::MlsError, session::ConversationError}; use openmls::{framing::errors::MlsMessageError, prelude::tls_codec}; pub use thiserror::Error; @@ -37,6 +38,10 @@ pub enum ChatError { KeyPackage(#[from] openmls::prelude::KeyPackageVerifyError), #[error("Delivery: {0}")] Delivery(String), + #[error("mls error: {0}")] + MlsError(#[from] MlsError), + #[error("demls error: {0}")] + DeMlsError(#[from] ConversationError), } impl ChatError { diff --git a/core/conversations/src/inbox_v2.rs b/core/conversations/src/inbox_v2.rs index a713b66..23589ff 100644 --- a/core/conversations/src/inbox_v2.rs +++ b/core/conversations/src/inbox_v2.rs @@ -1,31 +1,36 @@ mod identity; mod mls_provider; -use crypto::Ed25519VerifyingKey; -pub use identity::MlsIdentityProvider; -pub(crate) use mls_provider::MlsEphemeralPqProvider; -use shared_traits::IdentId; -use shared_traits::IdentIdRef; - use chat_proto::logoschat::envelope::EnvelopeV1; +use crypto::Ed25519VerifyingKey; +use de_mls::protos::de_mls::messages::v1::MemberWelcome; use openmls::prelude::tls_codec::Serialize; use openmls::prelude::*; use prost::{Message, Oneof}; +use std::cell::RefCell; use storage::{ConversationKind, ConversationMeta, ConversationStore}; +use tracing::info; +use tracing::instrument; + +pub use identity::MlsIdentityProvider; +pub(crate) use mls_provider::MlsEphemeralPqProvider; use crate::ChatError; use crate::DeliveryService; -use crate::IdentityProvider; use crate::RegistrationService; -use crate::conversation::ConversationId; +use crate::conversation::GroupConvo; use crate::conversation::GroupV1Convo; -use crate::outcomes::{ConversationClass, InboxOutcome, NewConversation}; +use crate::conversation::GroupV2Convo; use crate::service_context::{ExternalServices, ServiceContext}; use crate::utils::{blake2b_hex, hash_size}; use crate::{ AccountAuthority, AccountDirectory, AddressedEnvelope, SignedDeviceBundle, encode_bundle_payload, }; +use crate::{IdentId, IdentIdRef, IdentityProvider}; + +// Downgraded from MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519 until demls accepts an external provider +const CIPHER_SUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; // Define unique Identifiers derivations used in InboxV2 fn delivery_address_for(ident_id: IdentIdRef) -> String { @@ -47,17 +52,43 @@ pub trait MlsProvider: OpenMlsProvider { ) -> Result<(), ChatError>; } +/// Deliver a de-mls welcome to `account_id` over its InboxV2 1-1 channel. +/// Function mirroring the GroupV1 `invite_user` path, but carrying a de-mls `MemberWelcome`. +pub fn invite_user_v2( + ds: &mut DS, + account_id: IdentIdRef, + welcome: &MemberWelcome, +) -> Result<(), ChatError> { + let frame = InboxV2Frame { + payload: Some(InviteType::GroupV2(welcome.encode_to_vec())), + }; + let envelope = EnvelopeV1 { + conversation_hint: conversation_id_for(account_id), + salt: 0, + payload: frame.encode_to_vec().into(), + }; + ds.publish(AddressedEnvelope { + delivery_address: delivery_address_for(account_id), + data: envelope.encode_to_vec(), + }) + .map_err(ChatError::generic) +} + /// An PQ focused Conversation initializer. /// InboxV2 Incorporates an Account based identity system to support PQ based conversation protocols /// such as MLS. pub struct InboxV2 { // Account_id field is an owned value, so it can be returned via reference. ident_id: IdentId, + pending_demls: RefCell>, } impl InboxV2 { pub fn new(ident_id: IdentId) -> Self { - Self { ident_id } + Self { + ident_id, + pending_demls: RefCell::new(None), + } } pub fn ident_id(&self) -> IdentIdRef<'_> { @@ -66,7 +97,7 @@ impl InboxV2 { /// Submit MlsKeypackage to registration service pub fn register( - &self, + &mut self, cx: &mut ServiceContext, ) -> Result<(), ChatError> { let keypackage_bytes = Self::create_keypackage(cx)?.tls_serialize_detached()?; @@ -75,7 +106,15 @@ impl InboxV2 { // "LastResort" package or publish multiple cx.registry .register(&cx.mls_identity, keypackage_bytes) - .map_err(ChatError::generic) + .map_err(ChatError::generic)?; + + // de-mls (GroupV2) joiner: build a conversation-less User and register + // its de-mls key package under the same account name. This shadows the + // OpenMLS key package above in the registry; GroupV2 is the path the + // de-mls integration exercises. + *self.pending_demls.borrow_mut() = Some(GroupV2Convo::new_pending(cx)?); + + Ok(()) } pub fn delivery_address(&self) -> String { @@ -86,20 +125,38 @@ impl InboxV2 { conversation_id_for(&self.ident_id) } + #[instrument(name = "inboxV2.handle_frame", skip_all, fields(user_id = %service_ctx.mls_identity.display_name()))] pub fn handle_frame( &self, + service_ctx: &mut ServiceContext, payload_bytes: &[u8], - cx: &mut ServiceContext, - ) -> Result { - let inbox_frame = InboxV2Frame::decode(payload_bytes)?; - + ) -> Result>>, ChatError> { + // On a broadcast transport the inbox address also receives traffic + // that isn't an invite (or that prost decodes into an empty frame). + // Treat anything we can't interpret as "not for us" and skip it, + // rather than failing the whole poll cycle. + let Ok(inbox_frame) = InboxV2Frame::decode(payload_bytes) else { + return Ok(None); + }; let Some(payload) = inbox_frame.payload else { - return Err(ChatError::BadParsing("InboxV2Payload missing")); + return Ok(None); }; match payload { - InviteType::GroupV1(group_v1_heavy_invite) => { - self.handle_heavy_invite(group_v1_heavy_invite, cx) + InviteType::GroupV1(inv) => { + Ok(Some(Box::new(self.handle_heavy_invite(service_ctx, inv)?))) + } + InviteType::GroupV2(welcome_bytes) => { + info!("Process WelcomeMessage"); + let mut convo = self + .pending_demls + .borrow_mut() + .take() + .ok_or_else(|| ChatError::generic("no pending de-mls convo"))?; + let mw = + MemberWelcome::decode(welcome_bytes.as_slice()).map_err(ChatError::generic)?; + convo.accept_welcome(service_ctx, &mw)?; + Ok(Some(Box::new(convo))) } } } @@ -123,9 +180,9 @@ impl InboxV2 { fn handle_heavy_invite( &self, - invite: GroupV1HeavyInvite, cx: &mut ServiceContext, - ) -> Result { + invite: GroupV1HeavyInvite, + ) -> Result { let (msg_in, _rest) = MlsMessageIn::tls_deserialize_bytes(invite.welcome_bytes.as_slice())?; let MlsMessageBodyIn::Welcome(welcome) = msg_in.extract() else { @@ -136,30 +193,22 @@ impl InboxV2 { }; let convo = GroupV1Convo::new_from_welcome(cx, welcome)?; - let convo_id: ConversationId = convo.id().to_string(); self.persist_convo(&convo, cx)?; - Ok(InboxOutcome { - new_conversation: NewConversation { - convo_id, - class: ConversationClass::Group, - }, - initial: None, - }) + + Ok(convo) } fn create_keypackage( cx: &ServiceContext, ) -> Result { let capabilities = Capabilities::builder() - .ciphersuites(vec![ - Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519, - ]) + .ciphersuites(vec![CIPHER_SUITE]) .extensions(vec![ExtensionType::ApplicationId]) .build(); let a = KeyPackage::builder() .leaf_node_capabilities(capabilities) .build( - Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519, + CIPHER_SUITE, &cx.mls_provider, &cx.mls_identity, cx.mls_identity.get_credential(), @@ -244,7 +293,7 @@ impl InboxV2 { #[derive(Clone, PartialEq, Message)] pub struct InboxV2Frame { - #[prost(oneof = "InviteType", tags = "1")] + #[prost(oneof = "InviteType", tags = "1, 2")] pub payload: Option, } @@ -252,6 +301,8 @@ pub struct InboxV2Frame { pub enum InviteType { #[prost(message, tag = "1")] GroupV1(GroupV1HeavyInvite), + #[prost(bytes, tag = "2")] + GroupV2(Vec), } #[derive(Clone, PartialEq, Message)] diff --git a/core/conversations/src/lib.rs b/core/conversations/src/lib.rs index 6be2c50..b701b12 100644 --- a/core/conversations/src/lib.rs +++ b/core/conversations/src/lib.rs @@ -27,8 +27,8 @@ pub use outcomes::{ Content, ConversationClass, ConvoOutcome, InboxOutcome, NewConversation, PayloadOutcome, }; pub use service_context::ExternalServices; -pub use service_traits::{DeliveryService, RegistrationService}; -pub use shared_traits::IdentityProvider; +pub use service_traits::{DeliveryService, RegistrationService, WakeupService}; +pub use shared_traits::{IdentId, IdentIdRef, IdentityProvider}; pub use storage::ConversationKind; pub use types::AddressedEnvelope; pub use utils::hex_trunc; diff --git a/core/conversations/src/service_context.rs b/core/conversations/src/service_context.rs index 01cd095..eb03210 100644 --- a/core/conversations/src/service_context.rs +++ b/core/conversations/src/service_context.rs @@ -6,6 +6,7 @@ use storage::ChatStore; use crate::IdentityProvider; use crate::causal_history::CausalHistoryStore; use crate::inbox_v2::{MlsEphemeralPqProvider, MlsIdentityProvider}; +use crate::service_traits::WakeupService; use crate::{DeliveryService, RegistrationService}; /// Bundles the external service types (`DS`, `RS`, `CS`) behind one `S`. The @@ -14,19 +15,22 @@ pub trait ExternalServices { type IP: IdentityProvider; type DS: DeliveryService; type RS: RegistrationService; + type WS: WakeupService; type CS: ChatStore; } -impl ExternalServices for (IP, DS, RS, CS) +impl ExternalServices for (IP, DS, RS, WS, CS) where IP: IdentityProvider, DS: DeliveryService, RS: RegistrationService, + WS: WakeupService, CS: ChatStore, { type IP = IP; type DS = DS; type RS = RS; + type WS = WS; type CS = CS; } @@ -39,6 +43,7 @@ pub(crate) struct ServiceContext { pub(crate) mls_provider: MlsEphemeralPqProvider, pub(crate) causal: CausalHistoryStore, pub(crate) identity: Identity, + pub(crate) wakeup_service: S::WS, } #[cfg(test)] @@ -106,7 +111,16 @@ mod test_support { } } - impl ServiceContext<(IP, NoopDelivery, NoopRegistration, CS)> { + #[derive(Debug)] + pub(crate) struct NoopWakeups; + + impl WakeupService for NoopWakeups { + fn wakeup_in(&mut self, _: std::time::Duration, _: crate::ConversationId) {} + } + + impl + ServiceContext<(IP, NoopDelivery, NoopRegistration, NoopWakeups, CS)> + { /// Builds a context around a real store, stubbing other services. pub(crate) fn for_test(ident: IP, store: CS) -> Result { let name = ident.id().as_str().to_string(); @@ -118,6 +132,7 @@ mod test_support { mls_provider: MlsEphemeralPqProvider::new().map_err(ChatError::generic)?, causal: CausalHistoryStore::new(), identity: Identity::new(name), + wakeup_service: NoopWakeups {}, }) } } diff --git a/core/conversations/src/service_traits.rs b/core/conversations/src/service_traits.rs index dfad9a0..3e4b886 100644 --- a/core/conversations/src/service_traits.rs +++ b/core/conversations/src/service_traits.rs @@ -2,9 +2,12 @@ /// platform clients. Platforms can alter the behaviour of the chat core by supplying /// different implementations. use shared_traits::IdentityProvider; -use std::{fmt::Debug, fmt::Display}; +use std::{ + fmt::{Debug, Display}, + time::Duration, +}; -use crate::{AccountDirectory, types::AddressedEnvelope}; +use crate::{AccountDirectory, ConversationId, types::AddressedEnvelope}; /// A Delivery service is responsible for payload transport. /// This interface allows Conversations to send payloads on the wire as well as @@ -62,3 +65,7 @@ impl KeyPackageProvider for T { RegistrationService::retrieve(self, device_id) } } + +pub trait WakeupService: Debug { + fn wakeup_in(&mut self, duration: Duration, convo_id: ConversationId); +} diff --git a/core/integration_tests_core/Cargo.toml b/core/integration_tests_core/Cargo.toml index 0f4bde8..8a5cabf 100644 --- a/core/integration_tests_core/Cargo.toml +++ b/core/integration_tests_core/Cargo.toml @@ -6,13 +6,22 @@ edition = "2024" # [[test]] # name = "integration_tests_core" -[dev-dependencies] +[dependencies] # Workspace dependencies (sorted) chat-sqlite = { workspace = true } components = { workspace = true } libchat = { workspace = true } -logos-account = { workspace = true , features = ["dev"]} +logos-account = { workspace = true, features = ["dev"]} +shared-traits = { workspace = true } + +# External dependencies (sorted) +tracing = "0.1" + +[dev-dependencies] +chat-sqlite = { workspace = true } storage = { workspace = true } # External dependencies (sorted) tempfile = "3" +tracing = "0.1.44" +tracing-subscriber = "0.3" diff --git a/core/integration_tests_core/src/lib.rs b/core/integration_tests_core/src/lib.rs index 8b13789..0c2e590 100644 --- a/core/integration_tests_core/src/lib.rs +++ b/core/integration_tests_core/src/lib.rs @@ -1 +1,4 @@ +mod test_client; +mod wakeup; +pub use test_client::TestHarness; diff --git a/core/integration_tests_core/src/test_client.rs b/core/integration_tests_core/src/test_client.rs new file mode 100644 index 0000000..3843f93 --- /dev/null +++ b/core/integration_tests_core/src/test_client.rs @@ -0,0 +1,335 @@ +use libchat::{ConversationId, Core, IdentityProvider, PayloadOutcome}; +use logos_account::TestLogosAccount; +use shared_traits::IdentId; +use std::collections::HashMap; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; +use std::time::Duration; +use tracing::{info, warn}; + +use components::{EphemeralRegistry, LocalBroadcaster, MemStore}; + +use crate::wakeup::{TestWakeupProvider, TestWakeupService, WakeupRecord}; + +type OnMessageCallback = dyn Fn(&TestClient, PayloadOutcome); + +type WS = TestWakeupService; +type WP = TestWakeupProvider; + +const SARO: usize = 0; +const RAYA: usize = 1; +const PAX: usize = 2; +const MIRA: usize = 3; + +// type ClientType = CoreClient; +type ClientType = Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + WP, + MemStore, +)>; + +#[derive(Debug)] +pub struct ReceivedMessage { + pub convo_id: ConversationId, + pub contents: T, +} + +pub struct TestClient { + inner: ClientType, + received_messages: Vec>>, +} + +impl TestClient { + fn init(client: ClientType) -> Self { + Self { + inner: client, + received_messages: vec![], + } + } + + pub fn addr(&self) -> IdentId { + self.inner.ident_id().clone() + } + + fn drain_outcomes(&mut self) -> Vec { + let mut messages = vec![]; + while let Some(data) = self.inner.ds().poll() { + messages.push(data); + } + + let mut outcomes = vec![]; + for data in messages { + let outcome = self.inner.handle_payload(&data).unwrap(); + warn!(id= ?self.ident_id(),?outcome, "DRAIN CLIENT"); + // Copy Convo Messages to received buffer + + match &outcome { + PayloadOutcome::Empty => continue, + PayloadOutcome::Convo(convo_outcome) => { + if let Some(data) = &convo_outcome.content { + info!( + content = String::from_utf8_lossy(&data.bytes).to_string(), + "COT" + ); + self.received_messages.push(ReceivedMessage { + convo_id: convo_outcome.convo_id.clone(), + contents: data.bytes.clone(), + }); + } + } + PayloadOutcome::Inbox(_) => {} + } + + if !matches!(outcome, PayloadOutcome::Empty) { + outcomes.push(outcome); + } + } + outcomes + } + + pub fn received_messages(&self) -> &[ReceivedMessage>] { + &self.received_messages + } + + pub fn check(&self, convo_id: &str, content: &[u8]) -> bool { + for msg in &self.received_messages { + if msg.convo_id == convo_id && msg.contents == content { + return true; + } + } + false + } + + pub fn convo_count(&self) -> usize { + self.list_conversations().map_or(0, |v| v.len()) + } +} + +impl Deref for TestClient { + type Target = ClientType; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for TestClient { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +#[allow(unused)] +pub struct Observation { + ident: IdentId, + outcome: PayloadOutcome, +} + +#[allow(unused)] +pub struct TestHarness { + addresses: HashMap, + clients: Vec, + wakeup_service: WS, + cb: Box, + // List of outcomes that were detected across all clients. + pub observed_outcomes: Vec, +} + +impl TestHarness { + pub fn new(cb: impl Fn(&TestClient, PayloadOutcome) + 'static) -> Self { + const { assert!(N > 0, "TestHarness requires at least one client") }; + const { assert!(N <= 4, "Only 4 clients are supported(Soft Limit") }; + + let mut clients = vec![]; + let mut addresses = HashMap::new(); + + let ds = LocalBroadcaster::new(); + let rs = EphemeralRegistry::new(); + let ws = TestWakeupService::new(); + + for i in 0..N { + let wp = ws.new_provider(i); + let ident = TestLogosAccount::new(Self::names(i)); + + addresses.insert(i, ident.id().clone()); + let core_client = + ClientType::new_with_name(ident, ds.clone(), rs.clone(), wp, MemStore::new()) + .unwrap(); + + let client = TestClient::init(core_client); + + clients.push(client); + } + + dbg!(&rs); + + Self { + addresses, + clients, + wakeup_service: ws, + cb: Box::new(cb), + observed_outcomes: vec![], + } + } + + pub fn client(&mut self, i: usize) -> &TestClient { + &self.clients[i] + } + + pub fn client_mut(&mut self, i: usize) -> &mut TestClient { + &mut self.clients[i] + } + + fn names(i: usize) -> &'static str { + match i { + SARO => "saro", + RAYA => "raya", + PAX => "pax", + MIRA => "mira", + _ => "unnamed", + } + } + + pub fn process(&mut self, duration: Duration) { + self.process_payloads(); + + let records = self.wakeup_service.advance_time(duration); + self.process_records(records); + } + + pub fn process_until(&mut self, predicate: impl Fn(&mut TestHarness) -> bool) { + let timeout = Duration::from_mins(1); + let step = Duration::from_millis(50); + let mut elapsed = Duration::ZERO; + + while !predicate(self) { + if elapsed >= timeout { + panic!("process_until timed out after {:?}", timeout); + } + self.process(step); + elapsed += step; + } + } + + pub fn process_until_label( + &mut self, + label: &str, + predicate: impl Fn(&mut TestHarness) -> bool, + ) { + info!(label, "Process Until"); + self.process_until(predicate); + } + + fn process_payloads(&mut self) { + // Process existing payloads for all clients. + for client in self.clients.iter_mut() { + for outcome in client.drain_outcomes() { + info!(id = ?client.ident_id(), ?outcome, "Process drain"); + self.observed_outcomes.push(Observation { + ident: client.ident_id().clone(), + outcome: outcome.clone(), + }); + info!(id = ?client.ident_id(), ?outcome, "Process drain"); + (self.cb)(client, outcome) + } + } + } + + fn process_records(&mut self, records: Vec) { + for record in records { + self.clients[record.client_index] + .wakeup(&record.convo_id) + .expect("Error During wakeup"); + } + } +} + +// Avoid Developer confusion by gating access functions +// based on the number of clients in the harness + +impl TestHarness<1> { + pub fn saro(&mut self) -> &mut TestClient { + &mut self.clients[SARO] + } +} + +impl TestHarness<2> { + pub fn saro(&mut self) -> &mut TestClient { + &mut self.clients[SARO] + } + + pub fn raya(&mut self) -> &mut TestClient { + &mut self.clients[RAYA] + } +} + +impl TestHarness<3> { + pub fn saro(&mut self) -> &mut TestClient { + &mut self.clients[SARO] + } + + pub fn raya(&mut self) -> &mut TestClient { + &mut self.clients[RAYA] + } + + pub fn pax(&mut self) -> &mut TestClient { + &mut self.clients[PAX] + } +} + +impl TestHarness<4> { + pub fn saro(&mut self) -> &mut TestClient { + &mut self.clients[SARO] + } + + pub fn raya(&mut self) -> &mut TestClient { + &mut self.clients[RAYA] + } + + pub fn pax(&mut self) -> &mut TestClient { + &mut self.clients[PAX] + } + + pub fn mira(&mut self) -> &mut TestClient { + &mut self.clients[MIRA] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer() + .try_init(); + + let mut harness = TestHarness::<2>::new(|client, outcome| { + info!( id=?&client.ident_id(), outcome = ?outcome, "Result"); + }); + + //Create Convo + let particpants = &[&harness.raya().addr()]; + let convo_id = harness + .saro() + .create_group_convo(particpants) + .expect("saro create group"); + + harness.process_until_label("Raya Join", |h| h.raya().convo_count() == 1); + + assert_eq!(harness.raya().convo_count(), 1, "raya did not join"); + + harness + .saro() + .send_content(convo_id.as_str(), b"Hello") + .expect("raya send"); + + harness.process(Duration::from_millis(200)); + + assert!(harness.raya().check(&convo_id, b"Hello")) + } +} diff --git a/core/integration_tests_core/src/wakeup.rs b/core/integration_tests_core/src/wakeup.rs new file mode 100644 index 0000000..abcb125 --- /dev/null +++ b/core/integration_tests_core/src/wakeup.rs @@ -0,0 +1,176 @@ +use libchat::{ConversationId, WakeupService}; +use std::cell::RefCell; +use std::cmp::Reverse; +use std::collections::BinaryHeap; +use std::fmt::Debug; +use std::rc::Rc; +use std::time::Duration; +use tracing::{info, trace}; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct WakeupRecord { + pub expiry: Duration, + pub client_index: usize, + pub convo_id: String, +} + +pub struct TestWakeupProvider { + service: Rc>, + client_index: usize, +} + +impl Debug for TestWakeupProvider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TestWakeupProvider") + .field("client_index", &self.client_index) + .finish() + } +} + +impl TestWakeupProvider { + pub fn new(service: Rc>, id: usize) -> Self { + Self { + service, + client_index: id, + } + } +} + +impl WakeupService for TestWakeupProvider { + fn wakeup_in(&mut self, duration: Duration, convo_id: ConversationId) { + info!(?duration, convo_id, "Wakeup In"); + self.service + .borrow_mut() + .register_wakeup(duration, self.client_index, convo_id); + } +} + +pub struct InnerWakeupService { + now: Duration, + pending: BinaryHeap>, +} + +impl InnerWakeupService { + pub fn new() -> Self { + Self { + now: Duration::new(0, 0), + pending: BinaryHeap::new(), + } + } + + pub fn register_wakeup(&mut self, wake_in: Duration, client_index: usize, convo_id: String) { + info!(%client_index, ?wake_in, "ask for wake up"); + self.pending.push(Reverse(WakeupRecord { + expiry: self.now + wake_in, + client_index, + convo_id, + })); + } + + fn get_expired(&mut self) -> Vec { + trace!("Get Expired"); + let mut fired = vec![]; + + while self + .pending + .peek() + .is_some_and(|Reverse(w)| w.expiry <= self.now) + { + let Reverse(w) = self.pending.pop().unwrap(); + info!(now = self.now.as_secs(), w.convo_id, "Popping"); + fired.push(w); + } + + fired + } +} + +pub struct TestWakeupService { + inner: Rc>, +} + +impl Debug for TestWakeupService { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let srv = self.inner.borrow_mut(); + + f.debug_struct("TestWakeupService") + .field("heap", &srv.pending) + .finish() + } +} + +impl TestWakeupService { + pub fn new() -> Self { + Self { + inner: Rc::new(RefCell::new(InnerWakeupService::new())), + } + } + + pub fn new_provider(&self, id: usize) -> TestWakeupProvider { + TestWakeupProvider { + service: self.inner.clone(), + client_index: id, + } + } + + // Returns the ConvoIDs that triggered in order + pub fn advance_time(&mut self, duration: Duration) -> Vec { + let mut srv = self.inner.borrow_mut(); + trace!(?duration, "Advanced"); + // de-mls deadlines are real wall-clock; sleep so the millisecond-scale + // commit/consensus timers actually elapse between poll cycles + // Note: This is error prone as WakeupService tracks its own `now` variable. Does not account for processing time. + std::thread::sleep(duration); + + srv.now = srv.now.checked_add(duration).unwrap(); + srv.get_expired() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wakeup_service() { + let _ = tracing_subscriber::fmt().with_test_writer().try_init(); + + let mut ws = TestWakeupService::new(); + + let mut p1 = ws.new_provider(1); + let mut p2 = ws.new_provider(2); + + p1.wakeup_in(Duration::from_secs(2), "convo1".into()); + p1.wakeup_in(Duration::from_secs(4), "convo1".into()); + + p2.wakeup_in(Duration::from_secs(5), "convo1".into()); + p2.wakeup_in(Duration::from_secs(4), "convo1".into()); + + { + let batch = ws.advance_time(Duration::from_secs(2)); + assert_eq!(batch.len(), 1, "too many records"); + assert_eq!(batch[0].client_index, 1, "client mismatch"); + } + + { + let batch = ws.advance_time(Duration::from_secs(2)); + assert_eq!(batch.len(), 2, "too many records"); + assert_eq!( + batch[0].client_index, 1, + "client 1 shoudld be first, as it was entered first" + ); + assert_eq!(batch[1].client_index, 2, "client 2 should be second"); + } + + { + let batch = ws.advance_time(Duration::from_secs(1)); + assert_eq!(batch.len(), 1, "too many records"); + assert_eq!(batch[0].client_index, 2, "client mismatch"); + } + + { + let batch = ws.advance_time(Duration::from_secs(1)); + assert_eq!(batch.len(), 0, "records should be completely drained"); + } + } +} diff --git a/core/integration_tests_core/tests/causal_history.rs b/core/integration_tests_core/tests/causal_history.rs index 5c96339..4757d55 100644 --- a/core/integration_tests_core/tests/causal_history.rs +++ b/core/integration_tests_core/tests/causal_history.rs @@ -7,13 +7,21 @@ use std::ops::{Deref, DerefMut}; use components::{EphemeralRegistry, LocalBroadcaster, MemStore}; -use libchat::{Core, MissingMessage}; +use libchat::{Core, MissingMessage, WakeupService}; use logos_account::TestLogosAccount; + +#[derive(Debug)] +struct NoopWakeupService {} +impl WakeupService for NoopWakeupService { + fn wakeup_in(&mut self, _: std::time::Duration, _: libchat::ConversationId) {} +} + struct Client { inner: Core<( TestLogosAccount, LocalBroadcaster, EphemeralRegistry, + NoopWakeupService, MemStore, )>, } @@ -24,6 +32,7 @@ impl Client { TestLogosAccount, LocalBroadcaster, EphemeralRegistry, + NoopWakeupService, MemStore, )>, ) -> Self { @@ -54,6 +63,7 @@ impl Deref for Client { TestLogosAccount, LocalBroadcaster, EphemeralRegistry, + NoopWakeupService, MemStore, )>; fn deref(&self) -> &Self::Target { @@ -73,19 +83,31 @@ fn missing_group_message_is_detected() { let rs = EphemeralRegistry::new(); let saro_account = TestLogosAccount::new("saro"); - let saro_ctx = - Core::new_with_name(saro_account, ds.new_consumer(), rs.clone(), MemStore::new()).unwrap(); + let saro_ctx = Core::new_with_name( + saro_account, + ds.new_consumer(), + rs.clone(), + NoopWakeupService {}, + MemStore::new(), + ) + .unwrap(); let raya_account = TestLogosAccount::new("raya"); - let raya_ctx = - Core::new_with_name(raya_account, ds.clone(), rs.clone(), MemStore::new()).unwrap(); + let raya_ctx = Core::new_with_name( + raya_account, + ds.clone(), + rs.clone(), + NoopWakeupService {}, + MemStore::new(), + ) + .unwrap(); let mut saro = Client::init(saro_ctx); let mut raya = Client::init(raya_ctx); // Saro creates a group with Raya. let raya_id = raya.ident_id().clone(); - let convo_id = saro.create_group_convo(&[&raya_id]).unwrap().to_string(); + let convo_id = saro.create_group_convo_v1(&[&raya_id]).unwrap().to_string(); // Raya joins (processes the Welcome + commit). raya.process_messages(); diff --git a/core/integration_tests_core/tests/mls_integration.rs b/core/integration_tests_core/tests/mls_integration.rs index 9f578aa..d80e690 100644 --- a/core/integration_tests_core/tests/mls_integration.rs +++ b/core/integration_tests_core/tests/mls_integration.rs @@ -1,212 +1,60 @@ -use std::ops::{Deref, DerefMut}; - -use components::{EphemeralRegistry, LocalBroadcaster, MemStore}; -use libchat::{ - Content, ConversationClass, ConvoOutcome, Core, NewConversation, PayloadOutcome, hex_trunc, -}; -use logos_account::TestLogosAccount; - -type ResultCallback = Box; - -// Simple client Functionality for testing -struct Client { - inner: Core<( - TestLogosAccount, - LocalBroadcaster, - EphemeralRegistry, - MemStore, - )>, - on_result: Option, - new_conversations: Vec, - received_messages: Vec<(libchat::ConversationId, Content)>, -} - -impl Client { - fn init( - core: Core<( - TestLogosAccount, - LocalBroadcaster, - EphemeralRegistry, - MemStore, - )>, - cb: Option, - ) -> Self { - Client { - inner: core, - on_result: cb.map(|f| Box::new(f) as ResultCallback), - new_conversations: Vec::new(), - received_messages: Vec::new(), - } - } - - fn process_messages(&mut self) { - let payloads: Vec<_> = { - let ds = self.ds(); - std::iter::from_fn(|| ds.poll()).collect() - }; - - for data in payloads { - let result = self.handle_payload(&data).unwrap(); - if let Some(cb) = &self.on_result { - cb(&result); - } - match result { - PayloadOutcome::Empty => {} - PayloadOutcome::Convo(co) => self.absorb_convo_outcome(co), - PayloadOutcome::Inbox(io) => { - self.new_conversations.push(io.new_conversation); - if let Some(initial) = io.initial { - self.absorb_convo_outcome(initial); - } - } - } - } - } - - fn absorb_convo_outcome(&mut self, outcome: ConvoOutcome) { - if let Some(content) = outcome.content { - self.received_messages.push((outcome.convo_id, content)); - } - } -} - -impl Deref for Client { - type Target = Core<( - TestLogosAccount, - LocalBroadcaster, - EphemeralRegistry, - MemStore, - )>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for Client { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -// Higher order function to handle printing -fn pretty_print(prefix: impl Into) -> ResultCallback { - let prefix = prefix.into(); - Box::new(move |result: &PayloadOutcome| match result { - PayloadOutcome::Empty => {} - PayloadOutcome::Inbox(io) => { - let cid = hex_trunc(io.new_conversation.convo_id.as_bytes()); - println!( - "{prefix} ({cid:?}) [conversation started: {:?}]", - io.new_conversation.class - ); - if let Some(initial) = &io.initial { - print_contents(&prefix, initial); - } - } - PayloadOutcome::Convo(co) => print_contents(&prefix, co), - }) -} - -fn print_contents(prefix: &str, outcome: &ConvoOutcome) { - let cid = hex_trunc(outcome.convo_id.as_bytes()); - if let Some(content) = &outcome.content { - let text = String::from_utf8_lossy(&content.bytes); - println!("{prefix} ({cid:?}) {text}"); - } -} - -fn process(clients: &mut Vec) { - for client in clients { - client.process_messages(); - } -} +use integration_tests_core::TestHarness; +use std::time::Duration; #[test] fn create_group() { - let ds = LocalBroadcaster::new(); - let rs = EphemeralRegistry::new(); + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer() + .try_init(); - let saro_ident = TestLogosAccount::new("saro"); - let saro = - Core::new_with_name(saro_ident, ds.new_consumer(), rs.clone(), MemStore::new()).unwrap(); + let mut harness = TestHarness::<3>::new(|_, _| {}); - let raya_ident = TestLogosAccount::new("raya"); - let raya = Core::new_with_name(raya_ident, ds.clone(), rs.clone(), MemStore::new()).unwrap(); + let raya_id = harness.raya().ident_id().clone(); + let pax_id = harness.pax().ident_id().clone(); - let mut clients = vec![ - Client::init(saro, Some(pretty_print(" Saro "))), - Client::init(raya, Some(pretty_print(" Raya "))), - ]; + const M_R1: &[u8; 12] = b"Hi From Raya"; + const M_P1: &[u8; 13] = b"Hey it's Pax!"; - const SARO: usize = 0; - const RAYA: usize = 1; + // Step: Saro Create Convo with Raya - let raya_id = clients[RAYA].ident_id().clone(); - let convo_id = clients[SARO] - .create_group_convo(&[&raya_id]) - .unwrap() - .to_string(); + let convo_id = harness + .saro() + .create_group_convo_v1(&[&raya_id]) + .expect("Saro invite Raya "); + harness.process_until(|h| h.raya().list_conversations().unwrap().len() == 1); - // Raya can read this message because - // 1) It was sent after add_members was committed, and - // 2) LocalBroadcaster provides historical messages. + // Step: Raya Send Content - clients[SARO] - .send_content(&convo_id, b"ok who broke the group chat again") - .unwrap(); + harness + .raya() + .send_content(&convo_id, M_R1) + .expect("Raya send Msg"); - process(&mut clients); + harness.process_until(|h| h.saro().received_messages().len() == 1); - // Raya should observe exactly one new Group conversation from the - // welcome, even though no initial content arrives with it. - let raya_started = clients[RAYA] - .new_conversations - .iter() - .filter(|nc| matches!(nc.class, ConversationClass::Group)) - .count(); - assert_eq!( - raya_started, 1, - "Raya should have observed exactly one new Group conversation for the welcome" - ); + // Step: Saro add Pax - clients[RAYA] - .send_content(&convo_id, b"it was literally working five minutes ago") - .unwrap(); - - process(&mut clients); - - let pax_ident = TestLogosAccount::new("pax"); - let pax = Core::new_with_name(pax_ident, ds, rs, MemStore::new()).unwrap(); - clients.push(Client::init(pax, Some(pretty_print(" Pax")))); - const PAX: usize = 2; - - let pax_id = clients[PAX].ident_id().clone(); - clients[SARO] + harness + .saro() .group_add_member(&convo_id, &[&pax_id]) - .unwrap(); + .expect("Saro invite pax"); + harness.process_until(|h| h.pax().list_conversations().unwrap().len() == 1); - process(&mut clients); + // Step: Pax send Content - let pax_started = clients[PAX] - .new_conversations - .iter() - .filter(|nc| matches!(nc.class, ConversationClass::Group)) - .count(); - assert_eq!( - pax_started, 1, - "Pax should have observed exactly one new Group conversation for the welcome" - ); + harness + .pax() + .send_content(&convo_id, M_P1) + .expect("Pax send"); + harness.process(Duration::from_millis(500)); - clients[PAX] - .send_content(&convo_id, b"ngl the key rotation is cooked") - .unwrap(); + assert!(harness.saro().check(&convo_id, M_R1)); + assert!(harness.saro().check(&convo_id, M_P1)); - process(&mut clients); + assert!(!harness.raya().check(&convo_id, M_R1)); + assert!(harness.raya().check(&convo_id, M_P1)); - clients[SARO] - .send_content(&convo_id, b"bro we literally just added you to the group ") - .unwrap(); - - process(&mut clients); + assert!(!harness.pax().check(&convo_id, M_R1)); + assert!(!harness.pax().check(&convo_id, M_P1)); } diff --git a/core/integration_tests_core/tests/private_integration.rs b/core/integration_tests_core/tests/private_integration.rs index 388afd3..61e9584 100644 --- a/core/integration_tests_core/tests/private_integration.rs +++ b/core/integration_tests_core/tests/private_integration.rs @@ -1,15 +1,22 @@ use chat_sqlite::{ChatStorage, StorageConfig}; -use libchat::{ConversationClass, Core, Introduction, PayloadOutcome}; +use libchat::{ConversationClass, Core, Introduction, PayloadOutcome, WakeupService}; use logos_account::TestLogosAccount; use storage::{ConversationStore, IdentityStore}; use tempfile::tempdir; use components::{EphemeralRegistry, LocalBroadcaster}; +#[derive(Debug)] +struct NoopWakeupService {} +impl WakeupService for NoopWakeupService { + fn wakeup_in(&mut self, _: std::time::Duration, _: libchat::ConversationId) {} +} + type PrivateCore = Core<( TestLogosAccount, LocalBroadcaster, EphemeralRegistry, + NoopWakeupService, ChatStorage, )>; @@ -60,11 +67,19 @@ fn ctx_integration() { saro_account, ds.clone(), rs.clone(), + NoopWakeupService {}, ChatStorage::in_memory(), ) .unwrap(); let raya_account = TestLogosAccount::new("raya"); - let mut raya = Core::new_with_name(raya_account, ds, rs, ChatStorage::in_memory()).unwrap(); + let mut raya = Core::new_with_name( + raya_account, + ds, + rs, + NoopWakeupService {}, + ChatStorage::in_memory(), + ) + .unwrap(); // Raya creates intro bundle and sends to Saro let bundle = raya.create_intro_bundle().unwrap(); @@ -107,7 +122,7 @@ fn identity_persistence() { let rs = EphemeralRegistry::new(); let store1 = ChatStorage::new(StorageConfig::InMemory).unwrap(); let alice_account = TestLogosAccount::new("alice"); - let ctx1 = Core::new_with_name(alice_account, ds, rs, store1).unwrap(); + let ctx1 = Core::new_with_name(alice_account, ds, rs, NoopWakeupService {}, store1).unwrap(); let pubkey1 = ctx1.identity().public_key(); let name1 = ctx1.installation_name().to_string(); @@ -127,7 +142,7 @@ fn open_persists_new_identity() { let rs = EphemeralRegistry::new(); let store = ChatStorage::new(StorageConfig::File(db_path.clone())).unwrap(); let alice_account = TestLogosAccount::new("alice"); - let core = Core::new_from_store(alice_account, ds, rs, store).unwrap(); + let core = Core::new_from_store(alice_account, ds, rs, NoopWakeupService {}, store).unwrap(); let pubkey = core.identity().public_key(); drop(core); @@ -147,11 +162,19 @@ fn conversation_metadata_persistence() { alice_account, ds.clone(), rs.clone(), + NoopWakeupService {}, ChatStorage::in_memory(), ) .unwrap(); let bob_account = TestLogosAccount::new("bob"); - let mut bob = Core::new_with_name(bob_account, ds, rs, ChatStorage::in_memory()).unwrap(); + let mut bob = Core::new_with_name( + bob_account, + ds, + rs, + NoopWakeupService {}, + ChatStorage::in_memory(), + ) + .unwrap(); let bundle = alice.create_intro_bundle().unwrap(); let intro = Introduction::try_from(bundle.as_slice()).unwrap(); @@ -180,11 +203,19 @@ fn conversation_full_flow() { alice_account, ds.clone(), rs.clone(), + NoopWakeupService {}, ChatStorage::in_memory(), ) .unwrap(); let bob_account = TestLogosAccount::new("bob"); - let mut bob = Core::new_with_name(bob_account, ds, rs, ChatStorage::in_memory()).unwrap(); + let mut bob = Core::new_with_name( + bob_account, + ds, + rs, + NoopWakeupService {}, + ChatStorage::in_memory(), + ) + .unwrap(); let bundle = alice.create_intro_bundle().unwrap(); let intro = Introduction::try_from(bundle.as_slice()).unwrap(); diff --git a/core/integration_tests_core/tests/test_group_v2.rs b/core/integration_tests_core/tests/test_group_v2.rs new file mode 100644 index 0000000..f000423 --- /dev/null +++ b/core/integration_tests_core/tests/test_group_v2.rs @@ -0,0 +1,178 @@ +use integration_tests_core::TestHarness; +use tracing::info; + +#[test] +fn groupv2_2way_roundtrip() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer() + .try_init(); + + const S_M1: &[u8] = b"aaaaa"; + const R_M1: &[u8] = b"Hello"; + + // Initialize TestHarness with 2 clients + let mut harness = TestHarness::<2>::new(|_, _| {}); + + //Saro Create Convo + let particpants = &[&harness.raya().addr()]; + let convo_id = harness + .saro() + .create_group_convo_v2(particpants) + .expect("saro create group"); + + // Carry the invite through (commit, WelcomeReady, routing to Raya's inbox, + // accept_welcome); settle until Raya has joined. + harness.process_until_label("Saro Send", |h| h.raya().convo_count() == 1); + + // Saro sends a message; settle until Raya receives it. + info!(target: "chat", "Saro -> sending: {S_M1:?}"); + harness + .saro() + .send_content(&convo_id, S_M1) + .expect("saro send"); + + harness.process_until(|h| h.raya().check(&convo_id, S_M1)); + + // Raya replies; settle until Saro receives it. + info!(target: "chat", "Raya -> sending:{R_M1:?}"); + harness.raya().send_content(&convo_id, R_M1).unwrap(); + harness.process_until(|h| h.saro().check(&convo_id, R_M1)); +} + +#[test] +fn core_client() { + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer() + .try_init(); + + const S_M1: &[u8] = b"HI"; + const R_M1: &[u8] = b"hi back"; + const S_M2: &[u8] = b"EPOCHCHK"; + + let mut harness = TestHarness::<3>::new(|_, _| {}); + + let particpants = &[&harness.raya().addr()]; + let convo_id = harness + .saro() + .create_group_convo_v2(particpants) + .expect("Saro create"); + + // Carry the invite through (commit, WelcomeReady, routing to Raya's inbox, + // accept_welcome); settle until Raya has joined. + harness.process_until_label("saro create", |h| h.raya().convo_count() == 1); + + // Saro sends a message; settle until Raya receives it. + info!(target: "chat", "Saro -> sending: {S_M1:?}"); + harness + .saro() + .send_content(&convo_id, S_M1) + .expect("saro send"); + + harness.process_until_label("Recv S_M1", |h| h.raya().check(&convo_id, S_M1)); + + // Raya replies; settle until Saro receives it. + info!(target: "chat", "Raya -> sending: {R_M1:?}"); + harness + .raya() + .send_content(&convo_id, R_M1) + .expect("raya send"); + + harness.process_until_label("Recv R_M1", |h| h.saro().check(&convo_id, R_M1)); + + // Raya (a non-creator) invites Pax; settle until Pax has joined. + let particpants = &[&harness.pax().addr()]; + harness + .raya() + .group_add_member(&convo_id, particpants) + .expect("Raya add Pax"); + + harness.process_until_label("Raya add Pax", |h| h.pax().convo_count() == 1); + + // Everyone must be at the SAME epoch after Pax joined: a marker Saro sends + // now decrypts only for members that applied the Add commit. + info!(target: "chat", "Saro -> sending: EPOCHCHK"); + harness.saro().send_content(&convo_id, S_M2).unwrap(); + + harness.process_until_label("epoch check", |h| { + h.raya().check(&convo_id, S_M2) && h.pax().check(&convo_id, S_M2) + }); +} + +#[test] +fn core_client_batch_add() { + // Saro creates the group and adds BOTH Raya and Pax at the same time: one + // Add commit producing a single welcome that names both joiners. + + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer(); + + let mut harness = TestHarness::<3>::new(|_, _| {}); + + let particpants = &[&harness.raya().addr(), &harness.pax().addr()]; + harness + .saro() + .create_group_convo_v2(particpants) + .expect("Saro create"); + + // Carry the invite through (commit, WelcomeReady, routing to Raya's inbox, + // accept_welcome); settle until Raya has joined. + harness.process_until_label("saro create", |h| { + h.raya().convo_count() == 1 && h.pax().convo_count() == 1 + }); +} + +#[test] +fn core_client_four_members_two_epochs() { + // Epoch 1: Saro creates and batch-adds Raya + Pax (3 members). Epoch 2: Raya + // (a non-creator) adds a 4th member, Mira. Afterwards every member must be + // at the same epoch (each can decrypt a freshly-sent message) and settled + // back in Working (the >sn_max election that the 4th member triggers must + // have completed — no one stuck in Freezing/Selection/Reelection). + + let _ = tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_test_writer() + .try_init(); + + const MSG: &[u8] = b"CONVERGED"; + + let mut harness = TestHarness::<4>::new(|_, _| {}); + + let particpants = &[&harness.raya().addr(), &harness.pax().addr()]; + let convo_id = harness + .saro() + .create_group_convo_v2(particpants) + .expect("Saro create"); + + // Carry the invite through (commit, WelcomeReady, routing to Raya's inbox, + // accept_welcome); settle until Raya has joined. + harness.process_until_label("Raya + Pax join", |h| { + h.raya().convo_count() == 1 && h.pax().convo_count() == 1 + }); + + // Epoch 2: Raya adds the 4th member; settle until Mira has joined and the + // >sn_max election has returned everyone to Working. + let members = &[&harness.mira().addr()]; + harness + .raya() + .group_add_member(&convo_id, members) + .expect("Add Mira"); + + // TODO: Add State == Working for all clients + harness.process_until_label("Mira join", |h| h.mira().convo_count() == 1); + + // Same epoch: a message Saro sends now must reach all three peers. + harness + .saro() + .send_content(&convo_id, MSG) + .expect("Saro send"); + + harness.process_until_label("all chats converge", |h| { + h.raya().check(&convo_id, MSG) + && h.pax().check(&convo_id, MSG) + && h.mira().check(&convo_id, MSG) + }); +} diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index c5c8a4d..17b5439 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use std::thread::{self, JoinHandle}; -use components::EphemeralRegistry; +use components::{EphemeralRegistry, ThreadedWakeupService, WakeupEvent}; use crossbeam_channel::{Receiver, Sender, select}; use libchat::{ ChatError, ChatStorage, ConversationId, ConvoOutcome, Core, DeliveryService, InboxOutcome, @@ -13,7 +13,7 @@ use parking_lot::Mutex; use crate::errors::ClientError; use crate::event::Event; -type ClientCore = Core<(TestLogosAccount, T, R, ChatStorage)>; +type ClientCore = Core<(TestLogosAccount, T, R, ThreadedWakeupService, ChatStorage)>; /// The transport as the client sees it: a [`DeliveryService`] for outbound /// publishing plus the inbound payload stream the worker drains. One object owns @@ -52,14 +52,17 @@ impl ChatClient { pub fn new(name: impl Into, mut transport: T) -> (Self, Receiver) { let inbound = transport.inbound(); let ident = TestLogosAccount::new(name); + let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded(); + let wakeup_service = ThreadedWakeupService::new(wakeup_tx); let core = Core::new_with_name( ident, transport, EphemeralRegistry::new(), + wakeup_service, ChatStorage::in_memory(), ) .unwrap(); - Self::spawn(core, inbound) + Self::spawn(core, inbound, wakeup_rx) } /// Open or create a persistent client backed by `StorageConfig`. @@ -74,8 +77,16 @@ impl ChatClient { let store = ChatStorage::new(config).map_err(ChatError::from)?; let inbound = transport.inbound(); let ident = TestLogosAccount::new(name); - let core = Core::new_from_store(ident, transport, EphemeralRegistry::new(), store)?; - Ok(Self::spawn(core, inbound)) + let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded(); + let wakeup_service = ThreadedWakeupService::new(wakeup_tx); + let core = Core::new_from_store( + ident, + transport, + EphemeralRegistry::new(), + wakeup_service, + store, + )?; + Ok(Self::spawn(core, inbound, wakeup_rx)) } } @@ -106,19 +117,25 @@ where let store = ChatStorage::new(config).map_err(ChatError::from)?; let inbound = transport.inbound(); let ident = TestLogosAccount::new(name); - let mut core = Core::new_from_store(ident, transport, registry, store)?; + let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded(); + let wakeup_service = ThreadedWakeupService::new(wakeup_tx); + let mut core = Core::new_from_store(ident, transport, registry, wakeup_service, store)?; core.register_keypackage()?; - Ok(Self::spawn(core, inbound)) + Ok(Self::spawn(core, inbound, wakeup_rx)) } - fn spawn(core: ClientCore, inbound: Receiver>) -> (Self, Receiver) { + fn spawn( + core: ClientCore, + inbound: Receiver>, + wakeup_events: Receiver, + ) -> (Self, Receiver) { let core = Arc::new(Mutex::new(core)); let (event_tx, event_rx) = crossbeam_channel::unbounded(); let (shutdown_tx, shutdown_rx) = crossbeam_channel::bounded::<()>(0); let worker = thread::spawn({ let core = Arc::clone(&core); - move || worker_loop(core, inbound, shutdown_rx, event_tx) + move || worker_loop(core, inbound, wakeup_events, shutdown_rx, event_tx) }); ( @@ -187,6 +204,7 @@ impl Drop for ChatClient { fn worker_loop( core: Arc>>, inbound: Receiver>, + wakeup_events: Receiver, shutdown: Receiver<()>, event_tx: Sender, ) where @@ -217,6 +235,14 @@ fn worker_loop( } } } + recv(wakeup_events) -> msg => { + let Ok(WakeupEvent { convo_id }) = msg else { + return; // wakeup service's sender dropped + }; + if let Err(e) = core.lock().wakeup(&convo_id) { + tracing::warn!("wakeup failed: {e:?}"); + } + } recv(shutdown) -> _ => return, } } diff --git a/extensions/components/Cargo.toml b/extensions/components/Cargo.toml index fb65de6..582c0bc 100644 --- a/extensions/components/Cargo.toml +++ b/extensions/components/Cargo.toml @@ -11,6 +11,7 @@ storage = { workspace = true } # External dependencies (sorted) base64 = "0.22" +crossbeam-channel = { workspace = true } hex = "0.4.3" reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } diff --git a/extensions/components/src/delivery/local_broadcaster.rs b/extensions/components/src/delivery/local_broadcaster.rs index 5889def..f47807c 100644 --- a/extensions/components/src/delivery/local_broadcaster.rs +++ b/extensions/components/src/delivery/local_broadcaster.rs @@ -6,6 +6,7 @@ use std::{ }; use libchat::{AddressedEnvelope, DeliveryService}; +use tracing::info; #[derive(Debug)] struct BroadcasterShared { @@ -107,6 +108,7 @@ impl DeliveryService for LocalBroadcaster { type Error = String; fn publish(&mut self, envelope: AddressedEnvelope) -> Result<(), Self::Error> { + info!(?envelope.delivery_address, len=envelope.data.len(), "DS:Publish"); self.outbound_msgs.push(Self::msg_id(&envelope)); self.shared.borrow_mut().messages.push_back(envelope); @@ -114,6 +116,7 @@ impl DeliveryService for LocalBroadcaster { } fn subscribe(&mut self, delivery_address: &str) -> Result<(), Self::Error> { + info!(delivery_address, "DS:Subscribe"); // Strict temporal ordering of subscriptions is not enforced. // Subscriptions are evaluated on polling, not when the message is published self.subscriptions.insert(delivery_address.to_string()); diff --git a/extensions/components/src/lib.rs b/extensions/components/src/lib.rs index a147b70..25ae825 100644 --- a/extensions/components/src/lib.rs +++ b/extensions/components/src/lib.rs @@ -1,8 +1,10 @@ mod contact_registry; mod delivery; mod storage; +mod wakeup; pub use contact_registry::EphemeralRegistry; pub use contact_registry::http::{HttpRegistry, HttpRegistryError}; pub use delivery::*; pub use storage::*; +pub use wakeup::*; diff --git a/extensions/components/src/wakeup.rs b/extensions/components/src/wakeup.rs new file mode 100644 index 0000000..58905d1 --- /dev/null +++ b/extensions/components/src/wakeup.rs @@ -0,0 +1,133 @@ +use std::cmp::Reverse; +use std::collections::BinaryHeap; +use std::fmt; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant}; + +use crossbeam_channel::Sender; +use libchat::{ConversationId, WakeupService}; + +#[derive(Debug, Eq, PartialEq)] +struct WakeupRecord { + expiry: Instant, + convo_id: ConversationId, +} + +impl Ord for WakeupRecord { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.expiry.cmp(&other.expiry) + } +} + +impl PartialOrd for WakeupRecord { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Sent to the wakeup queue when a previously registered timer expires. +#[derive(Debug, Clone)] +pub struct WakeupEvent { + pub convo_id: ConversationId, +} + +struct Shared { + pending: Mutex>>, + condvar: Condvar, + running: AtomicBool, +} + +/// A [`WakeupService`] backed by a background thread that sleeps until the +/// nearest pending deadline, then emits a [`WakeupEvent`] on `events`. +pub struct ThreadedWakeupService { + shared: Arc, + thread: Option>, +} + +impl fmt::Debug for ThreadedWakeupService { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ThreadedWakeupService").finish() + } +} + +impl ThreadedWakeupService { + pub fn new(events: Sender) -> Self { + let shared = Arc::new(Shared { + pending: Mutex::new(BinaryHeap::new()), + condvar: Condvar::new(), + running: AtomicBool::new(true), + }); + + let thread = thread::spawn({ + let shared = Arc::clone(&shared); + move || run(shared, events) + }); + + Self { + shared, + thread: Some(thread), + } + } +} + +impl WakeupService for ThreadedWakeupService { + fn wakeup_in(&mut self, duration: Duration, convo_id: ConversationId) { + let mut pending = self.shared.pending.lock().unwrap(); + pending.push(Reverse(WakeupRecord { + expiry: Instant::now() + duration, + convo_id, + })); + // The worker may be sleeping until a later deadline; wake it so it + // can recompute the time until the new nearest deadline. + self.shared.condvar.notify_one(); + } +} + +impl Drop for ThreadedWakeupService { + fn drop(&mut self) { + self.shared.running.store(false, Ordering::SeqCst); + self.shared.condvar.notify_one(); + if let Some(thread) = self.thread.take() { + let _ = thread.join(); + } + } +} + +/// Background loop: sleep until the nearest deadline (or forever if the heap +/// is empty), then drain and emit any expired records. +fn run(shared: Arc, events: Sender) { + loop { + let mut pending = shared.pending.lock().unwrap(); + + if !shared.running.load(Ordering::SeqCst) { + return; + } + + let Some(Reverse(next)) = pending.peek() else { + // Nothing scheduled: wait until a registration or shutdown wakes us. + drop(shared.condvar.wait(pending).unwrap()); + continue; + }; + + let now = Instant::now(); + if next.expiry > now { + let timeout = next.expiry - now; + drop(shared.condvar.wait_timeout(pending, timeout).unwrap()); + continue; + } + + let Reverse(record) = pending.pop().unwrap(); + drop(pending); + + if events + .send(WakeupEvent { + convo_id: record.convo_id, + }) + .is_err() + { + return; + } + } +} diff --git a/flake.nix b/flake.nix index 6b7a746..0929535 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,7 @@ pkgs.pkg-config pkgs.cmake pkgs.perl + pkgs.protobuf ]; }; }