diff --git a/CLAUDE.md b/CLAUDE.md index 8885276d..36d3edfa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,7 @@ The workspace is declared in `Cargo.toml`. Crate-by-crate: | `sdk` | Rhai engine + two-phase Loader/Executor that compiles plugin scripts into pod2 modules. | | `txlib` | Transaction state machine: `StateHeader`, `GroundingWitness`, `Tx`, `TxBuilder` + `TxFinalized` rule. Accepts local object dictionaries or statements supplied by another party; does not model party identities. | | `joint-tx` | Multi-party transaction planning: validates participants and custody, derives the shared `TxPlan` and proving schedule, and exchanges proof contributions. Depends on `txlib`. | +| `interfaces/trade` | Two-party swap CLI over iroh and `joint-tx`: loads local objects and plugins, proves in process, posts through the relayer, and imports the received object through dobjd. | | `synchronizer` | Long-running service: ingests Ethereum blobs, maintains Merkle state, serves HTTP queries. | | `relayer` | HTTP service that wraps proofs as EIP-4844 blob txs and submits them. | | `archiver` | Service that follows beacon blocks and archives blobs filtered by destination address to the filesystem (no DB). Serves them via a beacon-compatible HTTP API; the synchronizer reads blobs from it. | diff --git a/Cargo.lock b/Cargo.lock index a8526184..4873742f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,41 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -254,7 +289,7 @@ dependencies = [ "either", "serde", "serde_with", - "sha2", + "sha2 0.10.9", "thiserror 2.0.18", ] @@ -399,7 +434,7 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru", + "lru 0.16.3", "parking_lot", "pin-project", "reqwest 0.12.28", @@ -946,6 +981,15 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "archiver" version = "0.1.0" @@ -1412,6 +1456,18 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64 0.22.1", + "http", + "log", + "url", +] + [[package]] name = "auto_impl" version = "1.3.0" @@ -1532,12 +1588,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.21.7" @@ -1658,6 +1731,19 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + [[package]] name = "block" version = "0.1.6" @@ -1673,6 +1759,15 @@ dependencies = [ "generic-array", ] +[[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 = "block2" version = "0.6.2" @@ -1941,9 +2036,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" @@ -1970,6 +2065,16 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -2045,6 +2150,21 @@ dependencies = [ "wire-types", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -2088,6 +2208,12 @@ 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-random" version = "0.1.18" @@ -2128,6 +2254,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.4.0" @@ -2153,6 +2285,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "cordyceps" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2245,6 +2387,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -2316,6 +2464,16 @@ 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", + "rand_core 0.10.0", +] + [[package]] name = "cssparser" version = "0.29.6" @@ -2372,6 +2530,53 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto", + "rand_core 0.10.0", + "rustc_version 0.4.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "darling" version = "0.20.11" @@ -2493,9 +2698,29 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "data-encoding-macro" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a127ecbb3c4632e1525380e04c0c3fcf8dcb44d32a79ea290d8a36906edcd8" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "c54e03a951783e8b327515db3f2a2fd0e3bed362a96b066f341ce66ed49b4ead" +dependencies = [ + "data-encoding", + "syn 2.0.117", +] [[package]] name = "dbus" @@ -2514,8 +2739,19 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", - "pem-rfc7468", + "const-oid 0.9.6", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "const-oid 0.10.2", + "pem-rfc7468 1.0.0", "zeroize", ] @@ -2618,6 +2854,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + [[package]] name = "digest" version = "0.9.0" @@ -2633,12 +2875,22 @@ 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", + "crypto-common 0.2.2", +] + [[package]] name = "directories" version = "6.0.0" @@ -2878,13 +3130,40 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.10", "digest 0.10.7", "elliptic-curve", "rfc6979", - "serdect", - "signature", - "spki", + "serdect 0.2.0", + "signature 2.2.0", + "spki 0.7.3", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8 0.11.0", + "serdect 0.4.3", + "signature 3.0.0", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.10.0", + "serde", + "sha2 0.11.0", + "signature 3.0.0", + "subtle", + "zeroize", ] [[package]] @@ -2914,16 +3193,16 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", + "base16ct 0.2.0", "crypto-bigint", "digest 0.10.7", "ff", "generic-array", "group", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", "sec1", - "serdect", + "serdect 0.2.0", "subtle", "zeroize", ] @@ -2948,6 +3227,18 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -2963,6 +3254,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +[[package]] +name = "enum-assoc" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0590c4a94da3372e83493b956755a6e2266830b6e4e3b101afe66e3f39477b91" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -3164,6 +3466,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "field-offset" version = "0.3.6" @@ -3235,7 +3543,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", - "spin", + "spin 0.9.8", ] [[package]] @@ -3338,6 +3646,19 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin 0.10.1", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -3555,6 +3876,21 @@ dependencies = [ "x11", ] +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3611,11 +3947,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.0", "wasip2", "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", ] [[package]] @@ -3703,6 +4051,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -3856,8 +4216,19 @@ dependencies = [ ] [[package]] -name = "hashlink" -version = "0.10.0" +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ @@ -3897,6 +4268,83 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hickory-net" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "hickory-proto", + "http", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.0", + "rustls", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand 0.10.0", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto", + "ipconfig", + "ipnet", + "jni 0.22.4", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.0", + "resolv-conf", + "rustls", + "smallvec", + "system-configuration", + "thiserror 2.0.18", + "tokio", + "tokio-rustls", + "tracing", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -3991,6 +4439,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -4085,7 +4542,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -4200,6 +4657,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "identity-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" + [[package]] name = "idna" version = "1.1.0" @@ -4221,6 +4684,26 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "igd-next" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581" +dependencies = [ + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.10.0", + "tokio", + "url", + "xmltree", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -4273,6 +4756,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -4297,11 +4789,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + [[package]] name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] [[package]] name = "iri-string" @@ -4313,6 +4821,172 @@ dependencies = [ "serde", ] +[[package]] +name = "iroh" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460de6bc52163b41b1646931f2897e5ab986f0966ade444467fec25024751a72" +dependencies = [ + "backon", + "blake3", + "bytes", + "cfg_aliases", + "ctutils", + "data-encoding", + "derive_more 2.1.1", + "ed25519-dalek", + "futures-util", + "getrandom 0.4.2", + "hickory-resolver", + "http", + "ipnet", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "iroh-relay", + "n0-error", + "n0-future", + "n0-watcher", + "netwatch", + "noq", + "noq-proto", + "noq-udp", + "papaya", + "pin-project", + "portable-atomic", + "portmapper", + "rand 0.10.0", + "reqwest 0.13.2", + "rustc-hash", + "rustls", + "rustls-pki-types", + "serde", + "smallvec", + "strum 0.28.0", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "iroh-base" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6be73e16ee21c923aca9b3121aaa0db936f7c7ecc156ff47b8dac944c68d59a8" +dependencies = [ + "curve25519-dalek", + "data-encoding", + "data-encoding-macro", + "derive_more 2.1.1", + "ed25519-dalek", + "getrandom 0.4.2", + "n0-error", + "rand 0.10.0", + "serde", + "url", + "zeroize", +] + +[[package]] +name = "iroh-dns" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f6a9b39d18e6345f5c151afd299f2488e2cb5c520fe41b107b6bd3dc4c3349" +dependencies = [ + "arc-swap", + "cfg_aliases", + "derive_more 2.1.1", + "hickory-resolver", + "iroh-base", + "n0-error", + "n0-future", + "ndk-context", + "portable-atomic", + "rand 0.10.0", + "rustls", + "simple-dns", + "strum 0.28.0", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "iroh-metrics" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291065721ad7c477b972e581bbc528df031dc8eb5e39fe1ff3300ae5dfb157ef" +dependencies = [ + "iroh-metrics-derive", + "itoa", + "n0-error", + "portable-atomic", + "ryu", + "serde", + "tracing", +] + +[[package]] +name = "iroh-metrics-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "iroh-relay" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24bd586cf927f7b700f56ec3639b53cb5fa901ce284784051ff71092bfbf8193" +dependencies = [ + "blake3", + "bytes", + "cfg_aliases", + "data-encoding", + "derive_more 2.1.1", + "getrandom 0.4.2", + "hickory-resolver", + "http", + "http-body-util", + "hyper", + "hyper-util", + "iroh-base", + "iroh-dns", + "iroh-metrics", + "lru 0.18.2", + "n0-error", + "n0-future", + "noq", + "noq-proto", + "num_enum", + "pin-project", + "postcard", + "rand 0.10.0", + "reqwest 0.13.2", + "rustls", + "rustls-pki-types", + "serde", + "serde_bytes", + "strum 0.28.0", + "tokio", + "tokio-rustls", + "tokio-util", + "tokio-websockets", + "tracing", + "url", + "webpki-roots 1.0.6", + "ws_stream_wasm", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -4436,19 +5110,68 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.0", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] +[[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 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[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.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[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" @@ -4513,8 +5236,8 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "serdect", - "sha2", + "serdect 0.2.0", + "sha2 0.10.9", ] [[package]] @@ -4575,7 +5298,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.8", ] [[package]] @@ -4727,6 +5450,19 @@ dependencies = [ "value-bag", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.16.3" @@ -4736,6 +5472,15 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" +dependencies = [ + "hashbrown 0.17.1", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -4774,6 +5519,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-addr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" + [[package]] name = "macro-string" version = "0.1.4" @@ -4939,6 +5690,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "muda" version = "0.19.2" @@ -4960,6 +5728,59 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "n0-error" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c37e81176a83a77d2514528b91bdafc70ef88aab428f0e1b91aebb8d99888895" +dependencies = [ + "n0-error-macros", + "spez", +] + +[[package]] +name = "n0-error-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2acd8b070213b0299282f884b4beba4e7b52d624fdcd504a3ad3665390c11e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "n0-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" +dependencies = [ + "cfg_aliases", + "derive_more 2.1.1", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "n0-watcher" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826" +dependencies = [ + "derive_more 2.1.1", + "n0-error", + "n0-future", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -4978,27 +5799,146 @@ dependencies = [ ] [[package]] -name = "ndk" -version = "0.9.0" +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.0", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.0", +] + +[[package]] +name = "netdev" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "569dfbdd2efd771b24ec9bb57f956e04d4fbfc72f62b2f11961723f9b3f4b020" +dependencies = [ + "block2", + "dispatch2", + "dlopen2", + "ipnet", + "jni 0.21.1", + "libc", + "mac-addr", + "ndk-context", + "netlink-packet-core", + "netlink-packet-route", + "netlink-sys", + "objc2", + "objc2-core-foundation", + "objc2-core-wlan", + "objc2-foundation", + "objc2-system-configuration", + "once_cell", + "plist", + "windows-sys 0.61.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b897d7bd4f0af82e68d40d0344cf37e97f9c97ddf74a098de3e4da05e96ca395" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-route" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007" +dependencies = [ + "bitflags 2.11.0", + "libc", + "log", + "netlink-packet-core", +] + +[[package]] +name = "netlink-proto" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93af8261786086024cd5e96e0a991dd65ced07bbf7c233a487bbc96b971d5539" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" dependencies = [ - "bitflags 2.11.0", - "jni-sys", + "bytes", + "futures-util", + "libc", "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", + "tokio", ] [[package]] -name = "ndk-sys" -version = "0.6.0+11769913" +name = "netwatch" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +checksum = "4d9cbe01741347ef750d743d6690603f5eed8341e679fb51c8e629337aa11976" dependencies = [ - "jni-sys", + "atomic-waker", + "bytes", + "cfg_aliases", + "derive_more 2.1.1", + "ipnet", + "js-sys", + "libc", + "n0-error", + "n0-future", + "n0-watcher", + "netdev", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "noq-udp", + "objc2-core-foundation", + "objc2-system-configuration", + "pin-project-lite", + "serde", + "socket2", + "time", + "tokio", + "tokio-util", + "tracing", + "web-sys", + "windows 0.62.2", + "windows-result 0.4.1", + "wmi", ] [[package]] @@ -5036,6 +5976,68 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noq" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e4bb6601fa543c110d8957813267d5a8d775a0f8fbaccf1f615d06ba9b10da" +dependencies = [ + "bytes", + "cfg_aliases", + "derive_more 2.1.1", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "web-time", +] + +[[package]] +name = "noq-proto" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa7b5ccd819a9c68a0d955e67a881032d09b1a17219b1f90b0997a0888e1a15" +dependencies = [ + "aes-gcm", + "bytes", + "derive_more 2.1.1", + "enum-assoc", + "getrandom 0.4.2", + "identity-hash", + "lru-slab", + "rand 0.10.0", + "rand_pcg 0.10.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "sorted-index-buffer", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "noq-udp" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bba20e097a5a16cd0ad14ec882fae1e80a092a124e9422fc4dddd92e96a647" +dependencies = [ + "cfg_aliases", + "libc", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -5278,7 +6280,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.11.0", + "block2", "dispatch2", + "libc", "objc2", ] @@ -5327,6 +6331,20 @@ dependencies = [ "objc2-core-graphics", ] +[[package]] +name = "objc2-core-wlan" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-security", + "objc2-security-foundation", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -5350,6 +6368,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.11.0", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -5387,6 +6406,41 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-security-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "libc", + "objc2", + "objc2-core-foundation", + "objc2-security", +] + [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -5447,6 +6501,7 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ + "critical-section", "portable-atomic", ] @@ -5456,6 +6511,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open" version = "5.3.3" @@ -5553,6 +6614,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "papaya" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" +dependencies = [ + "equivalent", + "seize", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -5657,6 +6728,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -5703,7 +6783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -5974,9 +7054,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] @@ -5985,8 +7065,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.1", + "spki 0.8.0", ] [[package]] @@ -6138,7 +7228,7 @@ dependencies = [ "serde_arrays", "serde_bytes", "serde_json", - "sha2", + "sha2 0.10.9", "strum 0.26.3", "strum_macros 0.26.4", "thiserror 2.0.18", @@ -6180,11 +7270,26 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +dependencies = [ + "serde", +] [[package]] name = "portable-atomic-util" @@ -6195,6 +7300,59 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "portmapper" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721" +dependencies = [ + "base64 0.22.1", + "bytes", + "derive_more 2.1.1", + "hyper-util", + "igd-next", + "iroh-metrics", + "libc", + "n0-error", + "n0-future", + "netwatch", + "num_enum", + "rand 0.10.0", + "serde", + "smallvec", + "socket2", + "time", + "tokio", + "tokio-util", + "tower-layer", + "tracing", + "url", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "postcard-derive", + "serde", +] + +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -6225,6 +7383,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -6473,7 +7642,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg", + "rand_pcg 0.2.1", ] [[package]] @@ -6592,6 +7761,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.0", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -6808,15 +7986,20 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -6844,6 +8027,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + [[package]] name = "rfc6979" version = "0.4.0" @@ -6989,16 +8178,16 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", + "const-oid 0.9.6", "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] @@ -7086,23 +8275,63 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.21.1", + "log", "once_cell", - "ring", - "rustls-pki-types", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", "rustls-webpki", - "subtle", - "zeroize", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] [[package]] -name = "rustls-pki-types" -version = "1.14.0" +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" @@ -7222,6 +8451,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -7260,11 +8495,11 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.10", "generic-array", - "pkcs8", - "serdect", + "pkcs8 0.10.2", + "serdect 0.2.0", "subtle", "zeroize", ] @@ -7313,6 +8548,16 @@ dependencies = [ "libc", ] +[[package]] +name = "seize" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "selectors" version = "0.24.0" @@ -7571,7 +8816,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" dependencies = [ - "base16ct", + "base16ct 0.2.0", + "serde", +] + +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct 1.0.0", "serde", ] @@ -7627,6 +8882,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -7638,6 +8899,17 @@ dependencies = [ "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.10.8" @@ -7693,12 +8965,46 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.0", +] + [[package]] name = "simd-adler32" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +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 = "simple-dns" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -7769,6 +9075,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + [[package]] name = "soup3" version = "0.5.0" @@ -7795,6 +9107,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spez" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "spin" version = "0.9.8" @@ -7804,6 +9127,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + [[package]] name = "spki" version = "0.7.3" @@ -7811,7 +9140,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.1", ] [[package]] @@ -7853,7 +9192,7 @@ dependencies = [ "rustls", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror 2.0.18", "tokio", @@ -7891,7 +9230,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -7934,7 +9273,7 @@ dependencies = [ "rsa", "serde", "sha1", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -7971,7 +9310,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -8110,6 +9449,15 @@ dependencies = [ "strum_macros 0.27.2", ] +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", +] + [[package]] name = "strum_macros" version = "0.26.4" @@ -8135,6 +9483,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -8174,6 +9534,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn-solidity" version = "1.5.7" @@ -8251,7 +9622,7 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.61.3", ] [[package]] @@ -8288,6 +9659,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tao" version = "0.35.3" @@ -8306,7 +9683,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -8322,8 +9699,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -8379,7 +9756,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -8410,7 +9787,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -8451,7 +9828,7 @@ dependencies = [ "semver 1.0.27", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.117", "tauri-utils", "thiserror 2.0.18", @@ -8510,7 +9887,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.18", "url", - "windows", + "windows 0.61.3", "zbus 5.14.0", ] @@ -8524,7 +9901,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -8536,7 +9913,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -8547,7 +9924,7 @@ checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -8561,7 +9938,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -8722,6 +10099,7 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", + "js-sys", "libc", "num-conv", "num_threads", @@ -8876,10 +10254,34 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] +[[package]] +name = "tokio-websockets" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-sink", + "getrandom 0.4.2", + "http", + "httparse", + "rand 0.10.0", + "ring", + "rustls-pki-types", + "sha1_smol", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + [[package]] name = "toml" version = "0.8.2" @@ -9134,6 +10536,31 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "trade" +version = "0.1.0" +dependencies = [ + "anyhow", + "bs58", + "clap", + "driver", + "env_logger", + "hex", + "iroh", + "joint-tx", + "payload", + "pexe", + "pod2", + "pod2utils", + "reqwest 0.12.28", + "sdk", + "serde", + "serde_json", + "tokio", + "txlib", + "wire-types", +] + [[package]] name = "tray-icon" version = "0.23.1" @@ -9329,6 +10756,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "unroll" version = "0.1.5" @@ -9793,6 +11230,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -9819,8 +11265,8 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", "windows-implement", "windows-interface", ] @@ -9843,8 +11289,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.18", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] @@ -9915,11 +11361,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -9928,7 +11386,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", ] [[package]] @@ -9944,15 +11411,39 @@ dependencies = [ "windows-strings 0.4.2", ] +[[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 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-future" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -9995,10 +11486,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-registry" version = "0.6.1" @@ -10172,6 +11673,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -10493,6 +12003,21 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wmi" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 2.0.18", + "windows 0.61.3", + "windows-core 0.61.2", +] + [[package]] name = "writeable" version = "0.6.2" @@ -10517,7 +12042,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -10529,7 +12054,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.18", @@ -10537,8 +12062,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -10612,6 +12137,21 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + [[package]] name = "yoke" version = "0.8.1" @@ -10801,18 +12341,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 88a08832..181c7130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "interfaces/cli", "interfaces/gui/src-tauri", "interfaces/mcp", + "interfaces/trade", "libs/driver", "libs/sdk", "libs/txlib", diff --git a/interfaces/trade/Cargo.toml b/interfaces/trade/Cargo.toml new file mode 100644 index 00000000..a112aae6 --- /dev/null +++ b/interfaces/trade/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "trade" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "dobj-trade" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +pod2 = { workspace = true } +pod2utils = { path = "../../libs/pod2utils" } +txlib = { path = "../../libs/txlib" } +joint-tx = { path = "../../libs/joint-tx" } +sdk = { path = "../../libs/sdk" } +payload = { path = "../../libs/payload", features = ["test-utils"] } +driver = { path = "../../libs/driver" } +pexe = { path = "../../libs/pexe" } +wire-types = { workspace = true } +hex = { workspace = true } +iroh = "1.0" +tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-util", "time"] } +clap = { version = "4", features = ["derive", "env"] } +bs58 = "0.5" +env_logger = { workspace = true } +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } diff --git a/interfaces/trade/src/engine.rs b/interfaces/trade/src/engine.rs new file mode 100644 index 00000000..ba3e1ab2 --- /dev/null +++ b/interfaces/trade/src/engine.rs @@ -0,0 +1,614 @@ +//! Transport-independent initiator and accepter state machines. +//! +//! Each transition consumes its stage and returns the next, preventing +//! message replay and out-of-order protocol steps. + +use std::sync::Arc; + +use anyhow::{Context as _, Result, anyhow, ensure}; +use joint_tx::{ + InputState, JointEvent, JointTransaction, TransferAcceptance, TransferOffer, TxPlan, +}; +use pod2::{ + backends::plonky2::{basetypes::DEFAULT_VD_SET, mainpod::Prover, mock::mainpod::MockProver}, + frontend::{MainPod, MultiPodBuilder}, + lang::Module, + middleware::{Hash, MainPodProver, Params, Statement, VDSet, Value, containers::Dictionary}, +}; +use pod2utils::{macros::BuildContext, map, rand_raw_value}; +use txlib::{GroundingWitness, StateHeader, Tx, TxBuilder, compute_nullifier, obj_with_key}; + +use crate::protocol::{ + ACCEPTER, AcceptMsg, AcceptanceMsg, INITIATOR, LegDisclosure, OfferMsg, PlanAckMsg, PlanDataMsg, +}; + +/// Return the new-state commitment for transfer action `index`. +fn transfer_new(transaction: &JointTransaction, index: usize) -> Result { + if let Some(JointEvent::Action(leg)) = transaction.events().get(index) + && let [JointEvent::Transfer { new, .. }] = leg.as_slice() + { + return Ok(*new); + } + Err(anyhow!( + "counterparty deal does not have the swap's two-leg shape" + )) +} + +/// Class-guard predicate name and Rekey branch index. +#[derive(Clone, Debug)] +pub struct ClassGuardInfo { + pub guard_name: String, + pub rekey_branch: usize, +} + +/// Guard metadata keyed by the class hash stored in object `type`. +#[derive(Clone, Debug, Default)] +pub struct ClassDirectory { + classes: std::collections::HashMap, +} + +impl ClassDirectory { + pub fn from_sdk_module(module: &sdk::SdkModule) -> Self { + let mut classes = std::collections::HashMap::new(); + for class in module.classes() { + let hash = module + .class_hash(&class.name) + .expect("loaded module resolves its own classes"); + classes.insert( + hash, + ClassGuardInfo { + guard_name: format!("Is{}", class.name), + rekey_branch: class.actions.len(), + }, + ); + } + Self { classes } + } + + /// Merge another directory in; later entries win on collision. + pub fn absorb(&mut self, other: ClassDirectory) { + self.classes.extend(other.classes); + } + + pub fn get(&self, class_hash: Hash) -> Result<&ClassGuardInfo> { + self.classes + .get(&class_hash) + .ok_or_else(|| anyhow!("no installed class with guard hash {class_hash:#}")) + } +} + +/// Shared proving dependencies. +#[derive(Clone)] +pub struct SwapDeps { + pub modules: Vec>, + pub classes: ClassDirectory, + pub mock: bool, +} + +impl SwapDeps { + fn vd_set(&self) -> VDSet { + if self.mock { + VDSet::new(&[]) + } else { + DEFAULT_VD_SET.clone() + } + } + + fn build_ctx(&self) -> BuildContext { + BuildContext { + builder: MultiPodBuilder::new(&Params::default(), &self.vd_set()), + modules: self.modules.clone(), + } + } + + fn prove_session(&self, builder: MultiPodBuilder) -> Result { + let solution = builder + .solve() + .map_err(|err| anyhow!("proving session does not solve: {err}"))?; + let prover: Box = if self.mock { + Box::new(MockProver {}) + } else { + Box::new(Prover {}) + }; + let pod = solution + .prove(prover.as_ref()) + .map_err(|err| anyhow!("proving session failed: {err}"))? + .output_pod() + .clone(); + pod.pod.verify().context("own pod fails verification")?; + Ok(pod) + } + + /// Reject pods produced in a different proving mode. + fn check_pod_mode(&self, pod: &MainPod, whose: &str) -> Result<()> { + let pod_is_mock = pod.pod.is_mock(); + ensure!( + pod_is_mock == self.mock, + "{whose} pod is {} but this side runs {}; both sides must use the same mode", + if pod_is_mock { "mock" } else { "real" }, + if self.mock { "--mock" } else { "real proving" }, + ); + Ok(()) + } + + /// Prove `Is{class}` with `st_rekey` in the guard's Rekey branch. + fn prove_guard( + &self, + ctx: &mut BuildContext, + class_hash: Hash, + header: &StateHeader, + st_rekey: Statement, + ) -> Result { + let info = self.classes.get(class_hash)?; + let mut premises = vec![Statement::None; info.rekey_branch + 1]; + premises[info.rekey_branch] = st_rekey; + ctx.apply_custom_pred( + false, + &info.guard_name, + map!({"state_header" => header.array()}), + premises, + ) + .map_err(|err| anyhow!("guard {} does not apply: {err}", info.guard_name)) + } +} + +/// Deal independently derived by both engines. +/// Event 0 transfers to the initiator; event 1 transfers to the accepter. +struct AgreedPlan { + transaction: JointTransaction, + context: Hash, + accepter_object: LegPlan, + initiator_object: LegPlan, +} + +struct LegPlan { + old: Hash, + new: Hash, + nullifier: Hash, +} + +impl AgreedPlan { + fn derive( + accepter_object: LegPlan, + initiator_object: LegPlan, + header: &StateHeader, + ) -> Result { + let transaction = JointTransaction::new( + vec![INITIATOR.to_string(), ACCEPTER.to_string()], + INITIATOR.to_string(), + vec![ + InputState { + commitment: accepter_object.old, + holder: ACCEPTER.to_string(), + }, + InputState { + commitment: initiator_object.old, + holder: INITIATOR.to_string(), + }, + ], + vec![ + JointEvent::transfer_leg( + ACCEPTER, + INITIATOR, + accepter_object.old, + accepter_object.new, + accepter_object.nullifier, + ), + JointEvent::transfer_leg( + INITIATOR, + ACCEPTER, + initiator_object.old, + initiator_object.new, + initiator_object.nullifier, + ), + ], + ) + .map_err(|err| anyhow!("deal does not validate: {err}"))?; + let context = transaction.plan().context(header.hash()); + Ok(Self { + transaction, + context, + accepter_object, + initiator_object, + }) + } + + fn plan(&self) -> &TxPlan { + self.transaction.plan() + } + + fn new_commitments(&self) -> Vec { + vec![self.accepter_object.new, self.initiator_object.new] + } + + fn nullifiers(&self) -> Vec { + vec![ + self.accepter_object.nullifier, + self.initiator_object.nullifier, + ] + } +} + +/// Received state and transaction effect expected by one party. +pub struct SwapExpectation { + pub received: Dictionary, + pub tx_final: Hash, + pub new_commitments: Vec, + pub nullifiers: Vec, +} + +/// Finalized pod, transaction, and expected effect. +pub struct SwapOutcome { + pub pod: MainPod, + pub tx: Tx, + pub expectation: SwapExpectation, +} + +// ---------------------------------------------------------------- // +// Initiator // +// ---------------------------------------------------------------- // + +pub struct Initiator { + deps: SwapDeps, + outgoing: Dictionary, + want_class: Hash, + new_key: Value, +} + +impl Initiator { + /// Start with the outgoing state and desired class hash. + pub fn new(deps: SwapDeps, outgoing: Dictionary, want_class: Hash) -> Self { + Self { + deps, + outgoing, + want_class, + new_key: Value::from(rand_raw_value()), + } + } + + /// Validate the accepter's disclosure and ground both inputs. + pub fn on_accept( + self, + msg: &AcceptMsg, + witness: Arc, + ) -> Result<(InitiatorNegotiated, PlanDataMsg)> { + msg.accepter_object.validate(self.want_class)?; + for commitment in [ + msg.accepter_object.old_commitment, + self.outgoing.commitment(), + ] { + ensure!( + witness.created_proofs.contains_key(&commitment), + "grounding witness has no proof for input {commitment:#}" + ); + } + let incoming_new = obj_with_key(&msg.accepter_object.mid, self.new_key.clone()); + let reply = PlanDataMsg { + initiator_object: LegDisclosure::of(&self.outgoing), + header: witness.state_header.clone(), + accepter_object_new: incoming_new.commitment(), + }; + let next = InitiatorNegotiated { + deps: self.deps, + outgoing: self.outgoing, + want_class: self.want_class, + new_key: self.new_key, + incoming: msg.accepter_object.clone(), + incoming_new, + witness, + }; + Ok((next, reply)) + } +} + +pub struct InitiatorNegotiated { + deps: SwapDeps, + outgoing: Dictionary, + want_class: Hash, + new_key: Value, + incoming: LegDisclosure, + incoming_new: Dictionary, + witness: Arc, +} + +impl InitiatorNegotiated { + /// Projected received state, including its new private key. + /// Persist this state before exporting an endorsement. + pub fn projected_received(&self) -> &Dictionary { + &self.incoming_new + } + + /// Require the accepter's deal to match, then prove the initiator's offer. + pub fn on_plan_ack(self, msg: &PlanAckMsg) -> Result<(InitiatorOffered, OfferMsg)> { + let agreed = AgreedPlan::derive( + LegPlan { + old: self.incoming.old_commitment, + new: self.incoming_new.commitment(), + nullifier: self.incoming.nullifier, + }, + LegPlan { + old: self.outgoing.commitment(), + new: transfer_new(&msg.transaction, 1)?, + nullifier: compute_nullifier(&self.outgoing), + }, + &self.witness.state_header, + )?; + ensure!( + agreed.transaction == msg.transaction, + "deals diverge: this side derives tx_final {:#}, counterparty {:#}", + agreed.plan().tx_final(), + msg.transaction.plan().tx_final() + ); + + let mut session = self.deps.build_ctx(); + let offer = TransferOffer::prove(&mut session, agreed.context, &self.outgoing); + let pod = self.deps.prove_session(session.builder)?; + let reply = OfferMsg { + offer: offer.clone(), + pod, + }; + let next = InitiatorOffered { + deps: self.deps, + outgoing: self.outgoing, + want_class: self.want_class, + new_key: self.new_key, + incoming: self.incoming, + incoming_new: self.incoming_new, + witness: self.witness, + agreed, + }; + Ok((next, reply)) + } +} + +pub struct InitiatorOffered { + deps: SwapDeps, + outgoing: Dictionary, + want_class: Hash, + new_key: Value, + incoming: LegDisclosure, + incoming_new: Dictionary, + witness: Arc, + agreed: AgreedPlan, +} + +impl InitiatorOffered { + /// Validate the accepter's session, assemble both legs, and finalize. + pub fn on_acceptance(self, msg: AcceptanceMsg) -> Result { + self.deps.check_pod_mode(&msg.pod, "the accepter's")?; + msg.pod + .pod + .verify() + .context("accepter's pod fails verification")?; + msg.offer + .validate(&msg.pod, self.agreed.context, self.incoming.old_commitment) + .context("accepter's offer does not validate")?; + msg.acceptance + .validate(&msg.pod, self.agreed.initiator_object.new) + .context("accepter's acceptance does not validate")?; + msg.acceptance + .validate_guard(&msg.pod, &msg.guard) + .context("accepter's class guard does not validate")?; + + let mut session = self.deps.build_ctx(); + session + .builder + .add_pod(msg.pod) + .map_err(|err| anyhow!("cannot import accepter's pod: {err}"))?; + let mut tx = TxBuilder::new_from_commitments( + &mut session, + &[self.incoming.old_commitment, self.outgoing.commitment()], + self.witness.clone(), + ); + ensure!( + self.agreed.plan().chain_start() == tx.chain_start, + "plan and builder disagree on chain start" + ); + + let header = &self.witness.state_header; + + // Apply the incoming leg. + let scope = tx.begin_action(); + let (received, st_rekey, handle) = tx.rekey_apply( + &mut session, + &msg.offer.spend_facts(), + msg.offer.st_key_erasure.clone(), + &self.incoming.mid, + self.new_key.clone(), + ); + let guard = self + .deps + .prove_guard(&mut session, self.want_class, header, st_rekey)?; + tx.set_guard(handle, guard); + tx.end_action(scope); + ensure!( + received.commitment() == self.incoming_new.commitment(), + "received state diverges from the planned projection" + ); + + // Record the outgoing leg against the accepter's class guard. + let scope = tx.begin_action(); + let handle = tx.rekey_record(&mut session, &self.outgoing, &msg.acceptance.state_facts()); + tx.set_guard(handle, msg.guard); + tx.end_action(scope); + + let (st_finalized, tx_out, _stats) = tx.finalize(&mut session); + session + .builder + .reveal(&st_finalized) + .map_err(|err| anyhow!("cannot reveal TxFinalized: {err}"))?; + ensure!( + tx_out.dict().commitment() == self.agreed.plan().tx_final(), + "finalized transaction diverges from the agreed plan" + ); + let pod = self.deps.prove_session(session.builder)?; + + Ok(SwapOutcome { + pod, + tx: tx_out, + expectation: SwapExpectation { + received, + tx_final: self.agreed.plan().tx_final(), + new_commitments: self.agreed.new_commitments(), + nullifiers: self.agreed.nullifiers(), + }, + }) + } +} + +// ---------------------------------------------------------------- // +// Accepter // +// ---------------------------------------------------------------- // + +pub struct Accepter { + deps: SwapDeps, + give: Dictionary, + incoming_class: Hash, + new_key: Value, +} + +impl Accepter { + /// Start with the outgoing state and invitation's offered class. + pub fn new(deps: SwapDeps, give: Dictionary, incoming_class: Hash) -> Self { + Self { + deps, + give, + incoming_class, + new_key: Value::from(rand_raw_value()), + } + } + + /// Disclose the object this party gives. + pub fn accept(self) -> (AccepterDisclosed, AcceptMsg) { + let msg = AcceptMsg { + accepter_object: LegDisclosure::of(&self.give), + }; + ( + AccepterDisclosed { + deps: self.deps, + give: self.give, + incoming_class: self.incoming_class, + new_key: self.new_key, + }, + msg, + ) + } +} + +pub struct AccepterDisclosed { + deps: SwapDeps, + give: Dictionary, + incoming_class: Hash, + new_key: Value, +} + +impl AccepterDisclosed { + /// Complete the plan and return the independently derived deal. + pub fn on_plan_data(self, msg: &PlanDataMsg) -> Result<(AccepterPlanned, PlanAckMsg)> { + msg.initiator_object.validate(self.incoming_class)?; + let incoming_new = obj_with_key(&msg.initiator_object.mid, self.new_key.clone()); + let agreed = AgreedPlan::derive( + LegPlan { + old: self.give.commitment(), + new: msg.accepter_object_new, + nullifier: compute_nullifier(&self.give), + }, + LegPlan { + old: msg.initiator_object.old_commitment, + new: incoming_new.commitment(), + nullifier: msg.initiator_object.nullifier, + }, + &msg.header, + )?; + let reply = PlanAckMsg { + transaction: agreed.transaction.clone(), + }; + let next = AccepterPlanned { + deps: self.deps, + give: self.give, + incoming_class: self.incoming_class, + new_key: self.new_key, + incoming: msg.initiator_object.clone(), + incoming_new, + header: msg.header.clone(), + agreed, + }; + Ok((next, reply)) + } +} + +pub struct AccepterPlanned { + deps: SwapDeps, + give: Dictionary, + incoming_class: Hash, + new_key: Value, + incoming: LegDisclosure, + incoming_new: Dictionary, + header: StateHeader, + agreed: AgreedPlan, +} + +impl AccepterPlanned { + /// Projected received state, including its new private key. + pub fn projected_received(&self) -> &Dictionary { + &self.incoming_new + } + + /// Validate the initiator's offer, then prove this side's offer, + /// acceptance, and class guard in one session. + pub fn on_offer(self, msg: OfferMsg) -> Result<(AcceptanceMsg, SwapExpectation)> { + self.deps.check_pod_mode(&msg.pod, "the initiator's")?; + msg.pod + .pod + .verify() + .context("initiator's pod fails verification")?; + msg.offer + .validate(&msg.pod, self.agreed.context, self.incoming.old_commitment) + .context("initiator's offer does not validate")?; + + let mut session = self.deps.build_ctx(); + session + .builder + .add_pod(msg.pod) + .map_err(|err| anyhow!("cannot import initiator's pod: {err}"))?; + let my_offer = TransferOffer::prove(&mut session, self.agreed.context, &self.give); + let (prev_chain, chain) = self.agreed.plan().event_range(1); + let (acceptance, received) = TransferAcceptance::prove( + &mut session, + &msg.offer, + &self.incoming.mid, + self.new_key.clone(), + prev_chain, + chain, + ); + ensure!( + received.commitment() == self.incoming_new.commitment(), + "received state diverges from the planned projection" + ); + let guard = self.deps.prove_guard( + &mut session, + self.incoming_class, + &self.header, + acceptance.st_rekey.clone(), + )?; + session + .builder + .reveal(&guard) + .map_err(|err| anyhow!("cannot reveal the class guard: {err}"))?; + let pod = self.deps.prove_session(session.builder)?; + + let reply = AcceptanceMsg { + offer: my_offer, + acceptance, + guard, + pod, + }; + let expectation = SwapExpectation { + received, + tx_final: self.agreed.plan().tx_final(), + new_commitments: self.agreed.new_commitments(), + nullifiers: self.agreed.nullifiers(), + }; + Ok((reply, expectation)) + } +} diff --git a/interfaces/trade/src/lib.rs b/interfaces/trade/src/lib.rs new file mode 100644 index 00000000..2290948f --- /dev/null +++ b/interfaces/trade/src/lib.rs @@ -0,0 +1,14 @@ +//! Two-party swap demo over the joint-transaction machinery. +//! +//! One negotiation exchange completes the plan, then two exchanges carry +//! three proving sessions. The initiator assembles, finalizes, and posts. + +pub mod engine; +pub mod local; +pub mod net; +pub mod post; +pub mod protocol; +pub mod ui; + +#[cfg(test)] +mod tests; diff --git a/interfaces/trade/src/local.rs b/interfaces/trade/src/local.rs new file mode 100644 index 00000000..b3e33a41 --- /dev/null +++ b/interfaces/trade/src/local.rs @@ -0,0 +1,312 @@ +//! Local objects, plugins, service settings, and received-state import. +//! +//! Full dictionaries and plugin sources are read from `$DOBJ_HOME`. +//! Received objects are written through dobjd for validation and canonical +//! storage. + +use std::collections::HashMap; +use std::sync::Arc; + +use anyhow::{Context as _, Result, anyhow}; +use driver::{ObjectRecord, parse_object_record_file, paths}; +use pod2::{lang::Module, middleware::Hash}; +use wire_types::{DriverSettings, ObjectStatus, QualifiedName}; + +use crate::engine::ClassDirectory; + +pub struct ClassDisplay { + pub qualified: QualifiedName, + pub emoji: String, +} + +pub struct PluginCatalog { + /// Txlib batches and installed plugin modules for `BuildContext`. + pub modules: Vec>, + pub classes: ClassDirectory, + pub display: HashMap, + by_name: HashMap, +} + +impl PluginCatalog { + pub fn resolve_class(&self, name: &str) -> Result { + self.by_name.get(name).copied().ok_or_else(|| { + let mut known: Vec<_> = self.by_name.keys().cloned().collect(); + known.sort(); + anyhow!( + "no installed class named {name}; installed: {}", + known.join(", ") + ) + }) + } + + pub fn class_label(&self, hash: Hash) -> String { + match self.display.get(&hash) { + Some(display) => format!("{} {}", display.emoji, display.qualified), + None => format!("(unknown class {hash:#})"), + } + } +} + +pub struct Local { + pub paths: driver::DriverPaths, + pub settings: DriverSettings, + pub catalog: PluginCatalog, + pub dobjd_url: String, +} + +impl Local { + pub fn open() -> Result { + let paths = paths::default_paths()?; + let settings = read_settings(&paths)?; + let catalog = load_plugins(&paths)?; + let port = match std::env::var("DOBJD_PORT") { + Ok(raw) => raw + .parse::() + .map_err(|err| anyhow!("invalid DOBJD_PORT env: {err}"))?, + Err(_) => 7717, + }; + Ok(Self { + paths, + settings, + catalog, + dobjd_url: format!("http://127.0.0.1:{port}"), + }) + } + + /// Every live object in the store, full dictionaries included. + pub fn live_objects(&self) -> Result> { + let mut records = Vec::new(); + let entries = match std::fs::read_dir(&self.paths.objects_dir) { + Ok(entries) => entries, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(records), + Err(err) => { + return Err(anyhow!( + "cannot read objects dir {}: {err}", + self.paths.objects_dir.display() + )); + } + }; + for entry in entries { + let path = entry?.path(); + if !paths::is_dobj_file(&path) { + continue; + } + let record = parse_object_record_file(&path) + .with_context(|| format!("unreadable object file {}", path.display()))?; + if record.status == ObjectStatus::Live { + records.push(record); + } + } + Ok(records) + } + + /// Live objects of one class, by guard hash. + pub fn live_objects_of_class(&self, class_hash: Hash) -> Result> { + let class_value = pod2::middleware::Value::from(class_hash); + Ok(self + .live_objects()? + .into_iter() + .filter(|record| { + record + .obj + .get(&pod2::middleware::StrKey::from("type")) + .ok() + .flatten() + .is_some_and(|value| value == class_value) + }) + .collect()) + } + + /// Import a received object through dobjd. + pub fn import_received( + &self, + obj: &pod2::middleware::containers::Dictionary, + class_hash: Hash, + tx_final: Hash, + ) -> Result<()> { + let display = self + .catalog + .display + .get(&class_hash) + .ok_or_else(|| anyhow!("received object's class is not installed"))?; + let record = serde_json::json!({ + "contentHash": obj.commitment(), + "class": display.qualified, + "status": "live", + "obj": obj, + "txFinal": tx_final, + }); + let body = serde_json::json!({ "dobj": record.to_string() }); + let url = format!("{}/objects/import", self.dobjd_url); + let response = reqwest::blocking::Client::new() + .post(&url) + .json(&body) + .send() + .with_context(|| format!("dobjd unreachable at {url}"))?; + let status = response.status(); + if !status.is_success() { + let text = response.text().unwrap_or_default(); + anyhow::bail!("dobjd import failed with {status}: {text}"); + } + Ok(()) + } +} + +/// Build a synthetic grounding witness for a `--mock` dry run. +pub fn mock_grounding_witness(commitments: &[Hash]) -> Arc { + let mut state = payload::test_state::TestState::default(); + state.apply_tx(commitments.to_vec(), Vec::new()); + state.build_grounding_witness( + commitments, + |block_meta, created_root, nullifiers_root, prior_state_history_root, created_proofs| { + Arc::new(txlib::GroundingWitness::new( + txlib::StateHeader::new( + block_meta.number as i64, + block_meta.timestamp as i64, + block_meta.hash, + created_root, + nullifiers_root, + prior_state_history_root, + ), + created_proofs, + )) + }, + ) +} + +/// Create an ungrounded local object for `--mock` development. +pub fn dev_spawn(local: &Local, class_hash: Hash) -> Result { + let display = local + .catalog + .display + .get(&class_hash) + .ok_or_else(|| anyhow!("class is not installed"))?; + let initial = pod2utils::dict!({ + "type" => pod2::middleware::Value::from(class_hash), + "key" => pod2utils::rand_raw_value() + }); + let obj = txlib::with_stable_identifier(&initial); + let record = ObjectRecord { + content_hash: obj.commitment(), + class: display.qualified.clone(), + status: ObjectStatus::Live, + tx_hash: None, + obj, + tx_final: Hash::default(), + }; + std::fs::create_dir_all(&local.paths.objects_dir)?; + let file_name = format!( + "{}_{}.dobj", + record.class.file_prefix(), + format!("{:#}", record.content_hash).to_ascii_lowercase() + ); + let path = local.paths.objects_dir.join(file_name); + std::fs::write(&path, serde_json::to_string_pretty(&record)?)?; + Ok(record) +} + +impl Local { + /// Fetch and reconcile object statuses through dobjd. + pub fn object_statuses(&self) -> Result> { + let url = format!("{}/objects", self.dobjd_url); + let summaries: Vec = reqwest::blocking::Client::new() + .get(&url) + .send() + .with_context(|| format!("dobjd unreachable at {url}"))? + .error_for_status() + .context("dobjd objects fetch failed")? + .json() + .context("unreadable dobjd objects response")?; + let mut statuses = HashMap::new(); + for summary in summaries { + if let Ok(hash) = payload::decode_hash_hex(&summary.content_hash) { + statuses.insert(hash, summary.status); + } + } + Ok(statuses) + } +} + +fn read_settings(paths: &driver::DriverPaths) -> Result { + match std::fs::read_to_string(&paths.settings_path) { + Ok(raw) => serde_json::from_str(&raw).map_err(|err| { + anyhow!( + "unreadable settings file {}: {err}", + paths.settings_path.display() + ) + }), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(DriverSettings { + synchronizer_api_url: "http://127.0.0.1:3000".to_string(), + relayer_api_url: "http://127.0.0.1:3200".to_string(), + mcp_enabled: false, + }), + Err(err) => Err(anyhow!( + "cannot read settings file {}: {err}", + paths.settings_path.display() + )), + } +} + +fn load_plugins(paths: &driver::DriverPaths) -> Result { + let mut modules = vec![ + Arc::new(txlib::predicates::events_module()), + Arc::new(txlib::predicates::rekey_module()), + Arc::new(txlib::predicates::module()), + ]; + let mut classes = ClassDirectory::default(); + let mut display = HashMap::new(); + let mut by_name = HashMap::new(); + + let entries = match std::fs::read_dir(&paths.actions_dir) { + Ok(entries) => entries, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + anyhow::bail!( + "no plugins installed under {} (run `just ensure-plugins`)", + paths.actions_dir.display() + ); + } + Err(err) => { + return Err(anyhow!( + "cannot read actions dir {}: {err}", + paths.actions_dir.display() + )); + } + }; + let sdk = sdk::Sdk::default(); + for entry in entries { + let path = entry?.path(); + if path.extension().and_then(|ext| ext.to_str()) != Some(pexe::PEXE_EXTENSION) { + continue; + } + let bytes = pexe::read_pexe_file(&path) + .with_context(|| format!("unreadable plugin {}", path.display()))?; + let (manifest, script) = pexe::unpack(&bytes) + .with_context(|| format!("cannot unpack plugin {}", path.display()))?; + let module = sdk + .load_module_from_src_manifest(&script, &manifest) + .map_err(|err| anyhow!("plugin {} does not load: {err}", manifest.plugin.name))?; + for class in module.classes() { + let hash = module + .class_hash(&class.name) + .expect("loaded module resolves its own classes"); + let emoji = manifest + .classes + .iter() + .find(|c| c.name == class.name) + .map(|c| c.emoji.clone()) + .unwrap_or_default(); + let qualified = QualifiedName::new(&manifest.plugin.name, &class.name); + by_name.insert(class.name.clone(), hash); + by_name.insert(qualified.to_string(), hash); + display.insert(hash, ClassDisplay { qualified, emoji }); + } + classes.absorb(ClassDirectory::from_sdk_module(&module)); + modules.push(module.module().clone()); + } + Ok(PluginCatalog { + modules, + classes, + display, + by_name, + }) +} diff --git a/interfaces/trade/src/main.rs b/interfaces/trade/src/main.rs new file mode 100644 index 00000000..f4f9d23f --- /dev/null +++ b/interfaces/trade/src/main.rs @@ -0,0 +1,701 @@ +//! `dobj-trade`: two-party swaps over iroh and joint transactions. +//! The offerer shares an invitation; the accepter connects, and the offerer +//! assembles, finalizes, and posts the agreed swap. + +use std::sync::Arc; + +use anyhow::{Result, anyhow}; +use clap::{Parser, Subcommand}; +use driver::{ + HttpSynchronizerClient, ObjectRecord, SYNCHRONIZER_POLL_INTERVAL_MS, + SYNCHRONIZER_POLL_TIMEOUT_SECS, SynchronizerClient, +}; +use joint_tx::{JointTransaction, graph::NodeKind}; +use pod2::middleware::Hash; +use trade::engine::{Accepter, Initiator, SwapDeps, SwapExpectation}; +use trade::protocol::{ACCEPTER, INITIATOR, Invitation, WireMsg}; +use trade::{local::Local, net, post, ui}; + +#[derive(Parser)] +#[command(name = "dobj-trade", about = "swap digital objects with another user")] +struct Cli { + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(Subcommand)] +enum Cmd { + /// Offer a trade: prints an invitation to hand to the counterparty, + /// then waits for them to connect. + Offer { + /// Class you are giving (e.g. Wood or craft-basics::Wood). + #[arg(long)] + give: String, + /// Class you want in return. + #[arg(long)] + want: String, + /// Commitment prefix selecting which of your objects to give. + #[arg(long)] + object: Option, + /// Prove with the mock prover and skip posting (local dry run). + #[arg(long)] + mock: bool, + }, + /// Accept a trade from a pasted invitation. + Accept { + /// The invitation blob; prompted for if omitted. + blob: Option, + /// Commitment prefix selecting which of your objects to give. + #[arg(long)] + object: Option, + /// Prove with the mock prover and skip watching (local dry run). + #[arg(long)] + mock: bool, + /// Answer yes to every prompt (for scripted runs). + #[arg(long)] + yes: bool, + }, + /// List your live objects. + Objects, + /// Fabricate a live object for mock runs (dev scaffolding). + #[command(hide = true)] + DevSpawn { + /// Class to fabricate (e.g. Wood). + class: String, + }, +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); + match Cli::parse().cmd { + Cmd::Offer { + give, + want, + object, + mock, + } => run_offer(give, want, object, mock).await, + Cmd::Accept { + blob, + object, + mock, + yes, + } => run_accept(blob, object, mock, yes).await, + Cmd::Objects => run_objects(), + Cmd::DevSpawn { class } => run_dev_spawn(class), + } +} + +fn blocking(f: impl FnOnce() -> T) -> T { + tokio::task::block_in_place(f) +} + +fn run_dev_spawn(class: String) -> Result<()> { + let local = Local::open()?; + let class_hash = local.catalog.resolve_class(&class)?; + let record = trade::local::dev_spawn(&local, class_hash)?; + println!( + "spawned {} {}", + local.catalog.class_label(class_hash), + ui::short(&record.content_hash) + ); + Ok(()) +} + +fn run_objects() -> Result<()> { + let local = Local::open()?; + let objects = local.live_objects()?; + if objects.is_empty() { + println!("no live objects"); + return Ok(()); + } + for record in objects { + let class_label = local.catalog.class_label(class_hash_of(&record)?); + println!( + " {} {} {}", + ui::short(&record.obj.commitment()), + class_label, + ui::describe_object(&record.obj) + ); + } + Ok(()) +} + +fn class_hash_of(record: &ObjectRecord) -> Result { + let value = record + .obj + .get(&pod2::middleware::StrKey::from("type")) + .ok() + .flatten() + .ok_or_else(|| anyhow!("object {} has no type field", record.content_hash))?; + Ok(Hash(value.raw().0)) +} + +/// Pick which of the user's live objects of `class_hash` to give. +fn pick_object(local: &Local, class_hash: Hash, prefix: Option<&str>) -> Result { + let mut candidates = local.live_objects_of_class(class_hash)?; + if let Some(prefix) = prefix { + let prefix = prefix.trim_start_matches("0x").to_ascii_lowercase(); + candidates.retain(|record| { + format!("{:#}", record.obj.commitment()) + .trim_start_matches("0x") + .to_ascii_lowercase() + .starts_with(&prefix) + }); + } + match candidates.len() { + 0 => anyhow::bail!( + "you hold no live object of class {}", + local.catalog.class_label(class_hash) + ), + 1 => Ok(candidates.remove(0)), + _ => { + println!(" you hold several; pick one with --object :"); + for record in &candidates { + ui::object_line("candidate", &record.obj); + } + anyhow::bail!("ambiguous object choice"); + } + } +} + +/// Render the derived schedule with viewer-relative party and object labels. +fn narrate_schedule(transaction: &JointTransaction, my_object: Hash, my_role: &str) { + let graph = transaction.graph(); + let labels: std::collections::HashMap<&str, String> = graph + .nodes() + .iter() + .map(|node| { + let whose = |object: &Hash| { + if *object == my_object { + "mine" + } else { + "theirs" + } + }; + let label = match node.kind() { + NodeKind::TransferOffer { object } => format!("offer:{}", whose(object)), + NodeKind::TransferAcceptance { object } => format!("accept:{}", whose(object)), + NodeKind::Finalize => "finalize".to_string(), + }; + (node.id(), label) + }) + .collect(); + let relabel = |ids: &[String]| { + ids.iter() + .map(|id| labels[id.as_str()].clone()) + .collect::>() + .join(", ") + }; + + ui::heading("the schedule (this is the whole protocol)"); + for (round, sessions) in graph.schedule().rounds.iter().enumerate() { + for session in sessions { + let side = if session.party == my_role { + "this side" + } else { + "their side" + }; + let mut line = format!( + "round {round}: {side} proves [{}]", + relabel(&session.statements) + ); + if !session.imports.is_empty() { + line.push_str(&format!(" importing [{}]", relabel(&session.imports))); + } + ui::note(&line); + } + } +} + +fn confirm(question: &str, auto_yes: bool) -> bool { + if auto_yes { + println!(" {question} [y/N] y (auto)"); + return true; + } + ui::confirm(question) +} + +async fn run_offer(give: String, want: String, object: Option, mock: bool) -> Result<()> { + let local = blocking(Local::open)?; + let give_class = local.catalog.resolve_class(&give)?; + let want_class = local.catalog.resolve_class(&want)?; + let record = pick_object(&local, give_class, object.as_deref())?; + let give_label = local.catalog.class_label(give_class); + let want_label = local.catalog.class_label(want_class); + + ui::say("you", &format!("offering {give_label} for {want_label}")); + ui::object_line("your object", &record.obj); + + ui::heading("opening the door"); + ui::say("you", "binding an iroh endpoint and coming online..."); + let (endpoint, addr) = net::listen().await?; + let invitation = Invitation { + node: addr, + offers: give_class, + wants: want_class, + mock, + }; + ui::key_moment("hand this invitation to your counterparty (any channel works)"); + ui::note("whoever holds this invitation can claim the trade; hand it only to your"); + ui::note("intended counterparty."); + println!("{}", invitation.encode()); + println!(); + ui::say("you", "waiting for them to paste it and connect..."); + + let (connection, mut channel) = net::accept_peer(&endpoint).await?; + ui::say("them", "connected over iroh"); + + let accept = match channel.recv().await? { + WireMsg::Accept(msg) => msg, + other => anyhow::bail!("expected their disclosure, got {other:?}"), + }; + ui::heading("data round"); + ui::say( + "them", + "disclosing the object they give (fields, commitment, nullifier; never the key):", + ); + ui::object_line("their object", &accept.accepter_object.mid); + ui::note(&format!( + "nullifier {} -- spendable only with their key", + ui::short(&accept.accepter_object.nullifier) + )); + ui::note("disclosing your side is irrevocable: they will recognize this object's"); + ui::note("later spend even if the deal dies here."); + + let commitments = [ + accept.accepter_object.old_commitment, + record.obj.commitment(), + ]; + let witness = if mock { + ui::say( + "you", + "mock run: grounding both inputs in a synthetic local state", + ); + trade::local::mock_grounding_witness(&commitments) + } else { + ui::say( + "you", + "fetching one grounding witness for both inputs from the synchronizer...", + ); + let sync_url = local.settings.synchronizer_api_url.clone(); + Arc::new(blocking(|| { + HttpSynchronizerClient::new().fetch_grounding_witness(&sync_url, &commitments) + })?) + }; + ui::note(&format!( + "grounded against state root {} (block {})", + ui::short(&witness.state_header.hash()), + witness.state_header.block_number + )); + + let deps = SwapDeps { + modules: local.catalog.modules.clone(), + classes: local.catalog.classes.clone(), + mock, + }; + let initiator = Initiator::new(deps, record.obj.clone(), want_class); + let (initiator, plan_data) = blocking(|| initiator.on_accept(&accept, witness.clone()))?; + ui::say( + "you -> them", + "your disclosure plus the grounding header and your projected new state", + ); + channel.send(&WireMsg::PlanData(plan_data)).await?; + + let plan_ack = match channel.recv().await? { + WireMsg::PlanAck(msg) => msg, + other => anyhow::bail!("expected their plan ack, got {other:?}"), + }; + let deal_tx_final = plan_ack.transaction.plan().tx_final(); + ui::say( + "them", + &format!( + "agreed; sent their full deal document (tx_final {})", + ui::short(&deal_tx_final) + ), + ); + ui::note("this side re-derives the deal and proves only if the documents match exactly."); + ui::note("endorsements bind this exact effect: it lands exactly as read, or not at all."); + + narrate_schedule(&plan_ack.transaction, record.obj.commitment(), INITIATOR); + + if !mock { + file_projection( + &local, + initiator.projected_received(), + want_class, + deal_tx_final, + &mut channel, + ) + .await?; + } + + ui::heading("round 0: your offer"); + ui::say( + "you", + "proving the offer of your object (openings, key erasure, spend endorsement)...", + ); + let (initiator, offer) = blocking(|| initiator.on_plan_ack(&plan_ack))?; + ui::say("you -> them", "offer pod"); + channel.send(&WireMsg::Offer(Box::new(offer))).await?; + + ui::say( + "you", + "waiting for their combined offer-plus-acceptance session...", + ); + let acceptance = match channel.recv().await? { + WireMsg::Acceptance(msg) => *msg, + other => anyhow::bail!("expected their acceptance, got {other:?}"), + }; + ui::say( + "them", + "their offer, their acceptance of your object, and its class guard, in one pod", + ); + + ui::heading("round 2: assembly and finalize"); + channel + .send(&WireMsg::Progress { + note: "validating your pod, assembling both legs, finalizing, proving".to_string(), + }) + .await?; + ui::say( + "you", + "validating their artifacts against the pod, then assembling...", + ); + if !mock { + ui::note("real proving now; expect roughly half a minute of silence."); + } + let outcome = blocking(|| initiator.on_acceptance(acceptance))?; + ui::say( + "you", + &format!( + "TxFinalized proven; tx_final {}", + ui::short(&outcome.expectation.tx_final) + ), + ); + + if mock { + channel + .send(&WireMsg::Posted { + tx_hash: None, + block_number: None, + }) + .await?; + ui::key_moment("mock run complete: nothing was posted"); + summarize(&outcome.expectation, &local); + } else { + channel + .send(&WireMsg::Progress { + note: "shrinking the proof into a blob payload and posting via the relayer" + .to_string(), + }) + .await?; + ui::say("you", "shrinking the proof and posting via the relayer..."); + let state_root = witness.state_header.hash(); + let relayer_url = local.settings.relayer_api_url.clone(); + let pod = outcome.pod.clone(); + let confirmation = blocking(move || -> Result<_> { + let bytes = post::build_payload_bytes(state_root, &outcome.tx, pod)?; + post::post_and_confirm(&relayer_url, &bytes) + })?; + ui::say( + "you", + &format!( + "posted: eth tx {}", + confirmation.tx_hash.as_deref().unwrap_or("(pending)") + ), + ); + channel + .send(&WireMsg::Posted { + tx_hash: confirmation.tx_hash.clone(), + block_number: confirmation.block_number, + }) + .await?; + watch_and_reconcile(&local, &outcome.expectation, record.obj.commitment()).await?; + } + + // Let the final message drain before closing the endpoint. + channel.finish(); + let _ = tokio::time::timeout(std::time::Duration::from_secs(30), connection.closed()).await; + endpoint.close().await; + Ok(()) +} + +async fn run_accept( + blob: Option, + object: Option, + mock: bool, + yes: bool, +) -> Result<()> { + let local = blocking(Local::open)?; + let blob = match blob { + Some(blob) => blob, + None => blocking(|| -> Result { + println!("paste the invitation blob:"); + let mut line = String::new(); + std::io::stdin().read_line(&mut line)?; + Ok(line) + })?, + }; + let invitation = Invitation::decode(&blob)?; + if mock && !invitation.mock { + anyhow::bail!( + "this invitation is for a real trade; drop --mock or ask for a mock invitation" + ); + } + let mock = invitation.mock; + let offers_label = local.catalog.class_label(invitation.offers); + let wants_label = local.catalog.class_label(invitation.wants); + ui::banner(); + println!(); + ui::field("their iroh id", &invitation.node.id.to_string()); + ui::field( + "they give", + &format!("{offers_label} (guard {})", ui::short(&invitation.offers)), + ); + ui::field( + "they want", + &format!("{wants_label} (guard {})", ui::short(&invitation.wants)), + ); + if mock { + ui::field("mode", "MOCK DRY RUN: mock proofs, nothing will be posted"); + } + + let record = pick_object(&local, invitation.wants, object.as_deref())?; + ui::object_line("you would give", &record.obj); + ui::note("accepting discloses this object's fields, commitment, and nullifier to them,"); + ui::note("irrevocably even if the deal dies. this is your decision point: everything"); + ui::note("after runs to completion."); + if !blocking(|| confirm("accept the trade?", yes)) { + anyhow::bail!("trade declined"); + } + + ui::say("you", "dialing their endpoint over iroh..."); + let (endpoint, connection, mut channel) = net::connect(invitation.node.clone()).await?; + ui::say("you", "connected"); + + let deps = SwapDeps { + modules: local.catalog.modules.clone(), + classes: local.catalog.classes.clone(), + mock, + }; + let accepter = Accepter::new(deps, record.obj.clone(), invitation.offers); + let (accepter, accept_msg) = accepter.accept(); + ui::heading("data round"); + ui::say( + "you -> them", + "your disclosure (fields, commitment, nullifier; never the key)", + ); + let my_commitment = record.obj.commitment(); + channel.send(&WireMsg::Accept(accept_msg)).await?; + + let plan_data = match channel.recv().await? { + WireMsg::PlanData(msg) => msg, + other => anyhow::bail!("expected their plan data, got {other:?}"), + }; + ui::say( + "them", + "their disclosure, the grounding header, and their projected new state:", + ); + ui::object_line("their object", &plan_data.initiator_object.mid); + ui::note(&format!( + "grounding block {}, state root {}", + plan_data.header.block_number, + ui::short(&plan_data.header.hash()) + )); + let (accepter, plan_ack) = blocking(|| accepter.on_plan_data(&plan_data))?; + let deal_tx_final = plan_ack.transaction.plan().tx_final(); + if !mock { + file_projection( + &local, + accepter.projected_received(), + invitation.offers, + deal_tx_final, + &mut channel, + ) + .await?; + } + ui::say( + "you -> them", + &format!( + "plan agreed; sending the full deal document (tx_final {})", + ui::short(&deal_tx_final) + ), + ); + ui::note( + "your endorsement will bind this exact effect: it lands exactly as read, or not at all.", + ); + narrate_schedule(&plan_ack.transaction, my_commitment, ACCEPTER); + channel.send(&WireMsg::PlanAck(plan_ack)).await?; + + ui::say("you", "waiting for their offer pod (round 0)..."); + let offer = match channel.recv().await? { + WireMsg::Offer(msg) => *msg, + other => anyhow::bail!("expected their offer, got {other:?}"), + }; + ui::say("them", "offer pod for the object they give"); + + ui::heading("round 1: your combined session"); + ui::say( + "you", + "validating their offer, then proving your offer, your acceptance, and the class guard...", + ); + if !mock { + ui::note("real proving now; this side takes a while too."); + } + let (acceptance, expectation) = blocking(|| accepter.on_offer(offer))?; + ui::say("you -> them", "combined offer-plus-acceptance pod"); + channel + .send(&WireMsg::Acceptance(Box::new(acceptance))) + .await?; + + ui::heading("round 2 is theirs: they assemble, finalize, and post"); + loop { + match channel.recv().await? { + WireMsg::Progress { note } => ui::say("them", ¬e), + WireMsg::Posted { + tx_hash, + block_number, + } => { + match (&tx_hash, mock) { + (Some(hash), _) => ui::say( + "them", + &format!( + "posted: eth tx {hash}{}", + block_number + .map(|block| format!(" (block {block})")) + .unwrap_or_default() + ), + ), + (None, true) => ui::say("them", "mock run complete: nothing was posted"), + (None, false) => ui::say("them", "posted (no tx hash reported)"), + } + break; + } + other => anyhow::bail!("unexpected message while watching: {other:?}"), + } + } + + if mock { + summarize(&expectation, &local); + } else { + watch_and_reconcile(&local, &expectation, record.obj.commitment()).await?; + } + + connection.close(0u32.into(), b"done"); + endpoint.close().await; + Ok(()) +} + +/// Persist the projected received state before exporting an endorsement. +/// Abort if its new key cannot be stored durably. +async fn file_projection( + local: &Local, + projection: &pod2::middleware::containers::Dictionary, + received_class: Hash, + tx_final: Hash, + channel: &mut trade::net::Channel, +) -> Result<()> { + ui::say( + "you", + "filing your projected new object (with its key) in dobjd before endorsing...", + ); + let result = blocking(|| local.import_received(projection, received_class, tx_final)); + if let Err(err) = result { + net::abort(channel, "could not durably file the projected object").await; + return Err(err.context( + "dobjd must be reachable to file the projected object before endorsing; aborted safely", + )); + } + ui::note("it shows as an unknown object until the transaction lands; if the deal"); + ui::note("dies instead, delete it from dobjd."); + Ok(()) +} + +/// Wait for the complete effect, then reconcile local object statuses. +async fn watch_and_reconcile( + local: &Local, + expectation: &SwapExpectation, + given: Hash, +) -> Result<()> { + ui::heading("watching the synchronizer"); + ui::say( + "you", + &format!( + "waiting until every new commitment and nullifier of tx_final {} is on-chain...", + ui::short(&expectation.tx_final) + ), + ); + let sync_url = local.settings.synchronizer_api_url.clone(); + let created = expectation.new_commitments.clone(); + let nullifiers = expectation.nullifiers.clone(); + let head = blocking(move || { + HttpSynchronizerClient::new().wait_for_tx( + &sync_url, + &created, + &nullifiers, + SYNCHRONIZER_POLL_TIMEOUT_SECS, + SYNCHRONIZER_POLL_INTERVAL_MS, + ) + })?; + ui::say( + "you", + &format!( + "landed; synchronizer head is now {}", + ui::short(&head.current_state_root) + ), + ); + + ui::say("you", "asking dobjd to reconcile object statuses..."); + match blocking(|| local.object_statuses()) { + Ok(statuses) => { + let received = expectation.received.commitment(); + match statuses.get(&received) { + Some(wire_types::ObjectStatus::Live) => { + ui::note("your new object is live in the object store.") + } + Some(status) => ui::note(&format!( + "your new object is filed but still {}; the next sync should settle it.", + status.as_str() + )), + None => ui::note("your new object is not in dobjd's store; check the daemon."), + } + match statuses.get(&given) { + Some(wire_types::ObjectStatus::Nullified) | None => { + ui::note("the object you gave is spent and moved to the nullified store.") + } + Some(status) => ui::note(&format!( + "the object you gave still shows as {}; the next sync should settle it.", + status.as_str() + )), + } + } + Err(err) => { + ui::note(&format!( + "dobjd is unreachable ({err:#}); your new object was filed before endorsing, so" + )); + ui::note("nothing is lost: statuses settle on the daemon's next sync."); + } + } + summarize(expectation, local); + Ok(()) +} + +fn summarize(expectation: &SwapExpectation, local: &Local) { + let class = class_label_of_dict(local, &expectation.received); + ui::key_moment("trade complete"); + ui::say("you", &format!("you now control {class}")); + ui::object_line("received", &expectation.received); + ui::note(&format!( + "under tx_final {}", + ui::short(&expectation.tx_final) + )); +} + +fn class_label_of_dict(local: &Local, obj: &pod2::middleware::containers::Dictionary) -> String { + obj.get(&pod2::middleware::StrKey::from("type")) + .ok() + .flatten() + .map(|value| local.catalog.class_label(Hash(value.raw().0))) + .unwrap_or_else(|| "(unknown class)".to_string()) +} diff --git a/interfaces/trade/src/net.rs b/interfaces/trade/src/net.rs new file mode 100644 index 00000000..b7660521 --- /dev/null +++ b/interfaces/trade/src/net.rs @@ -0,0 +1,119 @@ +//! JSON-line messages over one bidirectional iroh stream. + +use anyhow::{Context as _, Result, anyhow}; +use iroh::{ + Endpoint, EndpointAddr, + endpoint::{Connection, RecvStream, SendStream, presets}, +}; +use tokio::io::{AsyncBufReadExt, BufReader}; + +use crate::protocol::WireMsg; + +pub const ALPN: &[u8] = b"zk-craft/trade/0"; + +pub struct Channel { + send: SendStream, + recv: BufReader, +} + +impl Channel { + pub async fn send(&mut self, msg: &WireMsg) -> Result<()> { + let mut line = serde_json::to_vec(msg)?; + line.push(b'\n'); + self.send + .write_all(&line) + .await + .map_err(|err| anyhow!("send failed: {err}"))?; + Ok(()) + } + + pub async fn recv(&mut self) -> Result { + let mut line = String::new(); + let n = self + .recv + .read_line(&mut line) + .await + .map_err(|err| anyhow!("receive failed: {err}"))?; + if n == 0 { + anyhow::bail!("counterparty closed the connection"); + } + let msg: WireMsg = serde_json::from_str(&line) + .map_err(|err| anyhow!("unreadable message from counterparty: {err}"))?; + if let WireMsg::Abort { reason } = &msg { + anyhow::bail!("counterparty aborted: {reason}"); + } + Ok(msg) + } +} + +impl Channel { + /// Finish sending after queued messages have been written. + pub fn finish(&mut self) { + let _ = self.send.finish(); + } +} + +/// Bind the initiator's endpoint, come online, and return it with the +/// dialable address for the invitation. +pub async fn listen() -> Result<(Endpoint, EndpointAddr)> { + let endpoint = Endpoint::builder(presets::N0) + .alpns(vec![ALPN.to_vec()]) + .bind() + .await + .map_err(|err| anyhow!("cannot bind iroh endpoint: {err}"))?; + endpoint.online().await; + let addr = endpoint.addr(); + Ok((endpoint, addr)) +} + +/// Wait for the accepter to dial in and open the message stream. +pub async fn accept_peer(endpoint: &Endpoint) -> Result<(Connection, Channel)> { + let incoming = endpoint + .accept() + .await + .ok_or_else(|| anyhow!("endpoint closed while waiting for the counterparty"))?; + let connection = incoming + .await + .map_err(|err| anyhow!("incoming connection failed: {err}"))?; + let (send, recv) = connection + .accept_bi() + .await + .map_err(|err| anyhow!("counterparty opened no stream: {err}"))?; + let channel = Channel { + send, + recv: BufReader::new(recv), + }; + Ok((connection, channel)) +} + +/// Dial the initiator from the invitation and open the message stream. +pub async fn connect(addr: EndpointAddr) -> Result<(Endpoint, Connection, Channel)> { + let endpoint = Endpoint::bind(presets::N0) + .await + .map_err(|err| anyhow!("cannot bind iroh endpoint: {err}"))?; + let connection = endpoint + .connect(addr, ALPN) + .await + .map_err(|err| anyhow!("cannot reach the initiator: {err}"))?; + let (send, mut recv) = connection + .open_bi() + .await + .map_err(|err| anyhow!("cannot open message stream: {err}"))?; + // The protocol sends first, so the peer will observe the stream. + let _ = &mut recv; + let channel = Channel { + send, + recv: BufReader::new(recv), + }; + Ok((endpoint, connection, channel)) +} + +/// Send a best-effort abort notice. +pub async fn abort(channel: &mut Channel, reason: &str) { + let _ = channel + .send(&WireMsg::Abort { + reason: reason.to_string(), + }) + .await + .context("abort notice failed"); +} diff --git a/interfaces/trade/src/post.rs b/interfaces/trade/src/post.rs new file mode 100644 index 00000000..131c4775 --- /dev/null +++ b/interfaces/trade/src/post.rs @@ -0,0 +1,55 @@ +//! Build, submit, and confirm a relayer payload for a joint transaction. + +use anyhow::{Result, anyhow}; +use driver::{ + HttpRelayerClient, RELAYER_CONFIRM_TIMEOUT_SECS, RELAYER_POLL_INTERVAL_MS, + RELAYER_TX_HASH_TIMEOUT_SECS, RelayerClient, RelayerConfirmation, +}; +use payload::{ + payload::{Payload, PayloadProof}, + shrink::{ShrunkMainPodSetup, shrink_compress_pod}, +}; +use pod2::{ + frontend::MainPod, + middleware::{Hash, Params}, +}; +use txlib::Tx; + +pub fn build_payload_bytes(state_root: Hash, tx: &Tx, tx_pod: MainPod) -> Result> { + let params = Params::default(); + let setup = ShrunkMainPodSetup::new(¶ms) + .build() + .map_err(|err| anyhow!("cannot build the shrink circuit: {err}"))?; + let compressed = shrink_compress_pod(&setup, tx_pod) + .map_err(|err| anyhow!("cannot shrink the transaction proof: {err}"))?; + let payload = Payload { + proof: PayloadProof::Plonky2(Box::new(compressed)), + tx_final: tx.dict().commitment(), + state_root, + nullifiers: tx.nullifier_hashes()?, + live: tx.live_commitments()?, + }; + Ok(payload.to_bytes()) +} + +pub fn post_and_confirm( + relayer_api_url: &str, + payload_bytes: &[u8], +) -> Result { + let client = HttpRelayerClient::new(); + let submitted = client.submit_proof(relayer_api_url, payload_bytes, None)?; + let tx_hash = client.wait_for_tx_hash( + relayer_api_url, + &submitted.job_id, + RELAYER_TX_HASH_TIMEOUT_SECS, + RELAYER_POLL_INTERVAL_MS, + )?; + let mut confirmation = client.wait_for_confirmation( + relayer_api_url, + &submitted.job_id, + RELAYER_CONFIRM_TIMEOUT_SECS, + RELAYER_POLL_INTERVAL_MS, + )?; + confirmation.tx_hash.get_or_insert(tx_hash); + Ok(confirmation) +} diff --git a/interfaces/trade/src/protocol.rs b/interfaces/trade/src/protocol.rs new file mode 100644 index 00000000..0c2acbed --- /dev/null +++ b/interfaces/trade/src/protocol.rs @@ -0,0 +1,166 @@ +//! Wire messages for the swap protocol. +//! +//! Messages carry negotiated plan data or proven artifacts. Receive-side +//! checks validate disclosures, pods, and grounding data. Fields name the +//! accepter and initiator objects consistently: event 0 transfers the +//! accepter's object; event 1 transfers the initiator's. + +use joint_tx::{JointTransaction, TransferAcceptance, TransferOffer}; +use pod2::{ + frontend::MainPod, + middleware::{EMPTY_VALUE, Hash, Statement, StrKey, Value, containers::Dictionary}, +}; +use serde::{Deserialize, Serialize}; +use txlib::{StateHeader, compute_nullifier, erased_key_state}; + +/// Seat names embedded in the shared `JointTransaction`. +pub const INITIATOR: &str = "initiator"; +pub const ACCEPTER: &str = "accepter"; + +/// Erased-key state, original commitment, and nullifier for one swap leg. +/// +/// These values define the negotiated plan; subsequent proofs enforce it. +/// Disclosure lets the counterparty recognize a later spend of this state, +/// even if the trade is abandoned. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LegDisclosure { + pub mid: Dictionary, + pub old_commitment: Hash, + pub nullifier: Hash, +} + +impl LegDisclosure { + pub fn of(obj: &Dictionary) -> Self { + Self { + mid: erased_key_state(obj), + old_commitment: obj.commitment(), + nullifier: compute_nullifier(obj), + } + } + + /// Require an erased key and the negotiated class. + pub fn validate(&self, expected_class: Hash) -> anyhow::Result<()> { + let key = self + .mid + .get(&StrKey::from("key")) + .ok() + .flatten() + .ok_or_else(|| anyhow::anyhow!("disclosed state has no key entry"))?; + anyhow::ensure!( + key == Value::from(EMPTY_VALUE), + "disclosed state's key is not erased" + ); + let class = self + .mid + .get(&StrKey::from("type")) + .ok() + .flatten() + .ok_or_else(|| anyhow::anyhow!("disclosed state has no type entry"))?; + anyhow::ensure!( + class == Value::from(expected_class), + "disclosed object is of class {class}, negotiated class is {:#}", + expected_class + ); + Ok(()) + } +} + +/// Accepter -> initiator: accepts the invitation and discloses the +/// object it gives. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptMsg { + pub accepter_object: LegDisclosure, +} + +/// Initiator -> accepter: initiator disclosure and remaining plan data. +/// `header` grounds the transaction; `accepter_object_new` commits to +/// the accepter's object under the initiator's private new key. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlanDataMsg { + pub initiator_object: LegDisclosure, + pub header: StateHeader, + pub accepter_object_new: Hash, +} + +/// Accepter -> initiator: independently derived deal document. +/// The initiator requires exact equality before proving. Deserialization +/// validates the document. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlanAckMsg { + pub transaction: JointTransaction, +} + +/// Initiator -> accepter: transfer offer and its pod. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OfferMsg { + pub offer: TransferOffer, + pub pod: MainPod, +} + +/// Accepter -> initiator: offer, acceptance, class guard, and shared pod. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptanceMsg { + pub offer: TransferOffer, + pub acceptance: TransferAcceptance, + pub guard: Statement, + pub pod: MainPod, +} + +/// Base58 invitation exchanged out of band. +/// Contains the endpoint, class guard hashes, and proving mode, but no +/// grounding-bound transaction data. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Invitation { + pub node: iroh::EndpointAddr, + /// Guard hash of the class the initiator gives. + pub offers: Hash, + /// Guard hash of the class the initiator wants. + pub wants: Hash, + /// Proving mode selected by the initiator. Mock and real pods cannot mix. + pub mock: bool, +} + +impl Invitation { + pub fn encode(&self) -> String { + bs58::encode(serde_json::to_vec(self).expect("invitation serializes")).into_string() + } + + pub fn decode(blob: &str) -> anyhow::Result { + let bytes = bs58::decode(blob.trim()) + .into_vec() + .map_err(|err| anyhow::anyhow!("not a base58 invitation: {err}"))?; + serde_json::from_slice(&bytes) + .map_err(|err| anyhow::anyhow!("not a trade invitation: {err}")) + } +} + +/// One JSON-line message on the iroh stream. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "camelCase")] +pub enum WireMsg { + Accept(AcceptMsg), + PlanData(PlanDataMsg), + PlanAck(PlanAckMsg), + Offer(Box), + Acceptance(Box), + /// Progress text for the counterparty's screen. + Progress { + note: String, + }, + /// The transaction is with the relayer. + Posted { + tx_hash: Option, + block_number: Option, + }, + /// Either side aborts the trade. + Abort { + reason: String, + }, +} diff --git a/interfaces/trade/src/tests.rs b/interfaces/trade/src/tests.rs new file mode 100644 index 00000000..7252306f --- /dev/null +++ b/interfaces/trade/src/tests.rs @@ -0,0 +1,175 @@ +use std::sync::Arc; + +use payload::test_state::TestState; +use pod2::middleware::{Hash, StrKey, Value}; +use serde::{Serialize, de::DeserializeOwned}; +use txlib::{GroundingWitness, StateHeader, erased_key_state}; + +use crate::engine::{Accepter, ClassDirectory, Initiator, SwapDeps}; + +/// Cross a simulated wire boundary through JSON encoding. +fn wire(value: &T) -> T { + serde_json::from_str(&serde_json::to_string(value).unwrap()).unwrap() +} + +fn grounding_witness(state: &TestState, input_commitments: &[Hash]) -> Arc { + state.build_grounding_witness( + input_commitments, + |block_meta, created_root, nullifiers_root, prior_state_history_root, created_proofs| { + Arc::new(GroundingWitness::new( + StateHeader::new( + block_meta.number as i64, + block_meta.timestamp as i64, + block_meta.hash, + created_root, + nullifiers_root, + prior_state_history_root, + ), + created_proofs, + )) + }, + ) +} + +const CRAFT_SRC: &str = r#" + fn SpawnFoo(action) { + var foo = action.output("Foo"); + foo.set([["quality", 7]]); + } + + fn SpawnBar(action) { + var bar = action.output("Bar"); + bar.set([["weight", 3]]); + } +"#; + +struct Fixture { + state: TestState, + deps: SwapDeps, + foo: pod2::middleware::containers::Dictionary, + bar: pod2::middleware::containers::Dictionary, + foo_class: Hash, + bar_class: Hash, +} + +fn fixture() -> Fixture { + let sdk = sdk::Sdk::default(); + let module = sdk + .load_module_from_src_actions(CRAFT_SRC, &["SpawnFoo", "SpawnBar"]) + .unwrap(); + + let mut state = TestState::default(); + let executor = module.executor(true, grounding_witness(&state, &[])); + let res = executor.action("SpawnFoo", vec![]).unwrap(); + let [foo] = res.objs(); + state.apply_tx( + res.tx.live_commitments().unwrap(), + res.tx.nullifier_hashes().unwrap(), + ); + let executor = module.executor(true, grounding_witness(&state, &[])); + let res = executor.action("SpawnBar", vec![]).unwrap(); + let [bar] = res.objs(); + state.apply_tx( + res.tx.live_commitments().unwrap(), + res.tx.nullifier_hashes().unwrap(), + ); + + let deps = SwapDeps { + modules: vec![ + Arc::new(txlib::predicates::events_module()), + Arc::new(txlib::predicates::rekey_module()), + Arc::new(txlib::predicates::module()), + module.module().clone(), + ], + classes: ClassDirectory::from_sdk_module(&module), + mock: true, + }; + Fixture { + state, + deps, + foo: foo.obj.clone(), + bar: bar.obj.clone(), + foo_class: module.class_hash("Foo").unwrap(), + bar_class: module.class_hash("Bar").unwrap(), + } +} + +/// Swap two SDK-generated object classes over serialized messages. +#[test] +fn swap_engines_complete_a_trade() { + let fx = fixture(); + + let initiator = Initiator::new(fx.deps.clone(), fx.foo.clone(), fx.bar_class); + let accepter = Accepter::new(fx.deps.clone(), fx.bar.clone(), fx.foo_class); + + let (accepter, accept_msg) = accepter.accept(); + let accept_msg = wire(&accept_msg); + let witness = grounding_witness( + &fx.state, + &[ + accept_msg.accepter_object.old_commitment, + fx.foo.commitment(), + ], + ); + let (initiator, plan_data) = initiator.on_accept(&accept_msg, witness).unwrap(); + let (accepter, plan_ack) = accepter.on_plan_data(&wire(&plan_data)).unwrap(); + + let (initiator, offer_msg) = initiator.on_plan_ack(&wire(&plan_ack)).unwrap(); + let (acceptance_msg, accepter_expect) = accepter.on_offer(wire(&offer_msg)).unwrap(); + + let outcome = initiator.on_acceptance(wire(&acceptance_msg)).unwrap(); + + assert_eq!(outcome.expectation.tx_final, accepter_expect.tx_final); + assert_eq!(outcome.tx.dict().commitment(), outcome.expectation.tx_final); + for commitment in &outcome.expectation.new_commitments { + assert!(outcome.tx.live.contains(&Value::from(*commitment)).unwrap()); + } + for nullifier in &outcome.expectation.nullifiers { + assert!( + outcome + .tx + .nullifiers + .contains(&Value::from(*nullifier)) + .unwrap() + ); + } + + let initiator_received = &outcome.expectation.received; + assert_eq!( + initiator_received.get(&StrKey::from("type")).unwrap(), + Some(Value::from(fx.bar_class)) + ); + assert_eq!( + erased_key_state(initiator_received).commitment(), + erased_key_state(&fx.bar).commitment() + ); + let accepter_received = &accepter_expect.received; + assert_eq!( + accepter_received.get(&StrKey::from("type")).unwrap(), + Some(Value::from(fx.foo_class)) + ); + assert_eq!( + erased_key_state(accepter_received).commitment(), + erased_key_state(&fx.foo).commitment() + ); + assert_ne!( + initiator_received.get(&StrKey::from("key")).unwrap(), + fx.bar.get(&StrKey::from("key")).unwrap() + ); +} + +/// Reject a disclosure of the wrong class before proving. +#[test] +fn wrong_class_disclosure_is_rejected() { + let fx = fixture(); + + let initiator = Initiator::new(fx.deps.clone(), fx.foo.clone(), fx.bar_class); + let accepter = Accepter::new(fx.deps.clone(), fx.foo.clone(), fx.foo_class); + let (_accepter, accept_msg) = accepter.accept(); + let witness = grounding_witness(&fx.state, &[fx.foo.commitment()]); + let err = initiator + .on_accept(&wire(&accept_msg), witness) + .err() + .expect("wrong-class disclosure must be rejected"); + assert!(err.to_string().contains("class"), "unexpected error: {err}"); +} diff --git a/interfaces/trade/src/ui.rs b/interfaces/trade/src/ui.rs new file mode 100644 index 00000000..667bea1e --- /dev/null +++ b/interfaces/trade/src/ui.rs @@ -0,0 +1,86 @@ +//! Terminal narration for protocol messages and proof artifacts. + +use std::io::Write as _; + +use pod2::middleware::{Hash, StrKey, Value, containers::Dictionary}; + +pub fn banner() { + println!(); + println!(" +----------------------------------------------+"); + println!(" | ⚠️ TRADE OFFER ⚠️ |"); + println!(" +----------------------------------------------+"); +} + +/// A narration line: who is speaking, over which channel. +pub fn say(who: &str, text: &str) { + println!(" [{who}] {text}"); +} + +pub fn note(text: &str) { + println!(" {text}"); +} + +pub fn short(hash: &Hash) -> String { + let full = format!("{hash:#}"); + if full.len() > 14 { + format!("{}..{}", &full[..8], &full[full.len() - 4..]) + } else { + full + } +} + +/// Render an object's non-key fields compactly, key redacted. +pub fn describe_object(obj: &Dictionary) -> String { + let mut parts = Vec::new(); + let mut entries: Vec<(String, Value)> = obj.iter().filter_map(|kv| kv.ok()).collect(); + entries.sort_by(|a, b| a.0.cmp(&b.0)); + for (name, value) in entries { + match name.as_str() { + "key" => parts.push("key: (private)".to_string()), + "type" | "stable_identifier" => {} + _ => parts.push(format!("{name}: {value}")), + } + } + format!("{{ {} }}", parts.join(", ")) +} + +pub fn object_line(label: &str, obj: &Dictionary) { + println!( + " {label}: {} {}", + short(&obj.commitment()), + describe_object(obj) + ); +} + +/// Blocking yes/no prompt. +pub fn confirm(question: &str) -> bool { + print!(" {question} [y/N] "); + std::io::stdout().flush().ok(); + let mut answer = String::new(); + if std::io::stdin().read_line(&mut answer).is_err() { + return false; + } + matches!(answer.trim(), "y" | "Y" | "yes" | "YES") +} + +pub fn field(label: &str, value: &str) { + println!(" {label:<18} {value}"); +} + +pub fn heading(text: &str) { + println!(); + println!(" == {text} =="); +} + +pub fn key_moment(text: &str) { + println!(); + println!(" *** {text} ***"); + println!(); +} + +pub fn redact_key(obj: &Dictionary) -> String { + match obj.get(&StrKey::from("key")) { + Ok(Some(_)) => "held".to_string(), + _ => "missing".to_string(), + } +} diff --git a/libs/driver/src/clients/mod.rs b/libs/driver/src/clients/mod.rs index 5301e7fd..925471e4 100644 --- a/libs/driver/src/clients/mod.rs +++ b/libs/driver/src/clients/mod.rs @@ -3,16 +3,14 @@ use std::time::Duration; mod relayer_client; mod synchronizer_client; -#[cfg(test)] -pub(crate) use relayer_client::RelayerConfirmation; -pub(crate) use relayer_client::{HttpRelayerClient, RelayerClient}; pub use relayer_client::{ - RELAYER_CONFIRM_TIMEOUT_SECS, RELAYER_POLL_INTERVAL_MS, RELAYER_TX_HASH_TIMEOUT_SECS, + HttpRelayerClient, RELAYER_CONFIRM_TIMEOUT_SECS, RELAYER_POLL_INTERVAL_MS, + RELAYER_TX_HASH_TIMEOUT_SECS, RelayerClient, RelayerConfirmation, +}; +pub use synchronizer_client::{ + HttpSynchronizerClient, SYNCHRONIZER_POLL_INTERVAL_MS, SYNCHRONIZER_POLL_TIMEOUT_SECS, + SynchronizerClient, SynchronizerHead, SynchronizerMembership, }; -pub(crate) use synchronizer_client::{HttpSynchronizerClient, SynchronizerClient}; -pub use synchronizer_client::{SYNCHRONIZER_POLL_INTERVAL_MS, SYNCHRONIZER_POLL_TIMEOUT_SECS}; -#[cfg(test)] -pub(crate) use synchronizer_client::{SynchronizerHead, SynchronizerMembership}; /// Per-request ceiling for a single relayer/synchronizer HTTP call. The poll /// loops only re-check their overall deadline between iterations, so without a diff --git a/libs/driver/src/lib.rs b/libs/driver/src/lib.rs index 08381e9b..40efc2d3 100644 --- a/libs/driver/src/lib.rs +++ b/libs/driver/src/lib.rs @@ -16,8 +16,10 @@ mod tests; pub use crate::catalog::ActionCatalog as DriverActionCatalog; pub use crate::clients::{ - RELAYER_CONFIRM_TIMEOUT_SECS, RELAYER_POLL_INTERVAL_MS, RELAYER_TX_HASH_TIMEOUT_SECS, - SYNCHRONIZER_POLL_INTERVAL_MS, SYNCHRONIZER_POLL_TIMEOUT_SECS, + HttpRelayerClient, HttpSynchronizerClient, RELAYER_CONFIRM_TIMEOUT_SECS, + RELAYER_POLL_INTERVAL_MS, RELAYER_TX_HASH_TIMEOUT_SECS, RelayerClient, RelayerConfirmation, + SYNCHRONIZER_POLL_INTERVAL_MS, SYNCHRONIZER_POLL_TIMEOUT_SECS, SynchronizerClient, + SynchronizerHead, SynchronizerMembership, }; pub use crate::driver::{Driver, DriverDeps, PayloadBuilder}; pub use crate::error::DriverError;