commit b4e083f3beb8dd59f6506a129980133cc3a12233
parent 1038fc9b5b564fbf54bab3bb7c810231b6e8a53e
Author: triesap <tyson@radroots.org>
Date: Thu, 16 Jul 2026 17:40:24 +0000
release-product: align RHI agreement attestation
- remove order validation receipt and remote proof worker paths
- add release-product mutation observation and local attestation reports
- publish only canonical trade mutation handler kinds for service presence
- tighten config, CLI, state paths, source guards, and dependency graph
Diffstat:
20 files changed, 2274 insertions(+), 11237 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3,39 +3,13 @@
version = 4
[[package]]
-name = "addchain"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8"
-dependencies = [
- "num-bigint 0.3.3",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "addr2line"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler2"
-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",
- "generic-array 0.14.7",
+ "generic-array",
]
[[package]]
@@ -46,7 +20,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures 0.2.17",
+ "cpufeatures",
]
[[package]]
@@ -86,15 +60,6 @@ dependencies = [
]
[[package]]
-name = "ansi_term"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
-dependencies = [
- "winapi",
-]
-
-[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -157,51 +122,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
-name = "arrayref"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
-
-[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
-name = "async-scoped"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4042078ea593edffc452eef14e99fdb2b120caa4ad9618bcdeabc4a023b98740"
-dependencies = [
- "futures",
- "pin-project",
- "tokio",
-]
-
-[[package]]
-name = "async-stream"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
-dependencies = [
- "async-stream-impl",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-stream-impl"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -209,7 +135,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -244,24 +170,6 @@ dependencies = [
]
[[package]]
-name = "atoi"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "atomic"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
-dependencies = [
- "bytemuck",
-]
-
-[[package]]
name = "atomic-destructor"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -280,75 +188,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
-name = "axum"
-version = "0.7.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
-dependencies = [
- "async-trait",
- "axum-core",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "itoa",
- "matchit",
- "memchr",
- "mime",
- "percent-encoding",
- "pin-project-lite",
- "rustversion",
- "serde",
- "sync_wrapper",
- "tower 0.5.3",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "axum-core"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
-dependencies = [
- "async-trait",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "mime",
- "pin-project-lite",
- "rustversion",
- "sync_wrapper",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "backtrace"
-version = "0.3.76"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "serde",
- "windows-link",
-]
-
-[[package]]
-name = "base16ct"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
-
-[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -373,15 +212,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
[[package]]
-name = "bincode"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
-dependencies = [
- "serde",
-]
-
-[[package]]
name = "bip39"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -419,38 +249,12 @@ dependencies = [
]
[[package]]
-name = "bitvec"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
-dependencies = [
- "funty",
- "radium",
- "tap",
- "wyz",
-]
-
-[[package]]
-name = "blake3"
-version = "1.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
-dependencies = [
- "arrayref",
- "arrayvec",
- "cc",
- "cfg-if",
- "constant_time_eq",
- "cpufeatures 0.3.0",
-]
-
-[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
]
[[package]]
@@ -459,7 +263,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
]
[[package]]
@@ -469,76 +273,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
-name = "byte-slice-cast"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
-
-[[package]]
-name = "bytemuck"
-version = "1.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
-dependencies = [
- "bytemuck_derive",
-]
-
-[[package]]
-name = "bytemuck_derive"
-version = "1.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
-[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
-name = "camino"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "cargo-platform"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "cargo_metadata"
-version = "0.18.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
-dependencies = [
- "camino",
- "cargo-platform",
- "semver",
- "serde",
- "serde_json",
- "thiserror 1.0.69",
-]
-
-[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -577,7 +317,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures 0.2.17",
+ "cpufeatures",
]
[[package]]
@@ -648,7 +388,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -664,15 +404,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
-name = "concurrent-queue"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
-dependencies = [
- "crossbeam-utils",
-]
-
-[[package]]
name = "config"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -692,25 +423,6 @@ dependencies = [
]
[[package]]
-name = "console"
-version = "0.15.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
-dependencies = [
- "encode_unicode",
- "libc",
- "once_cell",
- "unicode-width",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "const-oid"
-version = "0.9.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
-
-[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -731,33 +443,6 @@ dependencies = [
]
[[package]]
-name = "const_format"
-version = "0.2.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e"
-dependencies = [
- "const_format_proc_macros",
- "konst",
-]
-
-[[package]]
-name = "const_format_proc_macros"
-version = "0.2.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "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.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -782,4730 +467,1733 @@ dependencies = [
]
[[package]]
-name = "cpufeatures"
-version = "0.3.0"
+name = "crossbeam-channel"
+version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
- "libc",
+ "crossbeam-utils",
]
[[package]]
-name = "crash-context"
-version = "0.6.3"
+name = "crossbeam-utils"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "031ed29858d90cfdf27fe49fae28028a1f20466db97962fa2f4ea34809aeebf3"
-dependencies = [
- "cfg-if",
- "libc",
- "mach2",
-]
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
-name = "crash-handler"
-version = "0.7.0"
+name = "crunchy"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0df5c9639f4942eb7702b964b3f9adf03a55724a57558cc177407388a8b936e2"
-dependencies = [
- "cfg-if",
- "crash-context",
- "libc",
- "mach2",
- "parking_lot",
-]
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
-name = "crc"
-version = "3.4.0"
+name = "crypto-common"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
- "crc-catalog",
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
]
[[package]]
-name = "crc-catalog"
-version = "2.5.0"
+name = "data-encoding"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]]
-name = "crc32fast"
-version = "1.5.0"
+name = "deranged"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
- "cfg-if",
+ "powerfmt",
]
[[package]]
-name = "crossbeam"
-version = "0.8.4"
+name = "digest"
+version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "crossbeam-channel",
- "crossbeam-deque",
- "crossbeam-epoch",
- "crossbeam-queue",
- "crossbeam-utils",
+ "block-buffer",
+ "crypto-common",
+ "subtle",
]
[[package]]
-name = "crossbeam-channel"
-version = "0.5.15"
+name = "displaydoc"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
- "crossbeam-utils",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
-name = "crossbeam-deque"
-version = "0.8.6"
+name = "dlv-list"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
- "crossbeam-epoch",
- "crossbeam-utils",
+ "const-random",
]
[[package]]
-name = "crossbeam-epoch"
-version = "0.9.18"
+name = "either"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
-dependencies = [
- "crossbeam-utils",
-]
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
-name = "crossbeam-queue"
-version = "0.3.12"
+name = "encoding_rs"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
- "crossbeam-utils",
+ "cfg-if",
]
[[package]]
-name = "crossbeam-utils"
-version = "0.8.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-
-[[package]]
-name = "crunchy"
-version = "0.2.4"
+name = "equivalent"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
-name = "crypto-bigint"
-version = "0.5.5"
+name = "errno"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
- "generic-array 0.14.7",
- "rand_core 0.6.4",
- "subtle",
- "zeroize",
+ "libc",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "crypto-common"
-version = "0.1.7"
+name = "fastrand"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
-dependencies = [
- "generic-array 0.14.7",
- "rand_core 0.6.4",
- "typenum",
-]
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
-name = "dashu"
-version = "0.4.2"
+name = "find-msvc-tools"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85b3e5ac1e23ff1995ef05b912e2b012a8784506987a2651552db2c73fb3d7e0"
-dependencies = [
- "dashu-base",
- "dashu-float",
- "dashu-int",
- "dashu-macros",
- "dashu-ratio",
- "rustversion",
-]
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
-name = "dashu-base"
-version = "0.4.1"
+name = "foldhash"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0b80bf6b85aa68c58ffea2ddb040109943049ce3fbdf4385d0380aef08ef289"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
-name = "dashu-float"
-version = "0.4.3"
+name = "form_urlencoded"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85078445a8dbd2e1bd21f04a816f352db8d333643f0c9b78ca7c3d1df71063e7"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
- "dashu-base",
- "dashu-int",
- "num-modular",
- "num-order",
- "rustversion",
- "static_assertions",
+ "percent-encoding",
]
[[package]]
-name = "dashu-int"
-version = "0.4.1"
+name = "futures"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee99d08031ca34a4d044efbbb21dff9b8c54bb9d8c82a189187c0651ffdb9fbf"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
- "cfg-if",
- "dashu-base",
- "num-modular",
- "num-order",
- "rustversion",
- "static_assertions",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
]
[[package]]
-name = "dashu-macros"
-version = "0.4.1"
+name = "futures-channel"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93381c3ef6366766f6e9ed9cf09e4ef9dec69499baf04f0c60e70d653cf0ab10"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
- "dashu-base",
- "dashu-float",
- "dashu-int",
- "dashu-ratio",
- "paste",
- "proc-macro2",
- "quote",
- "rustversion",
+ "futures-core",
+ "futures-sink",
]
[[package]]
-name = "dashu-ratio"
-version = "0.4.1"
+name = "futures-core"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47e33b04dd7ce1ccf8a02a69d3419e354f2bbfdf4eb911a0b7465487248764c9"
-dependencies = [
- "dashu-base",
- "dashu-float",
- "dashu-int",
- "num-modular",
- "num-order",
- "rustversion",
-]
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
-name = "data-encoding"
-version = "2.10.0"
+name = "futures-io"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
-name = "deepsize2"
-version = "0.1.0"
+name = "futures-sink"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b5184084af9beed35eecbf4c36baf6e26b9dc47b61b74e02f930c72a58e71b"
-dependencies = [
- "deepsize_derive2",
- "hashbrown 0.14.5",
-]
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
-name = "deepsize_derive2"
-version = "0.1.0"
+name = "futures-task"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0f8817865cacf3b93b943ca06b0fc5fd8e99eabfdb7ea5d296efcbc4afc4f69"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
-name = "der"
-version = "0.7.10"
+name = "futures-util"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
- "const-oid",
- "pem-rfc7468",
- "zeroize",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
]
[[package]]
-name = "deranged"
-version = "0.5.8"
+name = "generic-array"
+version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
- "powerfmt",
+ "typenum",
+ "version_check",
]
[[package]]
-name = "derive-where"
-version = "1.6.1"
+name = "getrandom"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
]
[[package]]
-name = "derive_more"
-version = "1.0.0"
+name = "getrandom"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
- "derive_more-impl",
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+ "wasm-bindgen",
]
[[package]]
-name = "derive_more-impl"
-version = "1.0.0"
+name = "getrandom"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "wasip2",
+ "wasip3",
]
[[package]]
-name = "digest"
-version = "0.10.7"
+name = "gloo-timers"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
dependencies = [
- "block-buffer",
- "const-oid",
- "crypto-common",
- "subtle",
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
]
[[package]]
-name = "dirs"
-version = "5.0.1"
+name = "hashbrown"
+version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
- "dirs-sys",
+ "ahash",
+ "allocator-api2",
]
[[package]]
-name = "dirs-sys"
-version = "0.4.1"
+name = "hashbrown"
+version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "libc",
- "option-ext",
- "redox_users",
- "windows-sys 0.48.0",
+ "foldhash",
]
[[package]]
-name = "displaydoc"
-version = "0.2.5"
+name = "hashbrown"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
-name = "dlv-list"
-version = "0.5.2"
+name = "hashlink"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
+checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
- "const-random",
+ "hashbrown 0.14.5",
]
[[package]]
-name = "dotenvy"
-version = "0.15.7"
+name = "heck"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
-name = "downcast-rs"
-version = "1.2.1"
+name = "hex"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
-name = "dynasm"
-version = "3.2.1"
+name = "hex-conservative"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7d4c414c94bc830797115b8e5f434d58e7e80cb42ba88508c14bc6ea270625"
+checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f"
dependencies = [
- "bitflags",
- "byteorder",
- "lazy_static",
- "proc-macro-error2",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "arrayvec",
]
[[package]]
-name = "dynasmrt"
-version = "3.2.1"
+name = "hmac"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "602f7458a3859195fb840e6e0cce5f4330dd9dfbfece0edaf31fe427af346f55"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
- "byteorder",
- "dynasm",
- "fnv",
- "memmap2",
+ "digest",
]
[[package]]
-name = "ecdsa"
-version = "0.16.9"
+name = "http"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
- "der",
- "digest",
- "elliptic-curve",
- "rfc6979",
- "serdect",
- "signature",
- "spki",
+ "bytes",
+ "itoa",
]
[[package]]
-name = "either"
-version = "1.15.0"
+name = "http-body"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
- "serde",
+ "bytes",
+ "http",
]
[[package]]
-name = "elf"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
-
-[[package]]
-name = "elliptic-curve"
-version = "0.13.8"
+name = "http-body-util"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
- "base16ct",
- "crypto-bigint",
- "digest",
- "ff",
- "generic-array 0.14.7",
- "group",
- "pem-rfc7468",
- "pkcs8",
- "rand_core 0.6.4",
- "sec1",
- "serdect",
- "subtle",
- "zeroize",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
]
[[package]]
-name = "encode_unicode"
-version = "1.0.0"
+name = "httparse"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
-name = "encoding_rs"
-version = "0.8.35"
+name = "hyper"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
- "cfg-if",
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec",
+ "tokio",
+ "want",
]
[[package]]
-name = "enum-map"
-version = "2.7.3"
+name = "hyper-rustls"
+version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
- "enum-map-derive",
- "serde",
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots 1.0.6",
]
[[package]]
-name = "enum-map-derive"
-version = "0.17.0"
+name = "hyper-util"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
]
[[package]]
-name = "equivalent"
-version = "1.0.2"
+name = "iana-time-zone"
+version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
[[package]]
-name = "errno"
-version = "0.3.14"
+name = "iana-time-zone-haiku"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
- "libc",
- "windows-sys 0.61.2",
+ "cc",
]
[[package]]
-name = "event-listener"
-version = "5.4.1"
+name = "icu_collections"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
- "concurrent-queue",
- "parking",
- "pin-project-lite",
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
]
[[package]]
-name = "eventsource-stream"
-version = "0.2.3"
+name = "icu_locale_core"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
- "futures-core",
- "nom",
- "pin-project-lite",
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
]
[[package]]
-name = "eyre"
-version = "0.6.12"
+name = "icu_normalizer"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
- "indenter",
- "once_cell",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
]
[[package]]
-name = "fastrand"
-version = "2.3.0"
+name = "icu_normalizer_data"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
-name = "ff"
-version = "0.13.1"
+name = "icu_properties"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
dependencies = [
- "bitvec",
- "byteorder",
- "ff_derive",
- "rand_core 0.6.4",
- "subtle",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
]
[[package]]
-name = "ff_derive"
-version = "0.13.1"
+name = "icu_properties_data"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60"
-dependencies = [
- "addchain",
- "num-bigint 0.3.3",
- "num-integer",
- "num-traits",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
[[package]]
-name = "find-msvc-tools"
-version = "0.1.9"
+name = "icu_provider"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
[[package]]
-name = "fixedbitset"
-version = "0.5.7"
+name = "id-arena"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
-name = "flate2"
-version = "1.1.9"
+name = "idna"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
- "crc32fast",
- "miniz_oxide",
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
]
[[package]]
-name = "flume"
-version = "0.12.0"
+name = "idna_adapter"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
- "futures-core",
- "futures-sink",
- "spin",
+ "icu_normalizer",
+ "icu_properties",
]
[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
-[[package]]
-name = "foldhash"
-version = "0.1.5"
+name = "indexmap"
+version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
[[package]]
-name = "foldhash"
-version = "0.2.0"
+name = "inout"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
[[package]]
-name = "form_urlencoded"
-version = "1.2.2"
+name = "instant"
+version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
- "percent-encoding",
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
]
[[package]]
-name = "funty"
-version = "2.0.0"
+name = "ipnet"
+version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
-name = "futures"
-version = "0.3.32"
+name = "iri-string"
+version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
+checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
dependencies = [
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
+ "memchr",
+ "serde",
]
[[package]]
-name = "futures-channel"
-version = "0.3.32"
+name = "is_terminal_polyfill"
+version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
-name = "futures-core"
-version = "0.3.32"
+name = "itoa"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "futures-executor"
-version = "0.3.32"
+name = "js-sys"
+version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
+checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
- "futures-core",
- "futures-task",
- "futures-util",
+ "once_cell",
+ "wasm-bindgen",
]
[[package]]
-name = "futures-intrusive"
-version = "0.5.0"
+name = "json5"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
- "futures-core",
- "lock_api",
- "parking_lot",
+ "pest",
+ "pest_derive",
+ "serde",
]
[[package]]
-name = "futures-io"
-version = "0.3.32"
+name = "lazy_static"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
-
-[[package]]
-name = "futures-macro"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "futures-sink"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
-
-[[package]]
-name = "futures-task"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
-
-[[package]]
-name = "futures-util"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-macro",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-project-lite",
- "slab",
-]
-
-[[package]]
-name = "gcd"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
-
-[[package]]
-name = "gen_ops"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "304de19db7028420975a296ab0fcbbc8e69438c4ed254a1e41e2a7f37d5f0e0a"
-
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
- "zeroize",
-]
-
-[[package]]
-name = "generic-array"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96512db27971c2c3eece70a1e106fbe6c87760234e31e8f7e5634912fe52794a"
-dependencies = [
- "serde",
- "typenum",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
-dependencies = [
- "cfg-if",
- "js-sys",
- "libc",
- "wasi",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
-dependencies = [
- "cfg-if",
- "js-sys",
- "libc",
- "r-efi 5.3.0",
- "wasip2",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi 6.0.0",
- "wasip2",
- "wasip3",
-]
-
-[[package]]
-name = "gimli"
-version = "0.32.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
-
-[[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 = "group"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
-dependencies = [
- "ff",
- "rand_core 0.6.4",
- "subtle",
-]
-
-[[package]]
-name = "h2"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
-dependencies = [
- "atomic-waker",
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "http",
- "indexmap 2.13.0",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
-[[package]]
-name = "hashbrown"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-dependencies = [
- "ahash",
- "allocator-api2",
- "serde",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.15.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
-dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash 0.1.5",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
-dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash 0.2.0",
-]
-
-[[package]]
-name = "hashlink"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
-dependencies = [
- "hashbrown 0.14.5",
-]
-
-[[package]]
-name = "hashlink"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
-dependencies = [
- "hashbrown 0.16.1",
-]
-
-[[package]]
-name = "heck"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
-[[package]]
-name = "hermit-abi"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
-
-[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-[[package]]
-name = "hex-conservative"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f"
-dependencies = [
- "arrayvec",
-]
-
-[[package]]
-name = "hmac"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
-dependencies = [
- "digest",
-]
-
-[[package]]
-name = "http"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
-dependencies = [
- "bytes",
- "itoa",
-]
-
-[[package]]
-name = "http-body"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
-dependencies = [
- "bytes",
- "http",
-]
-
-[[package]]
-name = "http-body-util"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
-dependencies = [
- "bytes",
- "futures-core",
- "http",
- "http-body",
- "pin-project-lite",
-]
-
-[[package]]
-name = "httparse"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
-
-[[package]]
-name = "httpdate"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
-
-[[package]]
-name = "hyper"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
-dependencies = [
- "atomic-waker",
- "bytes",
- "futures-channel",
- "futures-core",
- "h2",
- "http",
- "http-body",
- "httparse",
- "httpdate",
- "itoa",
- "pin-project-lite",
- "pin-utils",
- "smallvec",
- "tokio",
- "want",
-]
-
-[[package]]
-name = "hyper-rustls"
-version = "0.27.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
-dependencies = [
- "http",
- "hyper",
- "hyper-util",
- "rustls",
- "rustls-pki-types",
- "tokio",
- "tokio-rustls",
- "tower-service",
- "webpki-roots 1.0.6",
-]
-
-[[package]]
-name = "hyper-timeout"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
-dependencies = [
- "hyper",
- "hyper-util",
- "pin-project-lite",
- "tokio",
- "tower-service",
-]
-
-[[package]]
-name = "hyper-util"
-version = "0.1.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
-dependencies = [
- "base64 0.22.1",
- "bytes",
- "futures-channel",
- "futures-util",
- "http",
- "http-body",
- "hyper",
- "ipnet",
- "libc",
- "percent-encoding",
- "pin-project-lite",
- "socket2 0.6.3",
- "tokio",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "iana-time-zone"
-version = "0.1.65"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core 0.62.2",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "icu_collections"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
-dependencies = [
- "displaydoc",
- "potential_utf",
- "yoke",
- "zerofrom",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locale_core"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
-dependencies = [
- "displaydoc",
- "litemap",
- "tinystr",
- "writeable",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
-dependencies = [
- "icu_collections",
- "icu_normalizer_data",
- "icu_properties",
- "icu_provider",
- "smallvec",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer_data"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
-
-[[package]]
-name = "icu_properties"
-version = "2.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
-dependencies = [
- "icu_collections",
- "icu_locale_core",
- "icu_properties_data",
- "icu_provider",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "icu_properties_data"
-version = "2.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
-
-[[package]]
-name = "icu_provider"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
-dependencies = [
- "displaydoc",
- "icu_locale_core",
- "writeable",
- "yoke",
- "zerofrom",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "id-arena"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
-
-[[package]]
-name = "idna"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
-dependencies = [
- "idna_adapter",
- "smallvec",
- "utf8_iter",
-]
-
-[[package]]
-name = "idna_adapter"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
-dependencies = [
- "icu_normalizer",
- "icu_properties",
-]
-
-[[package]]
-name = "impl-trait-for-tuples"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "indenter"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
-
-[[package]]
-name = "indexmap"
-version = "1.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-dependencies = [
- "autocfg",
- "hashbrown 0.12.3",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
-dependencies = [
- "equivalent",
- "hashbrown 0.16.1",
- "serde",
- "serde_core",
-]
-
-[[package]]
-name = "indicatif"
-version = "0.17.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
-dependencies = [
- "console",
- "number_prefix",
- "portable-atomic",
- "unicode-width",
- "web-time",
-]
-
-[[package]]
-name = "inout"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
-dependencies = [
- "block-padding",
- "generic-array 0.14.7",
-]
-
-[[package]]
-name = "instant"
-version = "0.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
-dependencies = [
- "cfg-if",
- "js-sys",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "ipnet"
-version = "2.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
-
-[[package]]
-name = "iri-string"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
-dependencies = [
- "memchr",
- "serde",
-]
-
-[[package]]
-name = "is_terminal_polyfill"
-version = "1.70.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
-
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "itertools"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
-
-[[package]]
-name = "js-sys"
-version = "0.3.91"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
-dependencies = [
- "once_cell",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "json5"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
-dependencies = [
- "pest",
- "pest_derive",
- "serde",
-]
-
-[[package]]
-name = "k256"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
-dependencies = [
- "cfg-if",
- "ecdsa",
- "elliptic-curve",
- "once_cell",
- "serdect",
- "sha2",
- "signature",
-]
-
-[[package]]
-name = "konst"
-version = "0.2.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb"
-dependencies = [
- "konst_macro_rules",
-]
-
-[[package]]
-name = "konst_macro_rules"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
-
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-dependencies = [
- "spin",
-]
-
-[[package]]
-name = "leb128fmt"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
-
-[[package]]
-name = "libc"
-version = "0.2.183"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
-
-[[package]]
-name = "libredox"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "libsqlite3-sys"
-version = "0.37.0"
-dependencies = [
- "cc",
- "pkg-config",
- "vcpkg",
-]
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
-
-[[package]]
-name = "litemap"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
-
-[[package]]
-name = "lock_api"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
-
-[[package]]
-name = "lru"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
-dependencies = [
- "hashbrown 0.15.5",
-]
-
-[[package]]
-name = "lru"
-version = "0.16.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
-
-[[package]]
-name = "lru-slab"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
-
-[[package]]
-name = "mach2"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "matchers"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
-dependencies = [
- "regex-automata",
-]
-
-[[package]]
-name = "matchit"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
-
-[[package]]
-name = "md-5"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
-dependencies = [
- "cfg-if",
- "digest",
-]
-
-[[package]]
-name = "memchr"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
-
-[[package]]
-name = "memfd"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227"
-dependencies = [
- "rustix",
-]
-
-[[package]]
-name = "memmap2"
-version = "0.9.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "mime"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
-
-[[package]]
-name = "minimal-lexical"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-
-[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
- "simd-adler32",
-]
-
-[[package]]
-name = "mio"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
-dependencies = [
- "libc",
- "wasi",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "mti"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9563a7d5556636e74bbd8773241fbcbc5c89b9f6bfdc97b29b56e740c2c74b9"
-dependencies = [
- "typeid_prefix",
- "typeid_suffix",
-]
-
-[[package]]
-name = "multimap"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
-
-[[package]]
-name = "negentropy"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
-
-[[package]]
-name = "nom"
-version = "7.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-dependencies = [
- "memchr",
- "minimal-lexical",
-]
-
-[[package]]
-name = "nostr"
-version = "0.44.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1"
-dependencies = [
- "aes",
- "base64 0.22.1",
- "bech32",
- "bip39",
- "bitcoin_hashes",
- "cbc",
- "chacha20",
- "chacha20poly1305",
- "getrandom 0.2.17",
- "hex",
- "instant",
- "scrypt",
- "secp256k1",
- "serde",
- "serde_json",
- "unicode-normalization",
- "url",
-]
-
-[[package]]
-name = "nostr-database"
-version = "0.44.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
-dependencies = [
- "lru 0.16.3",
- "nostr",
- "tokio",
-]
-
-[[package]]
-name = "nostr-gossip"
-version = "0.44.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6"
-dependencies = [
- "nostr",
-]
-
-[[package]]
-name = "nostr-relay-pool"
-version = "0.44.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b"
-dependencies = [
- "async-utility",
- "async-wsocket",
- "atomic-destructor",
- "hex",
- "lru 0.16.3",
- "negentropy",
- "nostr",
- "nostr-database",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "nostr-sdk"
-version = "0.44.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393"
-dependencies = [
- "async-utility",
- "nostr",
- "nostr-database",
- "nostr-gossip",
- "nostr-relay-pool",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "ntapi"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "nu-ansi-term"
-version = "0.50.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "num"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
-dependencies = [
- "num-bigint 0.4.6",
- "num-complex",
- "num-integer",
- "num-iter",
- "num-rational",
- "num-traits",
-]
-
-[[package]]
-name = "num-bigint"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-bigint"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
-dependencies = [
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-complex"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "num-conv"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
-
-[[package]]
-name = "num-integer"
-version = "0.1.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "num-iter"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-modular"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
-
-[[package]]
-name = "num-order"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
-dependencies = [
- "num-modular",
-]
-
-[[package]]
-name = "num-rational"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
-dependencies = [
- "num-bigint 0.4.6",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
-name = "num_enum"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
-dependencies = [
- "num_enum_derive",
-]
-
-[[package]]
-name = "num_enum_derive"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
-dependencies = [
- "proc-macro-crate 1.3.1",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "number_prefix"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
-
-[[package]]
-name = "object"
-version = "0.37.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
-dependencies = [
- "crc32fast",
- "flate2",
- "hashbrown 0.15.5",
- "indexmap 2.13.0",
- "memchr",
- "ruzstd",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.21.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
-
-[[package]]
-name = "once_cell_polyfill"
-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 = "opentelemetry"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76"
-dependencies = [
- "futures-core",
- "futures-sink",
- "js-sys",
- "once_cell",
- "pin-project-lite",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "option-ext"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-
-[[package]]
-name = "ordered-multimap"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
-dependencies = [
- "dlv-list",
- "hashbrown 0.14.5",
-]
-
-[[package]]
-name = "p256"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
-dependencies = [
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "sha2",
-]
-
-[[package]]
-name = "p3-air"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3a5de20a2301bf2530de1ceb13768ec3a80f729fbf8b72f813e30bc54c5bce2"
-dependencies = [
- "p3-field",
- "p3-matrix",
- "serde",
-]
-
-[[package]]
-name = "p3-baby-bear"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d69e6e9af4eaaaa60f7bb9f0e0f73ebcbaefe7e00974d97ad0fa542d6a4f0890"
-dependencies = [
- "cfg-if",
- "num-bigint 0.4.6",
- "p3-field",
- "p3-mds",
- "p3-poseidon2",
- "p3-symmetric",
- "rand 0.8.5",
- "rustc_version",
- "serde",
-]
-
-[[package]]
-name = "p3-bn254-fr"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2077757c7cb514202ccb5368f521f23f5709c720599e6545c683c66e0a52d2d8"
-dependencies = [
- "ff",
- "num-bigint 0.4.6",
- "p3-field",
- "p3-poseidon2",
- "p3-symmetric",
- "rand 0.8.5",
- "serde",
-]
-
-[[package]]
-name = "p3-challenger"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6a908924d43e4cfb93fb41c8346cac211b70314385a9037e9241f5b7f3eaf77"
-dependencies = [
- "p3-field",
- "p3-maybe-rayon",
- "p3-symmetric",
- "p3-util",
- "serde",
- "tracing",
-]
-
-[[package]]
-name = "p3-commit"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50acacc7219fce6c01db938f82c1b21b5e7133990b7fff861f91534aeb569419"
-dependencies = [
- "itertools 0.12.1",
- "p3-challenger",
- "p3-field",
- "p3-matrix",
- "p3-util",
- "serde",
-]
-
-[[package]]
-name = "p3-dft"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be6408b10a2c27eb13a7d5580c546c2179a8dc7dbc10a990657311891f9b41c0"
-dependencies = [
- "p3-field",
- "p3-matrix",
- "p3-maybe-rayon",
- "p3-util",
- "tracing",
-]
-
-[[package]]
-name = "p3-field"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dc75969ca3ac847f43e632ab979d59ff7a68f9eac8dbf8edcbba47fc2e1d3aa"
-dependencies = [
- "itertools 0.12.1",
- "num-bigint 0.4.6",
- "num-traits",
- "p3-util",
- "rand 0.8.5",
- "serde",
-]
-
-[[package]]
-name = "p3-fri"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cbc4965ee488f3247867b7ec4bb005b8afa72cb0d461a4dcb1387ecab6426d5"
-dependencies = [
- "itertools 0.12.1",
- "p3-challenger",
- "p3-commit",
- "p3-dft",
- "p3-field",
- "p3-interpolation",
- "p3-matrix",
- "p3-maybe-rayon",
- "p3-util",
- "serde",
- "tracing",
-]
-
-[[package]]
-name = "p3-interpolation"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08ad7e9f08c336d7ea39d12e11951188473542565323bac2a6535e536b58487d"
-dependencies = [
- "p3-field",
- "p3-matrix",
- "p3-util",
-]
-
-[[package]]
-name = "p3-keccak-air"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f5bf177d56740078b5a5a842fa2393427796283dc8174b4a1a325c2d0b042de"
-dependencies = [
- "p3-air",
- "p3-field",
- "p3-matrix",
- "p3-maybe-rayon",
- "p3-util",
- "tracing",
-]
-
-[[package]]
-name = "p3-koala-bear"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a9683cd0ef68100df7c62490533047bcf19c04c4a0fa1efc9d7c1e03e31f6b3"
-dependencies = [
- "cfg-if",
- "num-bigint 0.4.6",
- "p3-field",
- "p3-mds",
- "p3-poseidon2",
- "p3-symmetric",
- "rand 0.8.5",
- "rustc_version",
- "serde",
-]
-
-[[package]]
-name = "p3-matrix"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c3f150ceb90e09539413bf481e618d05ee19210b4e467d2902eb82d2e15281"
-dependencies = [
- "itertools 0.12.1",
- "p3-field",
- "p3-maybe-rayon",
- "p3-util",
- "rand 0.8.5",
- "serde",
- "tracing",
-]
-
-[[package]]
-name = "p3-maybe-rayon"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0641952b42da45e1dfa2d4a2a3163e330f944ad9740942f35026c0a71a605f1"
-dependencies = [
- "rayon",
-]
-
-[[package]]
-name = "p3-mds"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa4a5f250e174dcfca5cbeac6ad75713924e7e7320e0a335e3c50b8b1f4fe8ec"
-dependencies = [
- "itertools 0.12.1",
- "p3-dft",
- "p3-field",
- "p3-matrix",
- "p3-symmetric",
- "p3-util",
- "rand 0.8.5",
-]
-
-[[package]]
-name = "p3-merkle-tree"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5703d9229d52a8c09970e4d722c3a8b4d37e688c306c3a1c03b872efcd204e6"
-dependencies = [
- "itertools 0.12.1",
- "p3-commit",
- "p3-field",
- "p3-matrix",
- "p3-maybe-rayon",
- "p3-symmetric",
- "p3-util",
- "serde",
- "tracing",
-]
-
-[[package]]
-name = "p3-poseidon2"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "522986377b2164c5f94f2dae88e0e0a3d169cc6239202ef4aeb4322d60feffd0"
-dependencies = [
- "gcd",
- "p3-field",
- "p3-mds",
- "p3-symmetric",
- "rand 0.8.5",
- "serde",
-]
-
-[[package]]
-name = "p3-symmetric"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9047ce85c086a9b3f118e10078f10636f7bfeed5da871a04da0b61400af8793a"
-dependencies = [
- "itertools 0.12.1",
- "p3-field",
- "serde",
-]
-
-[[package]]
-name = "p3-uni-stark"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc3dfdeba14d8db621c4e52dd63973384ff35f353fd750154ff88397f4ea5adf"
-dependencies = [
- "itertools 0.12.1",
- "p3-air",
- "p3-challenger",
- "p3-commit",
- "p3-dft",
- "p3-field",
- "p3-matrix",
- "p3-maybe-rayon",
- "p3-util",
- "serde",
- "tracing",
-]
-
-[[package]]
-name = "p3-util"
-version = "0.4.3-succinct"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff962f8eaa5f36e0447cee7c241f6b4b475fadf3ee61f154327a26bb4e009ba"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "parity-scale-codec"
-version = "3.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa"
-dependencies = [
- "arrayvec",
- "byte-slice-cast",
- "const_format",
- "impl-trait-for-tuples",
- "parity-scale-codec-derive",
- "rustversion",
-]
-
-[[package]]
-name = "parity-scale-codec-derive"
-version = "3.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a"
-dependencies = [
- "proc-macro-crate 3.5.0",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "parking"
-version = "2.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
-
-[[package]]
-name = "parking_lot"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-link",
-]
-
-[[package]]
-name = "password-hash"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
-dependencies = [
- "base64ct",
- "rand_core 0.6.4",
- "subtle",
-]
-
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
-[[package]]
-name = "pathdiff"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
-
-[[package]]
-name = "pbkdf2"
-version = "0.12.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
-dependencies = [
- "digest",
- "hmac",
-]
-
-[[package]]
-name = "pem-rfc7468"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
-dependencies = [
- "base64ct",
-]
-
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "pest"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
-dependencies = [
- "memchr",
- "ucd-trie",
-]
-
-[[package]]
-name = "pest_derive"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
-dependencies = [
- "pest",
- "sha2",
-]
-
-[[package]]
-name = "petgraph"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
-dependencies = [
- "fixedbitset",
- "indexmap 2.13.0",
-]
-
-[[package]]
-name = "pin-project"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
-[[package]]
-name = "pkcs8"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
-dependencies = [
- "der",
- "spki",
-]
-
-[[package]]
-name = "pkg-config"
-version = "0.3.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
-
-[[package]]
-name = "poly1305"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
-dependencies = [
- "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"
-
-[[package]]
-name = "potential_utf"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
-dependencies = [
- "zerovec",
-]
-
-[[package]]
-name = "powerfmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
-dependencies = [
- "zerocopy",
-]
-
-[[package]]
-name = "prettyplease"
-version = "0.2.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
-dependencies = [
- "proc-macro2",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "primeorder"
-version = "0.13.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
-dependencies = [
- "elliptic-curve",
-]
-
-[[package]]
-name = "proc-macro-crate"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
-dependencies = [
- "once_cell",
- "toml_edit 0.19.15",
-]
-
-[[package]]
-name = "proc-macro-crate"
-version = "3.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
-dependencies = [
- "toml_edit 0.25.11+spec-1.1.0",
-]
-
-[[package]]
-name = "proc-macro-error-attr2"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
-dependencies = [
- "proc-macro2",
- "quote",
-]
-
-[[package]]
-name = "proc-macro-error2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
-dependencies = [
- "proc-macro-error-attr2",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "prost"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
-dependencies = [
- "bytes",
- "prost-derive",
-]
-
-[[package]]
-name = "prost-build"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
-dependencies = [
- "heck",
- "itertools 0.14.0",
- "log",
- "multimap",
- "once_cell",
- "petgraph",
- "prettyplease",
- "prost",
- "prost-types",
- "regex",
- "syn 2.0.117",
- "tempfile",
-]
-
-[[package]]
-name = "prost-derive"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
-dependencies = [
- "anyhow",
- "itertools 0.14.0",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "prost-types"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
-dependencies = [
- "prost",
-]
-
-[[package]]
-name = "quinn"
-version = "0.11.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
-dependencies = [
- "bytes",
- "cfg_aliases",
- "pin-project-lite",
- "quinn-proto",
- "quinn-udp",
- "rustc-hash",
- "rustls",
- "socket2 0.6.3",
- "thiserror 2.0.18",
- "tokio",
- "tracing",
- "web-time",
-]
-
-[[package]]
-name = "quinn-proto"
-version = "0.11.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
-dependencies = [
- "bytes",
- "getrandom 0.3.4",
- "lru-slab",
- "rand 0.9.2",
- "ring",
- "rustc-hash",
- "rustls",
- "rustls-pki-types",
- "slab",
- "thiserror 2.0.18",
- "tinyvec",
- "tracing",
- "web-time",
-]
-
-[[package]]
-name = "quinn-udp"
-version = "0.5.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
-dependencies = [
- "cfg_aliases",
- "libc",
- "once_cell",
- "socket2 0.6.3",
- "tracing",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
-[[package]]
-name = "r-efi"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
-
-[[package]]
-name = "radium"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-
-[[package]]
-name = "radroots_authority"
-version = "0.1.0-alpha.2"
-dependencies = [
- "radroots_event",
-]
-
-[[package]]
-name = "radroots_core"
-version = "0.1.0-alpha.2"
-dependencies = [
- "rust_decimal",
- "rust_decimal_macros",
- "serde",
-]
-
-[[package]]
-name = "radroots_event"
-version = "0.1.0-alpha.2"
-dependencies = [
- "hex",
- "radroots_core",
- "serde",
- "serde_json",
- "sha2",
-]
-
-[[package]]
-name = "radroots_event_codec"
-version = "0.1.0-alpha.2"
-dependencies = [
- "nostr",
- "radroots_core",
- "radroots_event",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "radroots_identity"
-version = "0.1.0-alpha.2"
-dependencies = [
- "nostr",
- "radroots_event",
- "radroots_protected_store",
- "radroots_runtime",
- "radroots_runtime_paths",
- "radroots_secret_vault",
- "serde",
- "serde_json",
- "thiserror 1.0.69",
- "tracing",
-]
-
-[[package]]
-name = "radroots_log"
-version = "0.1.0-alpha.2"
-dependencies = [
- "chrono",
- "thiserror 1.0.69",
- "tracing",
- "tracing-appender",
- "tracing-subscriber",
-]
-
-[[package]]
-name = "radroots_nostr"
-version = "0.1.0-alpha.2"
-dependencies = [
- "nostr",
- "nostr-sdk",
- "radroots_event",
- "radroots_event_codec",
- "radroots_identity",
- "reqwest",
- "serde",
- "serde_json",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "radroots_protected_store"
-version = "0.1.0-alpha.2"
-dependencies = [
- "chacha20poly1305",
- "getrandom 0.2.17",
- "radroots_secret_vault",
- "serde",
- "serde_json",
- "zeroize",
-]
-
-[[package]]
-name = "radroots_runtime"
-version = "0.1.0-alpha.2"
-dependencies = [
- "anyhow",
- "chacha20poly1305",
- "clap",
- "config",
- "getrandom 0.2.17",
- "radroots_log",
- "radroots_protected_store",
- "radroots_runtime_paths",
- "radroots_secret_vault",
- "serde",
- "serde_json",
- "tempfile",
- "thiserror 1.0.69",
- "tokio",
- "toml",
- "tracing",
- "zeroize",
-]
-
-[[package]]
-name = "radroots_runtime_paths"
-version = "0.1.0-alpha.2"
-dependencies = [
- "serde",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "radroots_secret_vault"
-version = "0.1.0-alpha.2"
-
-[[package]]
-name = "radroots_trade"
-version = "0.1.0-alpha.2"
-dependencies = [
- "base64 0.22.1",
- "hex",
- "radroots_authority",
- "radroots_core",
- "radroots_event",
- "radroots_event_codec",
- "serde",
- "serde_json",
- "sha2",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "radroots_trade_sp1_guest"
-version = "0.1.0-alpha.2"
-dependencies = [
- "serde",
- "serde_json",
- "sha2",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "radroots_trade_sp1_host"
-version = "0.1.0-alpha.2"
-dependencies = [
- "base64 0.22.1",
- "bincode",
- "futures",
- "radroots_trade",
- "radroots_trade_sp1_guest",
- "serde",
- "serde_json",
- "sha2",
- "sp1-build",
- "sp1-sdk",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
-dependencies = [
- "rand_chacha 0.9.0",
- "rand_core 0.9.5",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.9.5",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.17",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
-dependencies = [
- "getrandom 0.3.4",
-]
-
-[[package]]
-name = "range-set-blaze"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8421b5d459262eabbe49048d362897ff3e3830b44eac6cfe341d6acb2f0f13d2"
-dependencies = [
- "gen_ops",
- "itertools 0.12.1",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "rayon"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
-dependencies = [
- "either",
- "rayon-core",
-]
-
-[[package]]
-name = "rayon-core"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
-dependencies = [
- "crossbeam-deque",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "rayon-scan"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f87cc11a0140b4b0da0ffc889885760c61b13672d80a908920b2c0df078fa14"
-dependencies = [
- "rayon",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.5.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "redox_users"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
-dependencies = [
- "getrandom 0.2.17",
- "libredox",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "regex"
-version = "1.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.8.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
-
-[[package]]
-name = "reqwest"
-version = "0.12.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
-dependencies = [
- "base64 0.22.1",
- "bytes",
- "futures-core",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-util",
- "js-sys",
- "log",
- "percent-encoding",
- "pin-project-lite",
- "quinn",
- "rustls",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper",
- "tokio",
- "tokio-rustls",
- "tokio-util",
- "tower 0.5.3",
- "tower-http",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "wasm-streams",
- "web-sys",
- "webpki-roots 1.0.6",
-]
-
-[[package]]
-name = "rfc6979"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
-dependencies = [
- "hmac",
- "subtle",
-]
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
-name = "rhi"
+name = "leb128fmt"
version = "0.1.0"
-dependencies = [
- "anyhow",
- "clap",
- "radroots_core",
- "radroots_event",
- "radroots_event_codec",
- "radroots_identity",
- "radroots_log",
- "radroots_nostr",
- "radroots_runtime",
- "radroots_runtime_paths",
- "radroots_trade",
- "radroots_trade_sp1_guest",
- "radroots_trade_sp1_host",
- "reqwest",
- "serde",
- "serde_json",
- "sha2",
- "sqlx",
- "tempfile",
- "thiserror 2.0.18",
- "tokio",
- "toml",
- "tracing",
-]
-
-[[package]]
-name = "ring"
-version = "0.17.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
-dependencies = [
- "cc",
- "cfg-if",
- "getrandom 0.2.17",
- "libc",
- "untrusted",
- "windows-sys 0.52.0",
-]
-
-[[package]]
-name = "ron"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
-dependencies = [
- "base64 0.21.7",
- "bitflags",
- "serde",
- "serde_derive",
-]
-
-[[package]]
-name = "rrs-succinct"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd079cd303257a4cb4e5aadfa79a7fe23f3c8301aa4740ccc3a99673485a352"
-dependencies = [
- "downcast-rs",
- "num_enum",
- "paste",
-]
-
-[[package]]
-name = "rust-ini"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
-dependencies = [
- "cfg-if",
- "ordered-multimap",
-]
-
-[[package]]
-name = "rust_decimal"
-version = "1.40.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0"
-dependencies = [
- "arrayvec",
- "num-traits",
- "serde",
-]
-
-[[package]]
-name = "rust_decimal_macros"
-version = "1.40.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519"
-dependencies = [
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "rustc-demangle"
-version = "0.1.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
-
-[[package]]
-name = "rustc-hash"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
-
-[[package]]
-name = "rustc-hex"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
-
-[[package]]
-name = "rustc_version"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
-dependencies = [
- "semver",
-]
-
-[[package]]
-name = "rustix"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
-dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "rustls"
-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-pemfile"
-version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
-dependencies = [
- "rustls-pki-types",
-]
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
-name = "rustls-pki-types"
-version = "1.14.0"
+name = "libc"
+version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
-dependencies = [
- "web-time",
- "zeroize",
-]
+checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
-name = "rustls-webpki"
-version = "0.103.9"
+name = "linux-raw-sys"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
-dependencies = [
- "ring",
- "rustls-pki-types",
- "untrusted",
-]
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
-[[package]]
-name = "rustversion"
-version = "1.0.22"
+[[package]]
+name = "litemap"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
-name = "ruzstd"
-version = "0.8.3"
+name = "lock_api"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
- "twox-hash",
+ "scopeguard",
]
[[package]]
-name = "ryu"
-version = "1.0.23"
+name = "log"
+version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
-name = "salsa20"
-version = "0.10.2"
+name = "lru"
+version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
-dependencies = [
- "cipher",
-]
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
[[package]]
-name = "scale-info"
-version = "2.11.6"
+name = "lru-slab"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b"
-dependencies = [
- "cfg-if",
- "derive_more",
- "parity-scale-codec",
- "scale-info-derive",
-]
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
-name = "scale-info-derive"
-version = "2.11.6"
+name = "matchers"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
- "proc-macro-crate 3.5.0",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "regex-automata",
]
[[package]]
-name = "scc"
-version = "2.4.0"
+name = "memchr"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
-dependencies = [
- "sdd",
-]
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
-name = "scopeguard"
-version = "1.2.0"
+name = "minimal-lexical"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
-name = "scrypt"
-version = "0.11.0"
+name = "mio"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
- "password-hash",
- "pbkdf2",
- "salsa20",
- "sha2",
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "sdd"
-version = "3.0.10"
+name = "negentropy"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
+checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
[[package]]
-name = "sec1"
-version = "0.7.3"
+name = "nom"
+version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
- "base16ct",
- "der",
- "generic-array 0.14.7",
- "pkcs8",
- "serdect",
- "subtle",
- "zeroize",
+ "memchr",
+ "minimal-lexical",
]
[[package]]
-name = "secp256k1"
-version = "0.29.1"
+name = "nostr"
+version = "0.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
+checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1"
dependencies = [
- "rand 0.8.5",
- "secp256k1-sys",
+ "aes",
+ "base64 0.22.1",
+ "bech32",
+ "bip39",
+ "bitcoin_hashes",
+ "cbc",
+ "chacha20",
+ "chacha20poly1305",
+ "getrandom 0.2.17",
+ "hex",
+ "instant",
+ "scrypt",
+ "secp256k1",
"serde",
+ "serde_json",
+ "unicode-normalization",
+ "url",
]
[[package]]
-name = "secp256k1-sys"
-version = "0.10.1"
+name = "nostr-database"
+version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
+checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
dependencies = [
- "cc",
+ "lru",
+ "nostr",
+ "tokio",
]
[[package]]
-name = "semver"
-version = "1.0.27"
+name = "nostr-gossip"
+version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6"
dependencies = [
- "serde",
- "serde_core",
+ "nostr",
]
[[package]]
-name = "serde"
-version = "1.0.228"
+name = "nostr-relay-pool"
+version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b"
dependencies = [
- "serde_core",
- "serde_derive",
+ "async-utility",
+ "async-wsocket",
+ "atomic-destructor",
+ "hex",
+ "lru",
+ "negentropy",
+ "nostr",
+ "nostr-database",
+ "tokio",
+ "tracing",
]
[[package]]
-name = "serde_arrays"
-version = "0.2.0"
+name = "nostr-sdk"
+version = "0.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df"
+checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393"
dependencies = [
- "serde",
+ "async-utility",
+ "nostr",
+ "nostr-database",
+ "nostr-gossip",
+ "nostr-relay-pool",
+ "tokio",
+ "tracing",
]
[[package]]
-name = "serde_core"
-version = "1.0.228"
+name = "nu-ansi-term"
+version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "serde_derive",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "serde_derive"
-version = "1.0.228"
+name = "num-conv"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
+checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
-name = "serde_json"
-version = "1.0.149"
+name = "num-traits"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
- "itoa",
- "memchr",
- "serde",
- "serde_core",
- "zmij",
+ "autocfg",
]
[[package]]
-name = "serde_spanned"
-version = "0.6.9"
+name = "once_cell"
+version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
+name = "once_cell_polyfill"
+version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
-name = "serdect"
-version = "0.2.0"
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "ordered-multimap"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
+checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [
- "base16ct",
- "serde",
+ "dlv-list",
+ "hashbrown 0.14.5",
]
[[package]]
-name = "serial_test"
-version = "3.4.0"
+name = "parking_lot"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
- "futures-executor",
- "futures-util",
- "log",
- "once_cell",
- "parking_lot",
- "scc",
- "serial_test_derive",
+ "lock_api",
+ "parking_lot_core",
]
[[package]]
-name = "serial_test_derive"
-version = "3.4.0"
+name = "parking_lot_core"
+version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
]
[[package]]
-name = "sha1"
-version = "0.10.6"
+name = "password-hash"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
- "cfg-if",
- "cpufeatures 0.2.17",
- "digest",
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
]
[[package]]
-name = "sha1_smol"
-version = "1.0.1"
+name = "pathdiff"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
-name = "sha2"
-version = "0.10.9"
+name = "pbkdf2"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
- "cfg-if",
- "cpufeatures 0.2.17",
"digest",
+ "hmac",
]
[[package]]
-name = "sharded-slab"
-version = "0.1.7"
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pest"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
dependencies = [
- "lazy_static",
+ "memchr",
+ "ucd-trie",
]
[[package]]
-name = "shlex"
-version = "1.3.0"
+name = "pest_derive"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
[[package]]
-name = "signal-hook-registry"
-version = "1.4.8"
+name = "pest_generator"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
dependencies = [
- "errno",
- "libc",
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
-name = "signature"
-version = "2.2.0"
+name = "pest_meta"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
dependencies = [
- "digest",
- "rand_core 0.6.4",
+ "pest",
+ "sha2",
]
[[package]]
-name = "simd-adler32"
-version = "0.3.9"
+name = "pin-project-lite"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
-name = "slab"
-version = "0.4.12"
+name = "pin-utils"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
-name = "slop-air"
-version = "6.2.3"
+name = "poly1305"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15597dcaa23dbf30bdbf9709938f34182736792300bb4623fe3459ebe33e775a"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
- "p3-air",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
]
[[package]]
-name = "slop-algebra"
-version = "6.2.3"
+name = "potential_utf"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d7e25239ac2fe1cbc2ed5114844209d70361c8d55ef812cb935c90d6f771b5b"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
- "itertools 0.14.0",
- "p3-field",
- "serde",
+ "zerovec",
]
[[package]]
-name = "slop-alloc"
-version = "6.2.3"
+name = "powerfmt"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aadbdfa7badeb1c7576621d1eeb04f305ed6c93c1accd5da5012ad1ddaea2aa1"
-dependencies = [
- "serde",
- "slop-algebra",
- "thiserror 1.0.69",
-]
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
-name = "slop-baby-bear"
-version = "6.2.3"
+name = "ppv-lite86"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9da8ad7e63b774a2f20704bfc963ebeb4f891e23754a3cb6df47a4a465a85492"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
- "lazy_static",
- "p3-baby-bear",
- "serde",
- "slop-algebra",
- "slop-challenger",
- "slop-poseidon2",
- "slop-symmetric",
+ "zerocopy",
]
[[package]]
-name = "slop-basefold"
-version = "6.2.3"
+name = "prettyplease"
+version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "012eb95fec1133adbf989e6e819943ce3cc2f12b817d063c13885a4b338581ae"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
- "derive-where",
- "itertools 0.14.0",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-baby-bear",
- "slop-bn254",
- "slop-challenger",
- "slop-koala-bear",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-primitives",
- "slop-tensor",
- "slop-utils",
- "thiserror 1.0.69",
+ "proc-macro2",
+ "syn",
]
[[package]]
-name = "slop-basefold-prover"
-version = "6.2.3"
+name = "proc-macro2"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c16dfb91c773ba557a735efe3ffbd29bff54b314440d0623ad9756ffc2dfe3fd"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
- "derive-where",
- "itertools 0.14.0",
- "rand 0.8.5",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-baby-bear",
- "slop-basefold",
- "slop-bn254",
- "slop-challenger",
- "slop-commit",
- "slop-dft",
- "slop-fri",
- "slop-futures",
- "slop-koala-bear",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-tensor",
- "thiserror 1.0.69",
+ "unicode-ident",
]
[[package]]
-name = "slop-bn254"
-version = "6.2.3"
+name = "quinn"
+version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1243ca619a3f4c07f536a060d1fd2c6f779d5b0e4cfeee3acee4d1f76ae35cb4"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
- "ff",
- "p3-bn254-fr",
- "serde",
- "slop-algebra",
- "slop-challenger",
- "slop-poseidon2",
- "slop-symmetric",
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "web-time",
]
[[package]]
-name = "slop-challenger"
-version = "6.2.3"
+name = "quinn-proto"
+version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "594a3251b7fbbabf7e0ba1e5c2f563c2797fc2d51982c0208ce70b2f52b8fbe4"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
- "futures",
- "p3-challenger",
- "serde",
- "slop-algebra",
- "slop-symmetric",
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand 0.9.2",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
]
[[package]]
-name = "slop-commit"
-version = "6.2.3"
+name = "quinn-udp"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee8fd26ee268213b9c36d208a322f6d43ca46347d82cf07a5b83a6b95701890b"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
- "p3-commit",
- "serde",
- "slop-alloc",
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.60.2",
]
[[package]]
-name = "slop-dft"
-version = "6.2.3"
+name = "quote"
+version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f79c2c28e4155679cc2b2ff17a1e258cbffea580182158a8e4d71dc7cd5dc9fe"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
- "p3-dft",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-matrix",
- "slop-tensor",
+ "proc-macro2",
]
[[package]]
-name = "slop-fri"
-version = "6.2.3"
+name = "r-efi"
+version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3645dbec4f2ddfc598d0250e880d422ecae2e70fbb2d7f434f0d5c21df011c1e"
-dependencies = [
- "p3-fri",
-]
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
-name = "slop-futures"
-version = "6.2.3"
+name = "r-efi"
+version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "058c54147a291a867e4253356adf491a29e9d999f45b26103bfc02452d8daa8e"
-dependencies = [
- "crossbeam",
- "futures",
- "pin-project",
- "rayon",
- "thiserror 1.0.69",
- "tokio",
- "tracing",
-]
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
-name = "slop-jagged"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60222309dec4d4bffae090b160847267a23bc5b9ecd4412ba4494b486d21c776"
+name = "radroots_authority"
+version = "0.1.0-alpha.2"
dependencies = [
- "derive-where",
- "futures",
- "itertools 0.14.0",
- "num_cpus",
- "rand 0.8.5",
- "rayon",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-baby-bear",
- "slop-basefold",
- "slop-basefold-prover",
- "slop-bn254",
- "slop-challenger",
- "slop-commit",
- "slop-futures",
- "slop-koala-bear",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-stacked",
- "slop-sumcheck",
- "slop-symmetric",
- "slop-tensor",
- "slop-utils",
- "thiserror 1.0.69",
- "tracing",
+ "radroots_event",
]
[[package]]
-name = "slop-keccak-air"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7d7511944cafbc44ce208dc768b591fa6fc55a87d3f7238f8888e2536db607c"
+name = "radroots_core"
+version = "0.1.0-alpha.2"
dependencies = [
- "p3-keccak-air",
+ "rust_decimal",
+ "rust_decimal_macros",
+ "serde",
]
[[package]]
-name = "slop-koala-bear"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ccc675284794435dd053d5e7415089bbd08fccf92cd91e044ea3213821dfb5c"
+name = "radroots_event"
+version = "0.1.0-alpha.2"
dependencies = [
- "lazy_static",
- "p3-koala-bear",
+ "hex",
+ "radroots_core",
"serde",
- "slop-algebra",
- "slop-challenger",
- "slop-poseidon2",
- "slop-symmetric",
+ "serde_json",
+ "sha2",
]
[[package]]
-name = "slop-matrix"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af3d316b50bf50f8e75d388c829a499ec2f2db77c87c4183d3884d75b99df244"
+name = "radroots_event_codec"
+version = "0.1.0-alpha.2"
dependencies = [
- "p3-matrix",
+ "nostr",
+ "radroots_core",
+ "radroots_event",
+ "serde",
+ "serde_json",
]
[[package]]
-name = "slop-maybe-rayon"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cabdac3bd6efd75739924e4162340caa59b32b24e02d3f84ab8f56d3f3386ee2"
+name = "radroots_identity"
+version = "0.1.0-alpha.2"
dependencies = [
- "p3-maybe-rayon",
+ "nostr",
+ "radroots_event",
+ "radroots_protected_store",
+ "radroots_runtime",
+ "radroots_runtime_paths",
+ "radroots_secret_vault",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "tracing",
]
[[package]]
-name = "slop-merkle-tree"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3b49522dc0a3b87966ba354d8cfcb762baadf9d2641f565aebca39e3032fe2a"
+name = "radroots_log"
+version = "0.1.0-alpha.2"
dependencies = [
- "derive-where",
- "itertools 0.14.0",
- "p3-merkle-tree",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-baby-bear",
- "slop-bn254",
- "slop-challenger",
- "slop-commit",
- "slop-futures",
- "slop-koala-bear",
- "slop-matrix",
- "slop-poseidon2",
- "slop-symmetric",
- "slop-tensor",
- "slop-utils",
+ "chrono",
"thiserror 1.0.69",
+ "tracing",
+ "tracing-appender",
+ "tracing-subscriber",
]
[[package]]
-name = "slop-multilinear"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e04d8ec813376aaa38b94c3e12a07dd6dd76d3e7240708f51a8daef48b6c2f8a"
+name = "radroots_nostr"
+version = "0.1.0-alpha.2"
dependencies = [
- "derive-where",
- "futures",
- "num_cpus",
- "rand 0.8.5",
- "rayon",
+ "nostr",
+ "nostr-sdk",
+ "radroots_event",
+ "radroots_event_codec",
+ "radroots_identity",
+ "reqwest",
"serde",
- "slop-algebra",
- "slop-alloc",
- "slop-challenger",
- "slop-commit",
- "slop-futures",
- "slop-matrix",
- "slop-tensor",
+ "serde_json",
+ "thiserror 1.0.69",
]
[[package]]
-name = "slop-poseidon2"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f77602b918f667f970d017293189f9e60ba056d1933c9f717634877838297bc"
+name = "radroots_protected_store"
+version = "0.1.0-alpha.2"
dependencies = [
- "p3-poseidon2",
+ "chacha20poly1305",
+ "getrandom 0.2.17",
+ "radroots_secret_vault",
+ "serde",
+ "serde_json",
+ "zeroize",
]
[[package]]
-name = "slop-primitives"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "592ded42eb74fd87d2ce13592906cf86f201128dd18e2aa5a35d298bc3534dcb"
+name = "radroots_runtime"
+version = "0.1.0-alpha.2"
dependencies = [
- "slop-algebra",
+ "anyhow",
+ "chacha20poly1305",
+ "clap",
+ "config",
+ "getrandom 0.2.17",
+ "radroots_log",
+ "radroots_protected_store",
+ "radroots_runtime_paths",
+ "radroots_secret_vault",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror 1.0.69",
+ "tokio",
+ "toml",
+ "tracing",
+ "zeroize",
]
[[package]]
-name = "slop-stacked"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd59d332620d6da35a7b4f8837906ec7365761bc0bfda344bb232f8c0b395db7"
+name = "radroots_runtime_paths"
+version = "0.1.0-alpha.2"
dependencies = [
- "derive-where",
- "futures",
- "itertools 0.14.0",
"serde",
- "slop-algebra",
- "slop-alloc",
- "slop-basefold",
- "slop-basefold-prover",
- "slop-challenger",
- "slop-commit",
- "slop-futures",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-tensor",
"thiserror 1.0.69",
]
[[package]]
-name = "slop-sumcheck"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d1176c38a586911076061f419101bdc6491b7b9e2039553f2d3a8237158cfb3"
+name = "radroots_secret_vault"
+version = "0.1.0-alpha.2"
+
+[[package]]
+name = "radroots_trade"
+version = "0.1.0-alpha.2"
dependencies = [
- "futures",
- "itertools 0.14.0",
- "rayon",
+ "base64 0.22.1",
+ "hex",
+ "radroots_authority",
+ "radroots_core",
+ "radroots_event",
+ "radroots_event_codec",
"serde",
- "slop-algebra",
- "slop-alloc",
- "slop-baby-bear",
- "slop-challenger",
- "slop-multilinear",
+ "serde_json",
+ "sha2",
"thiserror 1.0.69",
]
[[package]]
-name = "slop-symmetric"
-version = "6.2.3"
+name = "rand"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "108bf1769132b782218e3606b8e2f310741fb89d1745234b59ff17d9e7ab8700"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
- "p3-symmetric",
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
]
[[package]]
-name = "slop-tensor"
-version = "6.2.3"
+name = "rand"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bb040f7f2dc9be449a25184d2aee0321a748058d81d646a4a96ffcf32e53963"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
- "arrayvec",
- "derive-where",
- "itertools 0.14.0",
- "rand 0.8.5",
- "rayon",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-futures",
- "slop-matrix",
- "thiserror 1.0.69",
- "transpose",
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
]
[[package]]
-name = "slop-uni-stark"
-version = "6.2.3"
+name = "rand_chacha"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d8a279d09e78b0ea5c5e97daa1b6440d47db8118bf3f5014667661c169c4590"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
- "p3-uni-stark",
+ "ppv-lite86",
+ "rand_core 0.6.4",
]
[[package]]
-name = "slop-utils"
-version = "6.2.3"
+name = "rand_chacha"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9910567ffcfdd03aec3e5d220853f01bdbdfe1c1752f7e34cd0a4e20b226bd"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
- "p3-util",
- "tracing-forest",
- "tracing-subscriber",
+ "ppv-lite86",
+ "rand_core 0.9.5",
]
[[package]]
-name = "slop-whir"
-version = "6.2.3"
+name = "rand_core"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99519ca11d444567b54d786f71687f63612a75c58404abde353185f53f38dd5e"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "derive-where",
- "futures",
- "itertools 0.14.0",
- "rand 0.8.5",
- "rayon",
- "serde",
- "slop-algebra",
- "slop-alloc",
- "slop-baby-bear",
- "slop-basefold",
- "slop-challenger",
- "slop-commit",
- "slop-dft",
- "slop-jagged",
- "slop-koala-bear",
- "slop-matrix",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-stacked",
- "slop-tensor",
- "slop-utils",
- "thiserror 1.0.69",
+ "getrandom 0.2.17",
]
[[package]]
-name = "smallvec"
-version = "1.15.1"
+name = "rand_core"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
- "serde",
+ "getrandom 0.3.4",
]
[[package]]
-name = "snowbridge-amcl"
-version = "1.0.2"
+name = "redox_syscall"
+version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460a9ed63cdf03c1b9847e8a12a5f5ba19c4efd5869e4a737e05be25d7c427e5"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "parity-scale-codec",
- "scale-info",
+ "bitflags",
]
[[package]]
-name = "socket2"
-version = "0.5.10"
+name = "regex-automata"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
- "libc",
- "windows-sys 0.52.0",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
]
[[package]]
-name = "socket2"
-version = "0.6.3"
+name = "regex-syntax"
+version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
-dependencies = [
- "libc",
- "windows-sys 0.61.2",
-]
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
-name = "sp1-build"
-version = "6.2.3"
+name = "reqwest"
+version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bb06355982a703c0839b6ca1dc69a50d56a3c85e55d0badc8c3b18ebe8c9fa6"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
- "anyhow",
- "cargo_metadata",
- "chrono",
- "clap",
- "dirs",
- "sp1-primitives",
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots 1.0.6",
]
[[package]]
-name = "sp1-core-executor"
-version = "6.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58d3af539d746d5312346d33899d297232351ca5aed1668f16d5a5a0ee0a3ab3"
+name = "rhi"
+version = "0.1.0"
dependencies = [
- "bincode",
- "bytemuck",
- "cfg-if",
+ "anyhow",
"clap",
- "deepsize2",
- "elf",
- "enum-map",
- "eyre",
- "hashbrown 0.14.5",
- "hex",
- "itertools 0.14.0",
- "memmap2",
- "num",
- "object",
- "rrs-succinct",
+ "radroots_core",
+ "radroots_event",
+ "radroots_event_codec",
+ "radroots_identity",
+ "radroots_log",
+ "radroots_nostr",
+ "radroots_runtime",
+ "radroots_runtime_paths",
+ "radroots_trade",
"serde",
- "serde_arrays",
"serde_json",
- "slop-air",
- "slop-algebra",
- "slop-maybe-rayon",
- "slop-symmetric",
- "sp1-curves",
- "sp1-hypercube",
- "sp1-jit",
- "sp1-primitives",
- "strum",
- "subenum",
- "thiserror 1.0.69",
- "tiny-keccak",
+ "sha2",
+ "tempfile",
+ "thiserror 2.0.18",
+ "tokio",
+ "toml",
"tracing",
- "typenum",
- "vec_map",
]
[[package]]
-name = "sp1-core-executor-runner"
-version = "6.2.3"
+name = "ring"
+version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b3b61a7c5c891a8c18ac31deccca3914d59132f8656d6d7db09c453ee5ed6f"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
- "base64 0.22.1",
- "bincode",
- "cargo_metadata",
- "hashbrown 0.14.5",
- "hex",
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
"libc",
- "sha2",
- "sp1-core-executor",
- "sp1-core-executor-runner-binary",
- "sp1-jit",
- "sp1-primitives",
- "sysinfo",
- "tracing",
- "uuid",
+ "untrusted",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "sp1-core-executor-runner-binary"
-version = "6.2.3"
+name = "ron"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b0c810b99e4fca0b0d0e791a2a7d6b080aad6ffb6cf950e633409eb2723cba3"
+checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
- "bincode",
- "crash-handler",
- "libc",
+ "base64 0.21.7",
+ "bitflags",
"serde",
- "sp1-core-executor",
- "sp1-jit",
- "tracing-subscriber",
+ "serde_derive",
]
[[package]]
-name = "sp1-core-machine"
-version = "6.2.3"
+name = "rust-ini"
+version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29b3851384225aae5588b1fca3c9c8f67001187d53abff21709a7cf8f6098f08"
+checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
dependencies = [
- "bincode",
"cfg-if",
- "enum-map",
- "futures",
- "generic-array 1.1.0",
- "hashbrown 0.14.5",
- "itertools 0.14.0",
- "num",
- "num_cpus",
- "rayon",
- "rayon-scan",
- "rrs-succinct",
- "serde",
- "serde_json",
- "slop-air",
- "slop-algebra",
- "slop-challenger",
- "slop-futures",
- "slop-keccak-air",
- "slop-matrix",
- "slop-maybe-rayon",
- "slop-uni-stark",
- "snowbridge-amcl",
- "sp1-core-executor",
- "sp1-core-executor-runner",
- "sp1-curves",
- "sp1-derive",
- "sp1-hypercube",
- "sp1-jit",
- "sp1-primitives",
- "static_assertions",
- "struct-reflection",
- "strum",
- "sysinfo",
- "tempfile",
- "thiserror 1.0.69",
- "tokio",
- "tracing",
- "tracing-forest",
- "tracing-subscriber",
- "typenum",
+ "ordered-multimap",
]
[[package]]
-name = "sp1-cuda"
-version = "6.2.3"
+name = "rust_decimal"
+version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "468c0e82c980535eacdc57d2969f087e56002cd764809596e2f8e513c74abff9"
+checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0"
dependencies = [
- "bincode",
- "bytes",
- "reqwest",
+ "arrayvec",
+ "num-traits",
"serde",
- "serde_json",
- "sp1-core-executor",
- "sp1-core-machine",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-prover",
- "sp1-prover-types",
- "thiserror 1.0.69",
- "tokio",
- "tracing",
]
[[package]]
-name = "sp1-curves"
-version = "6.2.3"
+name = "rust_decimal_macros"
+version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bd90718c62dc06a42b9a47308ca914bb6d3f04f9265c77cb76bc135929cd59d"
+checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519"
dependencies = [
- "cfg-if",
- "dashu",
- "elliptic-curve",
- "generic-array 1.1.0",
- "itertools 0.14.0",
- "k256",
- "num",
- "p256",
- "serde",
- "slop-algebra",
- "snowbridge-amcl",
- "sp1-primitives",
- "typenum",
+ "quote",
+ "syn",
]
[[package]]
-name = "sp1-derive"
-version = "6.2.3"
+name = "rustc-hash"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecac7ec9b0d2a12f486486a62b2805257e7ffcced51dd39d25fcefaa133adaf6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
-name = "sp1-hypercube"
-version = "6.2.3"
+name = "rustix"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8449d118a6fe1532899da955c30247c413c90e8c78b06a452f3a40ff6432299e"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "arrayref",
- "deepsize2",
- "derive-where",
- "futures",
- "hashbrown 0.14.5",
- "itertools 0.14.0",
- "num-bigint 0.4.6",
- "num-traits",
- "num_cpus",
- "rayon",
- "rayon-scan",
- "serde",
- "slop-air",
- "slop-algebra",
- "slop-alloc",
- "slop-basefold",
- "slop-basefold-prover",
- "slop-bn254",
- "slop-challenger",
- "slop-commit",
- "slop-futures",
- "slop-jagged",
- "slop-koala-bear",
- "slop-matrix",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-poseidon2",
- "slop-stacked",
- "slop-sumcheck",
- "slop-symmetric",
- "slop-tensor",
- "slop-uni-stark",
- "slop-whir",
- "sp1-derive",
- "sp1-primitives",
- "struct-reflection",
- "strum",
- "thiserror 1.0.69",
- "thousands",
- "tokio",
- "tracing",
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "sp1-jit"
-version = "6.2.3"
+name = "rustls"
+version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9355cbdc9d7183d8ab244b695ae7630afb559c6b2bd466bc89a2f3e334b2047a"
+checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
- "dynasmrt",
- "hashbrown 0.14.5",
- "libc",
- "memfd",
- "memmap2",
- "serde",
- "sp1-primitives",
- "tracing",
- "uuid",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
]
[[package]]
-name = "sp1-primitives"
-version = "6.2.3"
+name = "rustls-pki-types"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f20c3dd3131b1285420ca25a37f507915ab609887d6dda73973396a0faa719c1"
+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
- "bincode",
- "blake3",
- "elf",
- "hex",
- "itertools 0.14.0",
- "lazy_static",
- "num-bigint 0.4.6",
- "serde",
- "sha2",
- "slop-algebra",
- "slop-bn254",
- "slop-challenger",
- "slop-koala-bear",
- "slop-poseidon2",
- "slop-primitives",
- "slop-symmetric",
+ "web-time",
+ "zeroize",
]
[[package]]
-name = "sp1-prover"
-version = "6.2.3"
+name = "rustls-webpki"
+version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72c3cb0201a41e9da5372b2b437af07ee3ccd6dca99a69ece4213af40d9436b7"
+checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
- "anyhow",
- "bincode",
- "clap",
- "dirs",
- "either",
- "enum-map",
- "eyre",
- "futures",
- "hashbrown 0.14.5",
- "hex",
- "indicatif",
- "itertools 0.14.0",
- "lru 0.12.5",
- "mti",
- "num-bigint 0.4.6",
- "opentelemetry",
- "pin-project",
- "rand 0.8.5",
- "reqwest",
- "serde",
- "serde_json",
- "serial_test",
- "sha2",
- "slop-air",
- "slop-algebra",
- "slop-basefold",
- "slop-bn254",
- "slop-challenger",
- "slop-futures",
- "slop-jagged",
- "slop-multilinear",
- "slop-stacked",
- "slop-symmetric",
- "sp1-core-executor",
- "sp1-core-executor-runner",
- "sp1-core-machine",
- "sp1-derive",
- "sp1-hypercube",
- "sp1-jit",
- "sp1-primitives",
- "sp1-prover-types",
- "sp1-recursion-circuit",
- "sp1-recursion-compiler",
- "sp1-recursion-executor",
- "sp1-recursion-gnark-ffi",
- "sp1-recursion-machine",
- "sp1-verifier",
- "static_assertions",
- "sysinfo",
- "tempfile",
- "thiserror 1.0.69",
- "tokio",
- "tonic",
- "tracing",
- "tracing-appender",
- "tracing-subscriber",
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
]
[[package]]
-name = "sp1-prover-types"
-version = "6.2.3"
+name = "rustversion"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b289303f43e7ae1d07b528f34e78ca088c61dea13a3458f63a6570da11e32e5"
-dependencies = [
- "anyhow",
- "async-scoped",
- "bincode",
- "chrono",
- "futures-util",
- "hashbrown 0.14.5",
- "mti",
- "prost",
- "serde",
- "sp1-core-machine",
- "sp1-hypercube",
- "sp1-primitives",
- "tokio",
- "tonic",
- "tonic-build",
- "tracing",
-]
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
-name = "sp1-recursion-circuit"
-version = "6.2.3"
+name = "ryu"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19d9ce031605bc111474c11e7841536f3469311253791ef326ee16427e10178e"
-dependencies = [
- "bincode",
- "itertools 0.14.0",
- "rand 0.8.5",
- "rayon",
- "serde",
- "slop-air",
- "slop-algebra",
- "slop-alloc",
- "slop-basefold",
- "slop-basefold-prover",
- "slop-bn254",
- "slop-challenger",
- "slop-commit",
- "slop-jagged",
- "slop-koala-bear",
- "slop-matrix",
- "slop-merkle-tree",
- "slop-multilinear",
- "slop-stacked",
- "slop-sumcheck",
- "slop-symmetric",
- "slop-tensor",
- "slop-whir",
- "sp1-core-executor",
- "sp1-core-machine",
- "sp1-derive",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-recursion-compiler",
- "sp1-recursion-executor",
- "sp1-recursion-machine",
- "tracing",
-]
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
-name = "sp1-recursion-compiler"
-version = "6.2.3"
+name = "salsa20"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "993b5dcde57622009857883637177946254f3cf00290cc4b998b54a76ffab7c0"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
- "backtrace",
- "cfg-if",
- "itertools 0.14.0",
- "serde",
- "slop-algebra",
- "slop-bn254",
- "slop-symmetric",
- "sp1-core-machine",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-recursion-executor",
- "tracing",
- "vec_map",
+ "cipher",
]
[[package]]
-name = "sp1-recursion-executor"
-version = "6.2.3"
+name = "scopeguard"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "983d18b6e0e641c8173b250c234fe355c3a9ba5b7485d3af5a54ddb7ab51e814"
-dependencies = [
- "backtrace",
- "cfg-if",
- "hashbrown 0.14.5",
- "itertools 0.14.0",
- "range-set-blaze",
- "serde",
- "slop-algebra",
- "slop-maybe-rayon",
- "slop-poseidon2",
- "slop-symmetric",
- "smallvec",
- "sp1-derive",
- "sp1-hypercube",
- "static_assertions",
- "thiserror 1.0.69",
- "tracing",
-]
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
-name = "sp1-recursion-gnark-ffi"
-version = "6.2.3"
+name = "scrypt"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43cb9411bdf57706fa6b342c60534c47c6a066aeb5129e89d8d39f6fb37d12e5"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
- "anyhow",
- "bincode",
- "cfg-if",
- "hex",
- "num-bigint 0.4.6",
- "serde",
- "serde_json",
+ "password-hash",
+ "pbkdf2",
+ "salsa20",
"sha2",
- "slop-algebra",
- "slop-symmetric",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-recursion-compiler",
- "sp1-verifier",
- "tempfile",
- "tracing",
]
[[package]]
-name = "sp1-recursion-machine"
-version = "6.2.3"
+name = "secp256k1"
+version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01ab820757fa3783a0230efd48a509fb64b8e7fbaf1d257211dae0c66df79002"
+checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
- "itertools 0.14.0",
"rand 0.8.5",
- "slop-air",
- "slop-algebra",
- "slop-basefold",
- "slop-matrix",
- "slop-maybe-rayon",
- "slop-symmetric",
- "sp1-derive",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-recursion-executor",
- "strum",
- "tracing",
+ "secp256k1-sys",
+ "serde",
]
[[package]]
-name = "sp1-sdk"
-version = "6.2.3"
+name = "secp256k1-sys"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "321a52e7b41aef0eb04e880bc2625f9d150553a04be7607756ac5d494850d037"
+checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
dependencies = [
- "anyhow",
- "async-trait",
- "bincode",
- "cfg-if",
- "dirs",
- "eventsource-stream",
- "futures",
- "hex",
- "indicatif",
- "itertools 0.14.0",
- "k256",
- "num-bigint 0.4.6",
- "serde",
- "sha2",
- "sp1-build",
- "sp1-core-executor",
- "sp1-core-executor-runner",
- "sp1-core-machine",
- "sp1-cuda",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-prover",
- "sp1-prover-types",
- "sp1-recursion-executor",
- "sp1-verifier",
- "strum",
- "tempfile",
- "thiserror 1.0.69",
- "tokio",
- "tracing",
+ "cc",
]
[[package]]
-name = "sp1-verifier"
-version = "6.2.3"
+name = "semver"
+version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44e7a8627fcbfd89ec21932ae33e3a5b9adc4ef844c0e295ef36ee43f0fbf442"
-dependencies = [
- "bincode",
- "blake3",
- "cfg-if",
- "dirs",
- "hex",
- "lazy_static",
- "serde",
- "sha2",
- "slop-algebra",
- "slop-challenger",
- "slop-primitives",
- "slop-symmetric",
- "sp1-hypercube",
- "sp1-primitives",
- "sp1-recursion-executor",
- "sp1-recursion-machine",
- "strum",
- "substrate-bn-succinct-rs",
- "thiserror 2.0.18",
-]
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
-name = "spin"
-version = "0.9.8"
+name = "serde"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
- "lock_api",
+ "serde_core",
+ "serde_derive",
]
[[package]]
-name = "spki"
-version = "0.7.3"
+name = "serde_core"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
- "base64ct",
- "der",
+ "serde_derive",
]
[[package]]
-name = "sqlx"
-version = "0.9.0"
+name = "serde_derive"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
- "sqlx-core",
- "sqlx-macros",
- "sqlx-sqlite",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
-name = "sqlx-core"
-version = "0.9.0"
+name = "serde_json"
+version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
- "base64 0.22.1",
- "bytes",
- "cfg-if",
- "crc",
- "crossbeam-queue",
- "either",
- "event-listener",
- "futures-core",
- "futures-intrusive",
- "futures-io",
- "futures-util",
- "hashbrown 0.16.1",
- "hashlink 0.11.1",
- "indexmap 2.13.0",
- "log",
+ "itoa",
"memchr",
- "percent-encoding",
"serde",
- "sha2",
- "smallvec",
- "thiserror 2.0.18",
- "tokio",
- "tokio-stream",
- "tracing",
- "url",
-]
-
-[[package]]
-name = "sqlx-macros"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf"
-dependencies = [
- "proc-macro2",
- "quote",
- "sqlx-core",
- "sqlx-macros-core",
- "syn 2.0.117",
+ "serde_core",
+ "zmij",
]
[[package]]
-name = "sqlx-macros-core"
-version = "0.9.0"
+name = "serde_spanned"
+version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
- "cfg-if",
- "dotenvy",
- "either",
- "heck",
- "hex",
- "proc-macro2",
- "quote",
"serde",
- "serde_json",
- "sha2",
- "sqlx-core",
- "sqlx-sqlite",
- "syn 2.0.117",
- "tokio",
- "url",
]
[[package]]
-name = "sqlx-sqlite"
-version = "0.9.0"
+name = "serde_urlencoded"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
- "atoi",
- "flume",
"form_urlencoded",
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-intrusive",
- "futures-util",
- "libsqlite3-sys",
- "log",
- "percent-encoding",
+ "itoa",
+ "ryu",
"serde",
- "sqlx-core",
- "thiserror 2.0.18",
- "tracing",
- "url",
]
[[package]]
-name = "stable_deref_trait"
-version = "1.2.1"
+name = "sha1"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
[[package]]
-name = "static_assertions"
-version = "1.1.0"
+name = "sha2"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
[[package]]
-name = "strength_reduce"
-version = "0.2.4"
+name = "sharded-slab"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
[[package]]
-name = "strsim"
-version = "0.11.1"
+name = "shlex"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
-name = "struct-reflection"
-version = "0.1.0"
+name = "signal-hook-registry"
+version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "701b671d1ad68e250e05718f95dae3014a17f4e69cbe51842531c30495ff3301"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
- "struct-reflection-derive",
+ "errno",
+ "libc",
]
[[package]]
-name = "struct-reflection-derive"
-version = "0.1.0"
+name = "slab"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ab74230a0592602e361bd63c645413fa8cbe4500d10274e849179e5c72548f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
-name = "strum"
-version = "0.27.2"
+name = "smallvec"
+version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
-dependencies = [
- "strum_macros",
-]
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
-name = "strum_macros"
-version = "0.27.2"
+name = "socket2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "libc",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "subenum"
-version = "1.2.0"
+name = "stable_deref_trait"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eee3fb942ed39f3971438fcc7e05e20717e599e14c5c7cb50edd0df2a44b274"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
-name = "substrate-bn-succinct-rs"
-version = "0.6.0"
+name = "strsim"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a241fd7c1016fb8ad30fcf5a20986c0c4538e8f15a1b41a1761516299e377ec1"
-dependencies = [
- "bytemuck",
- "byteorder",
- "cfg-if",
- "crunchy",
- "lazy_static",
- "num-bigint 0.4.6",
- "rand 0.8.5",
- "rustc-hex",
-]
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
@@ -5515,17 +2203,6 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
@@ -5540,41 +2217,20 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
-dependencies = [
- "futures-core",
-]
-
-[[package]]
-name = "synstructure"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "sysinfo"
-version = "0.30.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
-dependencies = [
- "cfg-if",
- "core-foundation-sys",
- "libc",
- "ntapi",
- "once_cell",
- "rayon",
- "windows",
+dependencies = [
+ "futures-core",
]
[[package]]
-name = "tap"
-version = "1.0.1"
+name = "synstructure"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "tempfile"
@@ -5615,7 +2271,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -5626,16 +2282,10 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
-name = "thousands"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
-
-[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5721,7 +2371,7 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "socket2 0.6.3",
+ "socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
@@ -5734,7 +2384,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -5760,17 +2410,6 @@ dependencies = [
]
[[package]]
-name = "tokio-stream"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
-dependencies = [
- "futures-core",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
name = "tokio-tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5787,19 +2426,6 @@ dependencies = [
]
[[package]]
-name = "tokio-util"
-version = "0.7.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
-dependencies = [
- "bytes",
- "futures-core",
- "futures-sink",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5807,8 +2433,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
- "toml_datetime 0.6.11",
- "toml_edit 0.22.27",
+ "toml_datetime",
+ "toml_edit",
]
[[package]]
@@ -5821,58 +2447,17 @@ dependencies = [
]
[[package]]
-name = "toml_datetime"
-version = "1.1.1+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.19.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
-dependencies = [
- "indexmap 2.13.0",
- "toml_datetime 0.6.11",
- "winnow 0.5.40",
-]
-
-[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
- "indexmap 2.13.0",
+ "indexmap",
"serde",
"serde_spanned",
- "toml_datetime 0.6.11",
+ "toml_datetime",
"toml_write",
- "winnow 0.7.15",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.25.11+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
-dependencies = [
- "indexmap 2.13.0",
- "toml_datetime 1.1.1+spec-1.1.0",
- "toml_parser",
- "winnow 1.0.3",
-]
-
-[[package]]
-name = "toml_parser"
-version = "1.1.2+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
-dependencies = [
- "winnow 1.0.3",
+ "winnow",
]
[[package]]
@@ -5882,72 +2467,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
-name = "tonic"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
-dependencies = [
- "async-stream",
- "async-trait",
- "axum",
- "base64 0.22.1",
- "bytes",
- "h2",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-timeout",
- "hyper-util",
- "percent-encoding",
- "pin-project",
- "prost",
- "rustls-pemfile",
- "socket2 0.5.10",
- "tokio",
- "tokio-rustls",
- "tokio-stream",
- "tower 0.4.13",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "tonic-build"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
-dependencies = [
- "prettyplease",
- "proc-macro2",
- "prost-build",
- "prost-types",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "tower"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
-dependencies = [
- "futures-core",
- "futures-util",
- "indexmap 1.9.3",
- "pin-project",
- "pin-project-lite",
- "rand 0.8.5",
- "slab",
- "tokio",
- "tokio-util",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5975,7 +2494,7 @@ dependencies = [
"http-body",
"iri-string",
"pin-project-lite",
- "tower 0.5.3",
+ "tower",
"tower-layer",
"tower-service",
]
@@ -5998,7 +2517,6 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
- "log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -6024,7 +2542,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -6038,19 +2556,6 @@ dependencies = [
]
[[package]]
-name = "tracing-forest"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f"
-dependencies = [
- "ansi_term",
- "smallvec",
- "thiserror 1.0.69",
- "tracing",
- "tracing-subscriber",
-]
-
-[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6080,16 +2585,6 @@ dependencies = [
]
[[package]]
-name = "transpose"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
-dependencies = [
- "num-integer",
- "strength_reduce",
-]
-
-[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6115,27 +2610,6 @@ dependencies = [
]
[[package]]
-name = "twox-hash"
-version = "2.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
-
-[[package]]
-name = "typeid_prefix"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9da1387307fdee46aa441e4f08a1b491e659fcac1aca9cd71f2c624a0de5d1b"
-
-[[package]]
-name = "typeid_suffix"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77b55e96f110c6db5d1a2f24072552537f0091dc90cebeaa679540bac93e7405"
-dependencies = [
- "uuid",
-]
-
-[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6169,12 +2643,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
-name = "unicode-width"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
-
-[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6228,41 +2696,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
-name = "uuid"
-version = "1.23.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
-dependencies = [
- "atomic",
- "getrandom 0.4.2",
- "js-sys",
- "md-5",
- "sha1_smol",
- "wasm-bindgen",
-]
-
-[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
-[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
-dependencies = [
- "serde",
-]
-
-[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6347,7 +2786,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
"wasm-bindgen-shared",
]
@@ -6377,25 +2816,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
- "indexmap 2.13.0",
+ "indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
-name = "wasm-streams"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
-dependencies = [
- "futures-util",
- "js-sys",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
-[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6403,7 +2829,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
- "indexmap 2.13.0",
+ "indexmap",
"semver",
]
@@ -6446,47 +2872,6 @@ dependencies = [
]
[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
-dependencies = [
- "windows-core 0.52.0",
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6507,7 +2892,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -6518,7 +2903,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -6547,15 +2932,6 @@ dependencies = [
[[package]]
name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
-[[package]]
-name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
@@ -6565,15 +2941,6 @@ dependencies = [
[[package]]
name = "windows-sys"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
@@ -6592,21 +2959,6 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
-[[package]]
-name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -6640,12 +2992,6 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
@@ -6658,12 +3004,6 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
-[[package]]
-name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
@@ -6676,12 +3016,6 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
-[[package]]
-name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -6706,12 +3040,6 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
-[[package]]
-name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
@@ -6724,12 +3052,6 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
-[[package]]
-name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
@@ -6742,12 +3064,6 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
@@ -6760,12 +3076,6 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -6778,15 +3088,6 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
-version = "0.5.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
@@ -6795,15 +3096,6 @@ dependencies = [
]
[[package]]
-name = "winnow"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
-dependencies = [
- "memchr",
-]
-
-[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6831,9 +3123,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
- "indexmap 2.13.0",
+ "indexmap",
"prettyplease",
- "syn 2.0.117",
+ "syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -6849,7 +3141,7 @@ dependencies = [
"prettyplease",
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -6862,7 +3154,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
- "indexmap 2.13.0",
+ "indexmap",
"log",
"serde",
"serde_derive",
@@ -6881,7 +3173,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
- "indexmap 2.13.0",
+ "indexmap",
"log",
"semver",
"serde",
@@ -6898,15 +3190,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
-name = "wyz"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
-dependencies = [
- "tap",
-]
-
-[[package]]
name = "yaml-rust2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6914,7 +3197,7 @@ checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
dependencies = [
"arraydeque",
"encoding_rs",
- "hashlink 0.8.4",
+ "hashlink",
]
[[package]]
@@ -6936,7 +3219,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
"synstructure",
]
@@ -6957,7 +3240,7 @@ checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
@@ -6977,7 +3260,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
"synstructure",
]
@@ -7017,7 +3300,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.117",
+ "syn",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2024"
authors = ["Radroots Authors"]
rust-version = "1.97.0"
license = "AGPL-3.0-or-later"
-description = "Radroots trade validation and receipt worker"
+description = "Radroots trade agreement attestation worker"
[workspace]
resolver = "2"
@@ -19,21 +19,16 @@ radroots_log = { path = "../lib/crates/log" }
radroots_nostr = { path = "../lib/crates/nostr" }
radroots_runtime = { path = "../lib/crates/runtime" }
radroots_runtime_paths = { path = "../lib/crates/runtime_paths" }
-radroots_trade_sp1_guest = { path = "../lib/crates/trade_sp1_guest" }
-radroots_trade_sp1_host = { path = "../lib/crates/trade_sp1_host" }
radroots_trade = { path = "../lib/crates/trade" }
[features]
default = []
-sp1_verify = ["radroots_trade_sp1_host/sp1_verify"]
-sp1_proving = ["sp1_verify", "radroots_trade_sp1_host/sp1_proving"]
-sp1_cuda_proving = ["sp1_proving", "radroots_trade_sp1_host/sp1_cuda"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
[dependencies]
-radroots_core = { workspace = true, features = ["std", "serde"] }
+radroots_core = { workspace = true, features = ["std", "serde"] }
radroots_event = { workspace = true, features = ["serde"] }
radroots_event_codec = { workspace = true, features = ["nostr"] }
radroots_identity = { workspace = true }
@@ -41,24 +36,17 @@ radroots_log = { workspace = true }
radroots_nostr = { workspace = true, features = ["client", "codec", "events", "http"] }
radroots_runtime = { workspace = true, features = ["cli"] }
radroots_runtime_paths = { workspace = true }
-radroots_trade_sp1_guest = { workspace = true }
-radroots_trade_sp1_host = { workspace = true }
radroots_trade = { workspace = true }
anyhow = { version = "1" }
clap = { version = "4", features = ["derive"] }
serde = { version = "1", default-features = false }
serde_json = { version = "1", default-features = false }
-reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
sha2 = { version = "0.10" }
-sqlx = { version = "0.9.0", default-features = false, features = ["runtime-tokio", "sqlite-bundled"] }
tokio = { version = "1", features = ["full"] }
thiserror = { version = "2" }
toml = { version = "0.8" }
tracing = { version = "0.1" }
-[patch.crates-io]
-libsqlite3-sys = { path = "../lib/crates/libsqlite3_sys_3_53_3" }
-
[dev-dependencies]
tempfile = { version = "3" }
diff --git a/src/cli.rs b/src/cli.rs
@@ -18,25 +18,13 @@ pub struct Args {
#[derive(clap::Subcommand, Debug, Clone)]
pub enum Command {
#[command(
- name = "proof-smoke",
- about = "Run a provider-neutral proof smoke request"
+ name = "attestation-smoke",
+ about = "Run a release-product agreement attestation smoke request"
)]
- ProofSmoke {
+ AttestationSmoke {
#[arg(long)]
input: Option<PathBuf>,
#[arg(long)]
output: Option<PathBuf>,
},
- #[command(
- name = "remote-prove",
- about = "Run a provider-neutral remote proof request"
- )]
- RemoteProve {
- #[arg(long)]
- input: Option<PathBuf>,
- #[arg(long)]
- output: Option<PathBuf>,
- #[arg(long, default_value = "cpu", value_parser = ["cpu", "cuda"])]
- proof_engine: String,
- },
}
diff --git a/src/config.rs b/src/config.rs
@@ -4,7 +4,7 @@ use radroots_runtime::{BackoffConfig, RadrootsNostrServiceConfig};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
-use crate::features::trade_validation_receipt::TradeValidationReceiptProverPolicy;
+use crate::features::trade_agreement_attestation::TradeAgreementAttestationPolicy;
use crate::paths::{
RhiRuntimePaths, default_subscriber_state_path_for_process, resolve_runtime_paths_with_resolver,
};
@@ -101,7 +101,7 @@ pub struct Configuration {
#[serde(default)]
pub subscriber: SubscriberConfig,
#[serde(default)]
- pub trade_validation_receipt: TradeValidationReceiptProverPolicy,
+ pub trade_agreement_attestation: TradeAgreementAttestationPolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -194,7 +194,7 @@ struct RawSettings {
#[serde(default)]
pub subscriber: RawSubscriberConfig,
#[serde(default)]
- pub trade_validation_receipt: TradeValidationReceiptProverPolicy,
+ pub trade_agreement_attestation: TradeAgreementAttestationPolicy,
}
impl RawSettings {
@@ -213,7 +213,7 @@ impl RawSettings {
service,
logging,
subscriber: self.subscriber.into_subscriber_config(paths),
- trade_validation_receipt: self.trade_validation_receipt,
+ trade_agreement_attestation: self.trade_agreement_attestation,
},
})
}
@@ -237,7 +237,7 @@ fn load_settings_from_path_with_resolver(
let settings: RawSettings =
toml::from_str(&raw).with_context(|| format!("parse configuration {}", path.display()))?;
let settings = settings.into_settings(&paths)?;
- settings.config.trade_validation_receipt.validate()?;
+ settings.config.trade_agreement_attestation.validate()?;
Ok(settings)
}
@@ -254,7 +254,7 @@ pub fn load_settings_from_path(path: &Path) -> Result<Settings> {
#[cfg(test)]
mod tests {
use super::load_settings_from_path_with_resolver;
- use crate::features::trade_validation_receipt::TradeValidationReceiptProverBackend;
+ use crate::features::trade_agreement_attestation::TradeAgreementAttestationBackend;
use crate::paths::{
default_subscriber_state_path_for_process, resolve_runtime_paths_with_resolver,
runtime_contract_with_resolver,
@@ -263,7 +263,6 @@ mod tests {
RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver,
RadrootsPlatform, RadrootsRuntimeNamespace,
};
- use radroots_trade_sp1_host::RadrootsSp1TradeProofMode;
use std::path::PathBuf;
fn linux_resolver() -> RadrootsPathResolver {
@@ -328,7 +327,9 @@ mod tests {
);
assert_eq!(
paths.subscriber_state_path,
- PathBuf::from("/home/treesap/.radroots/data/workers/rhi/trade-listing/state.json")
+ PathBuf::from(
+ "/home/treesap/.radroots/data/workers/rhi/trade-agreement-attestation/state.json"
+ )
);
}
@@ -354,7 +355,7 @@ mod tests {
);
assert_eq!(
paths.subscriber_state_path,
- PathBuf::from("/var/lib/radroots/workers/rhi/trade-listing/state.json")
+ PathBuf::from("/var/lib/radroots/workers/rhi/trade-agreement-attestation/state.json")
);
}
@@ -379,7 +380,7 @@ mod tests {
);
assert_eq!(
paths.subscriber_state_path,
- repo_local_root.join("data/workers/rhi/trade-listing/state.json")
+ repo_local_root.join("data/workers/rhi/trade-agreement-attestation/state.json")
);
}
@@ -434,22 +435,20 @@ replay_overlap_secs = 45
);
assert_eq!(
settings.config.subscriber.state.path,
- PathBuf::from("/home/treesap/.radroots/data/workers/rhi/trade-listing/state.json")
+ PathBuf::from(
+ "/home/treesap/.radroots/data/workers/rhi/trade-agreement-attestation/state.json"
+ )
);
assert_eq!(settings.config.subscriber.state.replay_window_secs, 123);
assert_eq!(settings.config.subscriber.state.replay_overlap_secs, 45);
assert_eq!(
- settings.config.trade_validation_receipt.backend,
- TradeValidationReceiptProverBackend::LocalExecute
- );
- assert_eq!(
- settings.config.trade_validation_receipt.proof_mode,
- RadrootsSp1TradeProofMode::None
+ settings.config.trade_agreement_attestation.backend,
+ TradeAgreementAttestationBackend::LocalStatementHash
);
}
#[test]
- fn load_settings_parses_trade_validation_receipt_policy() {
+ fn load_settings_parses_trade_agreement_attestation_policy() {
let temp = tempfile::tempdir().expect("tempdir");
let config_path = temp.path().join("config.toml");
std::fs::write(
@@ -477,11 +476,11 @@ factor = 3
jitter_ms = 5
[subscriber.state]
-path = "state/trade-listing.json"
+path = "state/trade-agreement-attestation.json"
-[trade_validation_receipt]
-backend = "local_execute"
-proof_mode = "none"
+[trade_agreement_attestation]
+backend = "local_statement_hash"
+expected_statement_contract_hash = "0x1111111111111111111111111111111111111111111111111111111111111111"
validator_set_addr = "30381:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:018f3d99-7d35-7c0c-8a0f-7f3b645abcde"
validator_set_event_id = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
"#,
@@ -521,20 +520,24 @@ validator_set_event_id = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
assert_eq!(settings.config.subscriber.backoff.jitter_ms, 5);
assert_eq!(
settings.config.subscriber.state.path,
- PathBuf::from("state/trade-listing.json")
+ PathBuf::from("state/trade-agreement-attestation.json")
);
assert_eq!(
- settings.config.trade_validation_receipt.backend,
- TradeValidationReceiptProverBackend::LocalExecute
+ settings.config.trade_agreement_attestation.backend,
+ TradeAgreementAttestationBackend::LocalStatementHash
);
assert_eq!(
- settings.config.trade_validation_receipt.proof_mode,
- RadrootsSp1TradeProofMode::None
+ settings
+ .config
+ .trade_agreement_attestation
+ .expected_statement_contract_hash
+ .as_deref(),
+ Some("0x1111111111111111111111111111111111111111111111111111111111111111")
);
assert_eq!(
settings
.config
- .trade_validation_receipt
+ .trade_agreement_attestation
.validator_set_addr
.as_deref(),
Some(
@@ -544,7 +547,7 @@ validator_set_event_id = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
assert_eq!(
settings
.config
- .trade_validation_receipt
+ .trade_agreement_attestation
.validator_set_event_id
.as_deref(),
Some("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
@@ -552,7 +555,7 @@ validator_set_event_id = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
}
#[test]
- fn load_settings_rejects_sp1_identity_hash_without_sp1_mode() {
+ fn load_settings_rejects_invalid_statement_contract_hash() {
let temp = tempfile::tempdir().expect("tempdir");
let config_path = temp.path().join("config.toml");
std::fs::write(
@@ -561,10 +564,9 @@ validator_set_event_id = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
[metadata]
name = "rhi-test"
-[trade_validation_receipt]
-backend = "local_execute"
-proof_mode = "none"
-expected_sp1_program_hash = "0x1111111111111111111111111111111111111111111111111111111111111111"
+[trade_agreement_attestation]
+backend = "local_statement_hash"
+expected_statement_contract_hash = "not-a-hash"
"#,
)
.expect("write config");
@@ -575,16 +577,16 @@ expected_sp1_program_hash = "0x1111111111111111111111111111111111111111111111111
RadrootsPathProfile::InteractiveUser,
None,
)
- .expect_err("sp1 identity without sp1 proof mode must fail");
+ .expect_err("invalid statement contract hash must fail");
let message = format!("{error:#}");
assert!(
- message.contains("SP1 identity constraints require an SP1 proof mode"),
+ message.contains("invalid configured hash field"),
"{message}"
);
}
#[test]
- fn load_settings_rejects_sp1_backend_without_identity_hashes() {
+ fn load_settings_rejects_partial_validator_set_binding() {
let temp = tempfile::tempdir().expect("tempdir");
let config_path = temp.path().join("config.toml");
std::fs::write(
@@ -593,9 +595,9 @@ expected_sp1_program_hash = "0x1111111111111111111111111111111111111111111111111
[metadata]
name = "rhi-test"
-[trade_validation_receipt]
-backend = "local_cpu_prove"
-proof_mode = "core"
+[trade_agreement_attestation]
+backend = "local_statement_hash"
+validator_set_addr = "30381:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:018f3d99-7d35-7c0c-8a0f-7f3b645abcde"
"#,
)
.expect("write config");
@@ -606,12 +608,10 @@ proof_mode = "core"
RadrootsPathProfile::InteractiveUser,
None,
)
- .expect_err("sp1 backend without identity hashes must fail");
+ .expect_err("partial validator-set binding must fail");
let message = format!("{error:#}");
assert!(
- message.contains(
- "trade validation receipt policy requires SP1 program and verifying-key hashes"
- ),
+ message.contains("attestation policy is missing validator_set_event_id"),
"{message}"
);
}
@@ -654,14 +654,13 @@ replay_window_secs = 10
"unknown field `config`",
),
(
- "config-trade-validation-receipt",
+ "config-trade-agreement-attestation",
r#"
[metadata]
name = "rhi-test"
-[config.trade_validation_receipt]
-backend = "local_execute"
-proof_mode = "none"
+[config.trade_agreement_attestation]
+backend = "local_statement_hash"
"#,
"unknown field `config`",
),
@@ -685,7 +684,7 @@ proof_mode = "none"
fn default_subscriber_state_path_is_canonical_for_current_process() {
let path =
default_subscriber_state_path_for_process().expect("resolve current process defaults");
- assert!(path.ends_with("trade-listing/state.json"));
+ assert!(path.ends_with("trade-agreement-attestation/state.json"));
}
#[test]
@@ -740,7 +739,9 @@ proof_mode = "none"
);
assert_eq!(
contract.canonical_subscriber_state_path,
- PathBuf::from("/home/treesap/.radroots/data/workers/rhi/trade-listing/state.json")
+ PathBuf::from(
+ "/home/treesap/.radroots/data/workers/rhi/trade-agreement-attestation/state.json"
+ )
);
}
@@ -767,7 +768,7 @@ proof_mode = "none"
);
assert_eq!(
contract.canonical_subscriber_state_path,
- PathBuf::from("/var/lib/radroots/workers/rhi/trade-listing/state.json")
+ PathBuf::from("/var/lib/radroots/workers/rhi/trade-agreement-attestation/state.json")
);
}
}
diff --git a/src/features/mod.rs b/src/features/mod.rs
@@ -1,2 +1 @@
-pub mod trade_listing;
-pub mod trade_validation_receipt;
+pub mod trade_agreement_attestation;
diff --git a/src/features/trade_agreement_attestation.rs b/src/features/trade_agreement_attestation.rs
@@ -0,0 +1,980 @@
+#![forbid(unsafe_code)]
+#![cfg_attr(coverage_nightly, coverage(off))]
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::convert::TryFrom;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use std::time::Duration;
+
+use anyhow::{Result, anyhow};
+use radroots_event::ids::{
+ RadrootsAddressableCoordinate, RadrootsEventId, RadrootsPublicKey, RadrootsTradeId,
+ RadrootsTradeMutationId,
+};
+use radroots_event::kinds::{TRADE_MUTATION_EVENT_KINDS, is_trade_mutation_event_kind};
+use radroots_event::trade::{
+ RadrootsTradeMutationEnvelopeV1, canonical_jcs_value, trade_mutation_from_canonical_content,
+};
+use radroots_nostr::prelude::{
+ RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrFilter, RadrootsNostrKeys,
+ RadrootsNostrKind, RadrootsNostrRelayPoolNotification, RadrootsNostrSubscriptionId,
+ RadrootsNostrTimestamp,
+};
+use radroots_trade::workflow::{
+ RADROOTS_TRADE_REDUCER_CONTRACT_ID, RADROOTS_TRADE_REDUCER_VERSION,
+ RadrootsTradeAgreementStateV1, RadrootsTradeAttestationResultV1,
+ RadrootsTradeAttestationStateV1, RadrootsTradeEvidenceStateV1, RadrootsTradeMutationRecordV1,
+ RadrootsTradeProjectionV1, RadrootsTradeReductionInputV1, reduce_trade_records,
+};
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+use thiserror::Error;
+use tokio::sync::{Mutex, watch};
+use tokio::time::sleep;
+use tracing::{info, warn};
+
+pub const RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID: &str = "radroots.rhi.agreement_attestation.v1";
+pub const RHI_AGREEMENT_ATTESTATION_STATE_VERSION: u32 = 1;
+pub const RHI_AGREEMENT_ATTESTATION_REPORT_VERSION: u16 = 1;
+pub const RHI_AGREEMENT_ATTESTATION_PROOF_SYSTEM_LOCAL_STATEMENT_HASH: &str =
+ "local_statement_hash";
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum TradeAgreementAttestationBackend {
+ #[default]
+ LocalStatementHash,
+}
+
+impl TradeAgreementAttestationBackend {
+ pub const fn as_str(self) -> &'static str {
+ match self {
+ Self::LocalStatementHash => "local_statement_hash",
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationPolicy {
+ #[serde(default)]
+ pub backend: TradeAgreementAttestationBackend,
+ #[serde(default)]
+ pub validator_set_addr: Option<String>,
+ #[serde(default)]
+ pub validator_set_event_id: Option<String>,
+ #[serde(default)]
+ pub expected_statement_contract_hash: Option<String>,
+}
+
+impl TradeAgreementAttestationPolicy {
+ pub fn validate(&self) -> Result<(), TradeAgreementAttestationError> {
+ validate_optional_hash32(&self.expected_statement_contract_hash)?;
+ match (
+ self.validator_set_addr.as_deref(),
+ self.validator_set_event_id.as_deref(),
+ ) {
+ (Some(addr), Some(event_id)) => {
+ RadrootsAddressableCoordinate::parse(addr).map_err(|_| {
+ TradeAgreementAttestationError::InvalidValidatorSetBinding("validator_set_addr")
+ })?;
+ RadrootsEventId::parse(event_id).map_err(|_| {
+ TradeAgreementAttestationError::InvalidValidatorSetBinding(
+ "validator_set_event_id",
+ )
+ })?;
+ Ok(())
+ }
+ (None, None) => Ok(()),
+ (Some(_), None) => Err(TradeAgreementAttestationError::MissingValidatorSetBinding(
+ "validator_set_event_id",
+ )),
+ (None, Some(_)) => Err(TradeAgreementAttestationError::MissingValidatorSetBinding(
+ "validator_set_addr",
+ )),
+ }
+ }
+
+ fn validator_set_binding(
+ &self,
+ ) -> Result<Option<TradeAgreementAttestationValidatorSetBinding>, TradeAgreementAttestationError>
+ {
+ self.validate()?;
+ match (
+ self.validator_set_addr.as_deref(),
+ self.validator_set_event_id.as_deref(),
+ ) {
+ (Some(addr), Some(event_id)) => {
+ Ok(Some(TradeAgreementAttestationValidatorSetBinding {
+ validator_set_addr: addr.to_owned(),
+ validator_set_event_id: event_id.to_owned(),
+ }))
+ }
+ _ => Ok(None),
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationValidatorSetBinding {
+ pub validator_set_addr: String,
+ pub validator_set_event_id: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationStatementV1 {
+ pub protocol_id: String,
+ pub schema_version: u16,
+ pub reducer_contract_id: String,
+ pub reducer_version: u16,
+ pub trade_id: String,
+ pub claim_mutation_id: String,
+ pub projection_digest: String,
+ pub agreement_state: RadrootsTradeAgreementStateV1,
+ pub attestation_state_before_report: RadrootsTradeAttestationStateV1,
+ pub active_agreement_claim_ids: Vec<String>,
+ pub contested_claim_ids: Vec<String>,
+ pub cancelled_claim_ids: Vec<String>,
+ pub evidence_state: RadrootsTradeEvidenceStateV1,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub validator_set: Option<TradeAgreementAttestationValidatorSetBinding>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationReportV1 {
+ pub report_version: u16,
+ pub attestation_id: String,
+ pub result: RadrootsTradeAttestationResultV1,
+ pub statement: TradeAgreementAttestationStatementV1,
+ pub statement_hash: String,
+ pub proof_system: String,
+ pub proof_identity_hash: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeMutationObservationV1 {
+ pub event_id: String,
+ pub event_kind: u32,
+ pub event_pubkey: String,
+ pub trade_id: String,
+ pub mutation_id: String,
+ pub content: String,
+ pub observed_at_unix_s: u64,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationState {
+ #[serde(default)]
+ seen_event_ids: BTreeSet<String>,
+ #[serde(default)]
+ mutation_events: BTreeMap<String, TradeMutationObservationV1>,
+ #[serde(default)]
+ reports_by_claim: BTreeMap<String, TradeAgreementAttestationReportV1>,
+ last_event_created_at: Option<u32>,
+}
+
+#[derive(Clone, Debug)]
+pub struct TradeAgreementAttestationRuntime {
+ state: Arc<Mutex<TradeAgreementAttestationState>>,
+ config: TradeAgreementAttestationRuntimeConfig,
+ persistence: Option<Arc<TradeAgreementAttestationStatePersistence>>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct TradeAgreementAttestationRuntimeConfig {
+ pub state_path: PathBuf,
+ pub replay_window_secs: u64,
+ pub replay_overlap_secs: u64,
+}
+
+#[derive(Clone, Debug)]
+struct TradeAgreementAttestationStatePersistence {
+ path: PathBuf,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+struct PersistedTradeAgreementAttestationState {
+ version: u32,
+ state: TradeAgreementAttestationState,
+}
+
+#[derive(Debug, Error)]
+pub enum TradeAgreementAttestationError {
+ #[error("event kind not supported")]
+ UnsupportedKind,
+ #[error("invalid event author")]
+ InvalidEventAuthor,
+ #[error("trade mutation is missing mutation_id")]
+ MissingMutationId,
+ #[error("agreement claim is missing")]
+ MissingAgreementClaim,
+ #[error("attestation policy is missing {0}")]
+ MissingValidatorSetBinding(&'static str),
+ #[error("attestation policy has invalid {0}")]
+ InvalidValidatorSetBinding(&'static str),
+ #[error("invalid configured hash field")]
+ InvalidHashField,
+ #[error("invalid signed event")]
+ InvalidSignedEvent,
+ #[error("trade protocol error: {0}")]
+ TradeProtocol(#[from] radroots_event::trade::RadrootsTradeProtocolError),
+ #[error("serde error: {0}")]
+ Serde(#[from] serde_json::Error),
+ #[error("state error: {0}")]
+ State(#[from] TradeAgreementAttestationRuntimeError),
+}
+
+#[derive(Debug, Error)]
+pub enum TradeAgreementAttestationRuntimeError {
+ #[error("state path is invalid: {0}")]
+ InvalidStatePath(PathBuf),
+ #[error("state version {0} is unsupported")]
+ UnsupportedStateVersion(u32),
+ #[error("io error: {0}")]
+ Io(#[from] std::io::Error),
+ #[error("serde error: {0}")]
+ Serde(#[from] serde_json::Error),
+}
+
+impl Default for TradeAgreementAttestationRuntimeConfig {
+ fn default() -> Self {
+ Self {
+ state_path: crate::paths::default_subscriber_state_path_for_process()
+ .expect("resolve canonical rhi agreement-attestation state path"),
+ replay_window_secs: 24 * 60 * 60,
+ replay_overlap_secs: 5 * 60,
+ }
+ }
+}
+
+impl Default for TradeAgreementAttestationRuntime {
+ fn default() -> Self {
+ Self {
+ state: Arc::new(Mutex::new(TradeAgreementAttestationState::default())),
+ config: TradeAgreementAttestationRuntimeConfig::default(),
+ persistence: None,
+ }
+ }
+}
+
+impl TradeAgreementAttestationRuntime {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub async fn load(
+ config: TradeAgreementAttestationRuntimeConfig,
+ ) -> Result<Self, TradeAgreementAttestationRuntimeError> {
+ let persistence = Arc::new(TradeAgreementAttestationStatePersistence::new(
+ config.state_path.clone(),
+ ));
+ let state = persistence.load().await?;
+ Ok(Self {
+ state: Arc::new(Mutex::new(state)),
+ config,
+ persistence: Some(persistence),
+ })
+ }
+
+ pub fn state(&self) -> Arc<Mutex<TradeAgreementAttestationState>> {
+ Arc::clone(&self.state)
+ }
+
+ pub async fn persist(&self) -> Result<(), TradeAgreementAttestationRuntimeError> {
+ let Some(persistence) = &self.persistence else {
+ return Ok(());
+ };
+ let snapshot = self.state.lock().await.clone();
+ persistence.persist(&snapshot).await
+ }
+
+ pub async fn mark_processed_event(
+ &self,
+ created_at: u32,
+ ) -> Result<(), TradeAgreementAttestationRuntimeError> {
+ {
+ let mut state = self.state.lock().await;
+ state.observe_event_created_at(created_at);
+ }
+ self.persist().await
+ }
+
+ pub async fn recovery_filter(&self, kinds: Vec<RadrootsNostrKind>) -> RadrootsNostrFilter {
+ let since = {
+ let state = self.state.lock().await;
+ state.replay_since(
+ RadrootsNostrTimestamp::now().as_secs(),
+ self.config.replay_window_secs,
+ self.config.replay_overlap_secs,
+ )
+ };
+ RadrootsNostrFilter::new()
+ .kinds(kinds)
+ .since(RadrootsNostrTimestamp::from(since))
+ }
+
+ pub async fn reports(&self) -> Vec<TradeAgreementAttestationReportV1> {
+ self.state
+ .lock()
+ .await
+ .reports_by_claim
+ .values()
+ .cloned()
+ .collect()
+ }
+
+ async fn observe_mutation_event(
+ &self,
+ event: &RadrootsNostrEvent,
+ mutation: &RadrootsTradeMutationEnvelopeV1,
+ mutation_id: &RadrootsTradeMutationId,
+ kind: u32,
+ ) -> Result<bool, TradeAgreementAttestationRuntimeError> {
+ let observation = TradeMutationObservationV1 {
+ event_id: event.id.to_hex(),
+ event_kind: kind,
+ event_pubkey: event.pubkey.to_hex(),
+ trade_id: mutation.trade_id.as_str().to_owned(),
+ mutation_id: mutation_id.as_str().to_owned(),
+ content: event.content.clone(),
+ observed_at_unix_s: event.created_at.as_secs(),
+ };
+ let mut state = self.state.lock().await;
+ if !state.seen_event_ids.insert(observation.event_id.clone()) {
+ return Ok(false);
+ }
+ state
+ .mutation_events
+ .insert(observation.mutation_id.clone(), observation);
+ Ok(true)
+ }
+
+ async fn reduce_trade(
+ &self,
+ trade_id: &RadrootsTradeId,
+ ) -> Result<RadrootsTradeProjectionV1, TradeAgreementAttestationError> {
+ let observations = {
+ let state = self.state.lock().await;
+ state
+ .mutation_events
+ .values()
+ .filter(|observation| observation.trade_id == trade_id.as_str())
+ .cloned()
+ .collect::<Vec<_>>()
+ };
+ let mut input = RadrootsTradeReductionInputV1::new(trade_id.clone());
+ input.evidence_state = RadrootsTradeEvidenceStateV1::Complete;
+ input.mutations = observations
+ .iter()
+ .map(|observation| {
+ let mutation = trade_mutation_from_canonical_content(observation.content.as_str())?;
+ let transport_event_id = RadrootsEventId::parse(observation.event_id.as_str())
+ .map(Some)
+ .map_err(|_| TradeAgreementAttestationError::InvalidSignedEvent)?;
+ Ok(RadrootsTradeMutationRecordV1 {
+ transport_event_id,
+ mutation,
+ })
+ })
+ .collect::<Result<Vec<_>, TradeAgreementAttestationError>>()?;
+ Ok(reduce_trade_records(input))
+ }
+
+ async fn store_report(
+ &self,
+ report: TradeAgreementAttestationReportV1,
+ ) -> Result<(), TradeAgreementAttestationRuntimeError> {
+ {
+ let mut state = self.state.lock().await;
+ state
+ .reports_by_claim
+ .insert(report.statement.claim_mutation_id.clone(), report);
+ }
+ self.persist().await
+ }
+}
+
+impl TradeAgreementAttestationState {
+ pub fn report_for_claim(
+ &self,
+ claim_mutation_id: &str,
+ ) -> Option<&TradeAgreementAttestationReportV1> {
+ self.reports_by_claim.get(claim_mutation_id)
+ }
+
+ pub fn observed_mutation_count(&self) -> usize {
+ self.mutation_events.len()
+ }
+
+ fn observe_event_created_at(&mut self, created_at: u32) {
+ self.last_event_created_at = Some(
+ self.last_event_created_at
+ .map_or(created_at, |current| current.max(created_at)),
+ );
+ }
+
+ fn replay_since(&self, now: u64, replay_window_secs: u64, replay_overlap_secs: u64) -> u64 {
+ let window_start = now.saturating_sub(replay_window_secs);
+ match self.last_event_created_at {
+ Some(last) => u64::from(last).saturating_sub(replay_overlap_secs),
+ None => window_start,
+ }
+ }
+}
+
+impl TradeAgreementAttestationStatePersistence {
+ fn new(path: PathBuf) -> Self {
+ Self { path }
+ }
+
+ async fn load(
+ &self,
+ ) -> Result<TradeAgreementAttestationState, TradeAgreementAttestationRuntimeError> {
+ if !self.path.exists() {
+ return Ok(TradeAgreementAttestationState::default());
+ }
+ let payload = tokio::fs::read_to_string(&self.path).await?;
+ let snapshot: PersistedTradeAgreementAttestationState = serde_json::from_str(&payload)?;
+ if snapshot.version != RHI_AGREEMENT_ATTESTATION_STATE_VERSION {
+ return Err(
+ TradeAgreementAttestationRuntimeError::UnsupportedStateVersion(snapshot.version),
+ );
+ }
+ Ok(snapshot.state)
+ }
+
+ async fn persist(
+ &self,
+ state: &TradeAgreementAttestationState,
+ ) -> Result<(), TradeAgreementAttestationRuntimeError> {
+ let parent = self.path.parent().ok_or_else(|| {
+ TradeAgreementAttestationRuntimeError::InvalidStatePath(self.path.clone())
+ })?;
+ tokio::fs::create_dir_all(parent).await?;
+ let snapshot = PersistedTradeAgreementAttestationState {
+ version: RHI_AGREEMENT_ATTESTATION_STATE_VERSION,
+ state: state.clone(),
+ };
+ let payload = serde_json::to_vec_pretty(&snapshot)?;
+ let temp = temp_state_path(&self.path)?;
+ tokio::fs::write(&temp, payload).await?;
+ tokio::fs::rename(temp, &self.path).await?;
+ Ok(())
+ }
+}
+
+fn temp_state_path(path: &Path) -> Result<PathBuf, TradeAgreementAttestationRuntimeError> {
+ let parent = path.parent().ok_or_else(|| {
+ TradeAgreementAttestationRuntimeError::InvalidStatePath(path.to_path_buf())
+ })?;
+ let file_name = path
+ .file_name()
+ .and_then(|value| value.to_str())
+ .ok_or_else(|| {
+ TradeAgreementAttestationRuntimeError::InvalidStatePath(path.to_path_buf())
+ })?;
+ Ok(parent.join(format!(".{file_name}.tmp")))
+}
+
+pub async fn handle_trade_mutation_event(
+ event: RadrootsNostrEvent,
+ runtime: TradeAgreementAttestationRuntime,
+ policy: &TradeAgreementAttestationPolicy,
+) -> Result<Option<TradeAgreementAttestationReportV1>, TradeAgreementAttestationError> {
+ policy.validate()?;
+ let kind = event_kind_u32(&event)?;
+ if !is_trade_mutation_event_kind(kind) {
+ return Err(TradeAgreementAttestationError::UnsupportedKind);
+ }
+ let mutation = trade_mutation_from_canonical_content(event.content.as_str())?;
+ if mutation.mutation_kind().nostr_kind() != kind {
+ return Err(TradeAgreementAttestationError::UnsupportedKind);
+ }
+ let event_author = RadrootsPublicKey::parse(event.pubkey.to_hex())
+ .map_err(|_| TradeAgreementAttestationError::InvalidSignedEvent)?;
+ if event_author != mutation.author_pubkey {
+ return Err(TradeAgreementAttestationError::InvalidEventAuthor);
+ }
+ let mutation_id = mutation
+ .mutation_id
+ .clone()
+ .ok_or(TradeAgreementAttestationError::MissingMutationId)?;
+ if !runtime
+ .observe_mutation_event(&event, &mutation, &mutation_id, kind)
+ .await?
+ {
+ return Ok(None);
+ }
+ let projection = runtime.reduce_trade(&mutation.trade_id).await?;
+ let Some(claim_id) = projection
+ .active_agreement_claim_ids
+ .iter()
+ .find(|claim_id| **claim_id == mutation_id)
+ .or_else(|| projection.active_agreement_claim_ids.first())
+ .cloned()
+ else {
+ return Ok(None);
+ };
+ let report = attest_projection_claim(&projection, &claim_id, policy)?;
+ runtime.store_report(report.clone()).await?;
+ Ok(Some(report))
+}
+
+pub fn attest_projection_claim(
+ projection: &RadrootsTradeProjectionV1,
+ claim_mutation_id: &RadrootsTradeMutationId,
+ policy: &TradeAgreementAttestationPolicy,
+) -> Result<TradeAgreementAttestationReportV1, TradeAgreementAttestationError> {
+ policy.validate()?;
+ if !projection
+ .agreement_claims
+ .iter()
+ .any(|claim| claim.claim_mutation_id == *claim_mutation_id)
+ {
+ return Err(TradeAgreementAttestationError::MissingAgreementClaim);
+ }
+ let statement = TradeAgreementAttestationStatementV1 {
+ protocol_id: RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID.to_owned(),
+ schema_version: RHI_AGREEMENT_ATTESTATION_REPORT_VERSION,
+ reducer_contract_id: RADROOTS_TRADE_REDUCER_CONTRACT_ID.to_owned(),
+ reducer_version: RADROOTS_TRADE_REDUCER_VERSION,
+ trade_id: projection.trade_id.as_str().to_owned(),
+ claim_mutation_id: claim_mutation_id.as_str().to_owned(),
+ projection_digest: projection.projection_digest.clone(),
+ agreement_state: projection.agreement_state,
+ attestation_state_before_report: projection.attestation_state,
+ active_agreement_claim_ids: mutation_ids_to_strings(&projection.active_agreement_claim_ids),
+ contested_claim_ids: mutation_ids_to_strings(&projection.contested_claim_ids),
+ cancelled_claim_ids: mutation_ids_to_strings(&projection.cancelled_claim_ids),
+ evidence_state: projection.evidence_state,
+ validator_set: policy.validator_set_binding()?,
+ };
+ let statement_hash = hash_canonical_value(
+ b"radroots:rhi-agreement-attestation-statement:v1\0",
+ &statement,
+ )?;
+ let result = if projection.agreement_state == RadrootsTradeAgreementStateV1::Agreed
+ && projection
+ .active_agreement_claim_ids
+ .iter()
+ .any(|claim| claim == claim_mutation_id)
+ && !projection
+ .contested_claim_ids
+ .iter()
+ .any(|claim| claim == claim_mutation_id)
+ && !projection
+ .cancelled_claim_ids
+ .iter()
+ .any(|claim| claim == claim_mutation_id)
+ {
+ RadrootsTradeAttestationResultV1::Valid
+ } else {
+ RadrootsTradeAttestationResultV1::Invalid
+ };
+ let proof_identity_hash = hash_canonical_value(
+ b"radroots:rhi-agreement-attestation-proof-identity:v1\0",
+ &serde_json::json!({
+ "backend": policy.backend.as_str(),
+ "proof_system": RHI_AGREEMENT_ATTESTATION_PROOF_SYSTEM_LOCAL_STATEMENT_HASH,
+ "statement_hash": statement_hash,
+ "validator_set": statement.validator_set.clone(),
+ }),
+ )?;
+ let attestation_id = hash_canonical_value(
+ b"radroots:rhi-agreement-attestation-report:v1\0",
+ &serde_json::json!({
+ "proof_identity_hash": proof_identity_hash,
+ "result": result,
+ "statement_hash": statement_hash,
+ }),
+ )?;
+ Ok(TradeAgreementAttestationReportV1 {
+ report_version: RHI_AGREEMENT_ATTESTATION_REPORT_VERSION,
+ attestation_id,
+ result,
+ statement,
+ statement_hash,
+ proof_system: RHI_AGREEMENT_ATTESTATION_PROOF_SYSTEM_LOCAL_STATEMENT_HASH.to_owned(),
+ proof_identity_hash,
+ })
+}
+
+pub fn trade_mutation_subscription_kinds() -> Vec<u32> {
+ TRADE_MUTATION_EVENT_KINDS.to_vec()
+}
+
+fn mutation_ids_to_strings(values: &[RadrootsTradeMutationId]) -> Vec<String> {
+ values
+ .iter()
+ .map(|value| value.as_str().to_owned())
+ .collect()
+}
+
+fn event_kind_u32(event: &RadrootsNostrEvent) -> Result<u32, TradeAgreementAttestationError> {
+ match event.kind {
+ RadrootsNostrKind::Custom(value) => Ok(u32::from(value)),
+ _ => Err(TradeAgreementAttestationError::UnsupportedKind),
+ }
+}
+
+fn validate_optional_hash32(value: &Option<String>) -> Result<(), TradeAgreementAttestationError> {
+ if let Some(value) = value {
+ validate_hash32(value)?;
+ }
+ Ok(())
+}
+
+fn validate_hash32(value: &str) -> Result<(), TradeAgreementAttestationError> {
+ let stripped = value.strip_prefix("0x").unwrap_or(value);
+ if stripped.len() != 64 || !stripped.bytes().all(|byte| byte.is_ascii_hexdigit()) {
+ return Err(TradeAgreementAttestationError::InvalidHashField);
+ }
+ Ok(())
+}
+
+fn hash_canonical_value(
+ domain: &[u8],
+ value: &impl Serialize,
+) -> Result<String, TradeAgreementAttestationError> {
+ let value = serde_json::to_value(value)?;
+ let canonical = canonical_jcs_value(&value)?;
+ let mut hasher = Sha256::new();
+ hasher.update(domain);
+ hasher.update(canonical.as_bytes());
+ Ok(format!("{:x}", hasher.finalize()))
+}
+
+fn map_notification_recv_result(
+ result: Result<RadrootsNostrRelayPoolNotification, tokio::sync::broadcast::error::RecvError>,
+) -> Result<RadrootsNostrRelayPoolNotification, ()> {
+ result.map_err(|_| ())
+}
+
+async fn subscribe_io(
+ client: &RadrootsNostrClient,
+ filter: RadrootsNostrFilter,
+) -> Result<RadrootsNostrSubscriptionId> {
+ let subscription = client.subscribe(filter, None).await?;
+ Ok(subscription.val)
+}
+
+async fn unsubscribe_io(
+ client: &RadrootsNostrClient,
+ subscription_id: &RadrootsNostrSubscriptionId,
+) {
+ client.unsubscribe(subscription_id).await;
+}
+
+fn should_delay_before_event_handle() -> bool {
+ cfg!(all(debug_assertions, not(test)))
+}
+
+async fn process_event_notification(
+ event: RadrootsNostrEvent,
+ runtime: TradeAgreementAttestationRuntime,
+ policy: TradeAgreementAttestationPolicy,
+) -> Result<()> {
+ let created_at = u32::try_from(event.created_at.as_secs()).unwrap_or(u32::MAX);
+ if should_delay_before_event_handle() {
+ sleep(Duration::from_millis(200)).await;
+ }
+ match handle_trade_mutation_event(event, runtime.clone(), &policy).await {
+ Ok(_) | Err(TradeAgreementAttestationError::UnsupportedKind) => {
+ runtime.mark_processed_event(created_at).await?;
+ Ok(())
+ }
+ Err(error) => {
+ warn!("rhi agreement attestation rejected event: {error}");
+ runtime.mark_processed_event(created_at).await?;
+ Ok(())
+ }
+ }
+}
+
+pub async fn subscriber(
+ client: RadrootsNostrClient,
+ _keys: RadrootsNostrKeys,
+ runtime: TradeAgreementAttestationRuntime,
+ policy: TradeAgreementAttestationPolicy,
+ mut stop_rx: watch::Receiver<bool>,
+) -> Result<()> {
+ let subscribed_kinds = trade_mutation_subscription_kinds();
+ info!(
+ "Starting subscriber for release-product trade mutation kinds: {:?}",
+ subscribed_kinds
+ );
+
+ let kinds: Vec<RadrootsNostrKind> = subscribed_kinds
+ .iter()
+ .map(|kind| u16::try_from(*kind).expect("trade mutation kinds fit in nostr custom range"))
+ .map(RadrootsNostrKind::Custom)
+ .collect();
+ let filter = runtime.recovery_filter(kinds).await;
+
+ if *stop_rx.borrow() {
+ return Ok(());
+ }
+
+ let subscription_id = subscribe_io(&client, filter).await?;
+ let mut notifications = client.notifications();
+ let mut notifications_closed = false;
+
+ loop {
+ tokio::select! {
+ _ = stop_rx.changed() => {
+ break;
+ }
+ msg = async {
+ map_notification_recv_result(notifications.recv().await)
+ } => {
+ let n = match msg {
+ Ok(n) => n,
+ Err(_) => {
+ notifications_closed = true;
+ break;
+ }
+ };
+
+ if let RadrootsNostrRelayPoolNotification::Event { event, .. } = n {
+ let event = (*event).clone();
+ process_event_notification(event, runtime.clone(), policy.clone()).await?;
+ }
+ }
+ }
+ }
+
+ unsubscribe_io(&client, &subscription_id).await;
+ if notifications_closed {
+ return Err(anyhow!(
+ "rhi agreement attestation subscriber notifications closed"
+ ));
+ }
+ Ok(())
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationSmokeRequest {
+ pub protocol_id: String,
+ pub operation: TradeAgreementAttestationSmokeOperation,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum TradeAgreementAttestationSmokeOperation {
+ Health,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TradeAgreementAttestationSmokeResponse {
+ pub ok: bool,
+ pub protocol_id: String,
+ pub operation: TradeAgreementAttestationSmokeOperation,
+ pub worker_name: String,
+ pub worker_version: String,
+ pub capabilities: Vec<String>,
+ pub error: Option<String>,
+}
+
+pub async fn run_smoke_cli_command(command: crate::cli::Command) -> anyhow::Result<()> {
+ let crate::cli::Command::AttestationSmoke { input, output } = command;
+ let request_bytes = read_input(input.as_deref())?;
+ let response = handle_smoke_request_bytes(&request_bytes).await;
+ let response_bytes = serde_json::to_vec_pretty(&response)?;
+ write_output(output.as_deref(), &response_bytes)?;
+ if response.ok {
+ Ok(())
+ } else {
+ Err(anyhow!(
+ "{}",
+ response
+ .error
+ .as_deref()
+ .unwrap_or("attestation smoke request failed")
+ ))
+ }
+}
+
+pub async fn handle_smoke_request_bytes(bytes: &[u8]) -> TradeAgreementAttestationSmokeResponse {
+ match serde_json::from_slice::<TradeAgreementAttestationSmokeRequest>(bytes) {
+ Ok(request) if request.protocol_id == RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID => {
+ TradeAgreementAttestationSmokeResponse {
+ ok: true,
+ protocol_id: request.protocol_id,
+ operation: request.operation,
+ worker_name: "rhi".to_owned(),
+ worker_version: env!("CARGO_PKG_VERSION").to_owned(),
+ capabilities: vec![
+ "release_product_trade_mutation_observation".to_owned(),
+ "agreement_claim_attestation_report".to_owned(),
+ "no_agreement_authority".to_owned(),
+ ],
+ error: None,
+ }
+ }
+ Ok(request) => TradeAgreementAttestationSmokeResponse {
+ ok: false,
+ protocol_id: request.protocol_id,
+ operation: request.operation,
+ worker_name: "rhi".to_owned(),
+ worker_version: env!("CARGO_PKG_VERSION").to_owned(),
+ capabilities: Vec::new(),
+ error: Some("invalid protocol id".to_owned()),
+ },
+ Err(error) => TradeAgreementAttestationSmokeResponse {
+ ok: false,
+ protocol_id: RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID.to_owned(),
+ operation: TradeAgreementAttestationSmokeOperation::Health,
+ worker_name: "rhi".to_owned(),
+ worker_version: env!("CARGO_PKG_VERSION").to_owned(),
+ capabilities: Vec::new(),
+ error: Some(error.to_string()),
+ },
+ }
+}
+
+fn read_input(path: Option<&Path>) -> anyhow::Result<Vec<u8>> {
+ match path {
+ Some(path) => std::fs::read(path).map_err(anyhow::Error::from),
+ None => std::io::read_to_string(std::io::stdin())
+ .map(|value| value.into_bytes())
+ .map_err(anyhow::Error::from),
+ }
+}
+
+fn write_output(path: Option<&Path>, bytes: &[u8]) -> anyhow::Result<()> {
+ match path {
+ Some(path) => std::fs::write(path, bytes).map_err(anyhow::Error::from),
+ None => {
+ println!("{}", String::from_utf8_lossy(bytes));
+ Ok(())
+ }
+ }
+}
+
+#[cfg(test)]
+#[cfg_attr(coverage_nightly, coverage(off))]
+mod tests {
+ use super::{
+ RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID, TradeAgreementAttestationPolicy,
+ TradeAgreementAttestationRuntime, TradeAgreementAttestationRuntimeConfig,
+ TradeAgreementAttestationSmokeOperation, TradeAgreementAttestationSmokeRequest,
+ attest_projection_claim, handle_smoke_request_bytes,
+ };
+ use radroots_event::ids::{RadrootsTradeId, RadrootsTradeMutationId};
+ use radroots_trade::workflow::{
+ RadrootsTradeAgreementStateV1, RadrootsTradeAttestationStateV1,
+ RadrootsTradeEvidenceStateV1, RadrootsTradeProjectionV1,
+ };
+
+ fn hex_64(ch: char) -> String {
+ std::iter::repeat_n(ch, 64).collect()
+ }
+
+ fn hex_32(ch: char) -> String {
+ std::iter::repeat_n(ch, 32).collect()
+ }
+
+ fn projection(claim_id: RadrootsTradeMutationId) -> RadrootsTradeProjectionV1 {
+ RadrootsTradeProjectionV1 {
+ reducer_contract_id: radroots_trade::workflow::RADROOTS_TRADE_REDUCER_CONTRACT_ID
+ .to_owned(),
+ reducer_version: radroots_trade::workflow::RADROOTS_TRADE_REDUCER_VERSION,
+ trade_id: RadrootsTradeId::parse(hex_32('1')).expect("trade id"),
+ root_mutation_id: None,
+ buyer_pubkey: None,
+ seller_pubkey: None,
+ farm_id: None,
+ negotiation_state: Default::default(),
+ agreement_state: RadrootsTradeAgreementStateV1::Agreed,
+ evidence_state: RadrootsTradeEvidenceStateV1::Complete,
+ conflict_state: Default::default(),
+ private_terms_state: Default::default(),
+ attestation_state: RadrootsTradeAttestationStateV1::None,
+ fulfillment_state: Default::default(),
+ payment_state: Default::default(),
+ candidate_heads: Vec::new(),
+ agreement_claims: vec![radroots_trade::workflow::RadrootsTradeAgreementClaimV1 {
+ claim_mutation_id: claim_id.clone(),
+ proposal_mutation_id: RadrootsTradeMutationId::parse(hex_64('2'))
+ .expect("proposal id"),
+ candidate_id: radroots_event::ids::RadrootsTradeCandidateId::parse(hex_64('3'))
+ .expect("candidate id"),
+ candidate_author_pubkey: radroots_event::ids::RadrootsPublicKey::parse(hex_64('4'))
+ .expect("author"),
+ accepted_by_pubkey: radroots_event::ids::RadrootsPublicKey::parse(hex_64('5'))
+ .expect("acceptor"),
+ reservation_commitment: hex_64('6'),
+ }],
+ active_agreement_claim_ids: vec![claim_id],
+ contested_claim_ids: Vec::new(),
+ cancelled_claim_ids: Vec::new(),
+ declined_candidate_ids: Vec::new(),
+ missing_parent_ids: Vec::new(),
+ missing_proposal_ids: Vec::new(),
+ unsupported_mutation_ids: Vec::new(),
+ issues: Vec::new(),
+ attestations: Vec::new(),
+ projection_digest: hex_64('7'),
+ }
+ }
+
+ #[test]
+ fn attestation_report_is_keyed_to_claim_and_projection_digest() {
+ let claim_id = RadrootsTradeMutationId::parse(hex_64('a')).expect("claim id");
+ let report = attest_projection_claim(
+ &projection(claim_id.clone()),
+ &claim_id,
+ &TradeAgreementAttestationPolicy::default(),
+ )
+ .expect("report");
+ assert_eq!(report.statement.claim_mutation_id, claim_id.as_str());
+ assert_eq!(report.statement.projection_digest, hex_64('7'));
+ assert_eq!(report.proof_system, "local_statement_hash");
+ }
+
+ #[tokio::test]
+ async fn runtime_persists_and_loads_attestation_state() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let config = TradeAgreementAttestationRuntimeConfig {
+ state_path: temp.path().join("state.json"),
+ replay_window_secs: 100,
+ replay_overlap_secs: 10,
+ };
+ let runtime = TradeAgreementAttestationRuntime::load(config.clone())
+ .await
+ .expect("load");
+ {
+ let state = runtime.state();
+ state.lock().await.observe_event_created_at(42);
+ }
+ runtime.persist().await.expect("persist");
+ let loaded = TradeAgreementAttestationRuntime::load(config)
+ .await
+ .expect("load persisted");
+ assert_eq!(loaded.state().lock().await.replay_since(100, 100, 10), 32);
+ }
+
+ #[tokio::test]
+ async fn smoke_request_reports_optional_capabilities() {
+ let request = TradeAgreementAttestationSmokeRequest {
+ protocol_id: RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID.to_owned(),
+ operation: TradeAgreementAttestationSmokeOperation::Health,
+ };
+ let response =
+ handle_smoke_request_bytes(&serde_json::to_vec(&request).expect("json")).await;
+ assert!(response.ok);
+ assert!(
+ response
+ .capabilities
+ .contains(&"no_agreement_authority".to_owned())
+ );
+ }
+}
diff --git a/src/features/trade_listing/handlers/events.rs b/src/features/trade_listing/handlers/events.rs
@@ -1,947 +0,0 @@
-#![forbid(unsafe_code)]
-#![cfg_attr(coverage_nightly, coverage(off))]
-
-use std::sync::Arc;
-
-use radroots_event::farm::RadrootsFarmRef;
-use radroots_event::ids::{RadrootsEventId, RadrootsPublicKey};
-use radroots_event::kinds::{
- KIND_JOB_FEEDBACK, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST,
- KIND_TRADE_VALIDATION_RECEIPT, is_listing_kind, is_order_event_kind,
- is_trade_validation_service_event_kind,
-};
-use radroots_event::trade_validation::RadrootsTradeValidationListingError as TradeListingValidationError;
-use radroots_event_codec::order::{RadrootsOrderEnvelopeParseError, parse_order_listing_event_tag};
-use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrEventBuilder, RadrootsNostrKeys,
- RadrootsNostrKind, RadrootsNostrTag, radroots_event_from_nostr, radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id, radroots_nostr_send_event,
-};
-use radroots_trade::listing::{parse_public_listing_address, validation::validate_listing_event};
-use radroots_trade::order::{
- RadrootsOrderEventRecord, RadrootsOrderProjection, order_event_record_from_event,
- reduce_order_event_records,
-};
-use radroots_trade::workflow::RadrootsTradeWorkflowState;
-use thiserror::Error;
-
-use crate::features::trade_listing::state::{
- TradeListingRuntime, TradeListingState, TradeListingStateError, TradeOrderState,
-};
-use crate::features::trade_validation_receipt::{
- TradeValidationReceiptJobError, TradeValidationReceiptProverPolicy, publish_validation_receipt,
-};
-
-#[derive(Debug, Error)]
-pub enum TradeListingEventError {
- #[error("event kind not supported")]
- UnsupportedKind,
- #[error("missing recipient tag")]
- MissingRecipient,
- #[error("missing required tag: {0}")]
- MissingTag(&'static str),
- #[error("tag mismatch: {0}")]
- TagMismatch(&'static str),
- #[error("invalid envelope: {0}")]
- InvalidEnvelope(String),
- #[error("invalid envelope payload: {0}")]
- InvalidPayload(String),
- #[error("invalid listing address")]
- InvalidListingAddr,
- #[error("invalid order request payload")]
- InvalidOrder,
- #[error("shared workflow rejected trade transition: {0}")]
- Workflow(String),
- #[error("state error: {0}")]
- State(#[from] TradeListingStateError),
- #[error("nostr error: {0}")]
- Nostr(#[from] radroots_nostr::error::RadrootsNostrError),
- #[error("serde error: {0}")]
- Serde(#[from] serde_json::Error),
- #[error("unauthorized sender")]
- Unauthorized,
-}
-
-#[cfg(test)]
-#[derive(Default)]
-struct TradeListingEventTestHooks {
- fetch_event_by_id_results:
- std::collections::VecDeque<Result<RadrootsNostrEvent, TradeListingEventError>>,
- send_event_results: std::collections::VecDeque<Result<(), TradeListingEventError>>,
- validate_listing_results:
- std::collections::VecDeque<Result<(String, RadrootsFarmRef), TradeListingValidationError>>,
-}
-
-#[cfg(test)]
-static TRADE_LISTING_EVENT_TEST_HOOKS: std::sync::OnceLock<
- std::sync::Mutex<TradeListingEventTestHooks>,
-> = std::sync::OnceLock::new();
-
-#[cfg(test)]
-fn trade_listing_event_test_hooks() -> &'static std::sync::Mutex<TradeListingEventTestHooks> {
- TRADE_LISTING_EVENT_TEST_HOOKS
- .get_or_init(|| std::sync::Mutex::new(TradeListingEventTestHooks::default()))
-}
-
-#[cfg(test)]
-fn pop_fetch_event_by_id_hook() -> Option<Result<RadrootsNostrEvent, TradeListingEventError>> {
- trade_listing_event_test_hooks()
- .lock()
- .expect("trade listing event test hooks lock")
- .fetch_event_by_id_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn pop_send_event_hook() -> Option<Result<(), TradeListingEventError>> {
- trade_listing_event_test_hooks()
- .lock()
- .expect("trade listing event test hooks lock")
- .send_event_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn pop_validate_listing_hook()
--> Option<Result<(String, RadrootsFarmRef), TradeListingValidationError>> {
- trade_listing_event_test_hooks()
- .lock()
- .expect("trade listing event test hooks lock")
- .validate_listing_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn take_fetch_event_by_id_hook() -> Option<Result<RadrootsNostrEvent, TradeListingEventError>> {
- pop_fetch_event_by_id_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_fetch_event_by_id_hook() -> Option<Result<RadrootsNostrEvent, TradeListingEventError>> {
- None
-}
-
-#[cfg(test)]
-fn take_send_event_hook() -> Option<Result<(), TradeListingEventError>> {
- pop_send_event_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_send_event_hook() -> Option<Result<(), TradeListingEventError>> {
- None
-}
-
-#[cfg(test)]
-fn take_validate_listing_hook()
--> Option<Result<(String, RadrootsFarmRef), TradeListingValidationError>> {
- pop_validate_listing_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_validate_listing_hook()
--> Option<Result<(String, RadrootsFarmRef), TradeListingValidationError>> {
- None
-}
-
-async fn fetch_event_by_id_io(
- client: &RadrootsNostrClient,
- id: &str,
-) -> Result<RadrootsNostrEvent, TradeListingEventError> {
- match take_fetch_event_by_id_hook() {
- Some(result) => result,
- None => radroots_nostr_fetch_event_by_id(client, id)
- .await
- .map_err(TradeListingEventError::from),
- }
-}
-
-#[cfg_attr(all(not(test), coverage_nightly), coverage(off))]
-async fn send_event_io(
- client: &RadrootsNostrClient,
- builder: RadrootsNostrEventBuilder,
-) -> Result<(), TradeListingEventError> {
- match take_send_event_hook() {
- Some(result) => result,
- None => radroots_nostr_send_event(client, builder)
- .await
- .map(|_| ())
- .map_err(TradeListingEventError::from),
- }
-}
-
-#[cfg_attr(all(not(test), coverage_nightly), coverage(off))]
-fn validate_listing_event_io(
- event: &RadrootsNostrEvent,
-) -> Result<(String, RadrootsFarmRef), TradeListingValidationError> {
- match take_validate_listing_hook() {
- Some(result) => result,
- None => validate_listing_event(&radroots_event_from_nostr(event))
- .map(|listing| (listing.listing_addr, listing.listing.farm)),
- }
-}
-
-pub async fn handle_event_with_policy(
- event: RadrootsNostrEvent,
- _tags: Vec<RadrootsNostrTag>,
- keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- runtime: TradeListingRuntime,
- proof_policy: &TradeValidationReceiptProverPolicy,
-) -> Result<(), TradeListingEventError> {
- let kind = event_kind_u32(&event)?;
- let state = runtime.state();
- if is_listing_kind(kind) {
- return handle_listing_event(&event, &state).await;
- }
- if event.pubkey == keys.public_key() {
- return Ok(());
- }
- if kind == KIND_TRADE_VALIDATION_RECEIPT {
- state
- .lock()
- .await
- .mark_non_order_event_seen(&event.id.to_string());
- return Ok(());
- }
- if is_order_event_kind(kind) {
- return handle_order_event(&event, kind, &keys, &client, &runtime, proof_policy).await;
- }
- if is_trade_validation_service_event_kind(kind) {
- return Err(TradeListingEventError::UnsupportedKind);
- }
- Err(TradeListingEventError::UnsupportedKind)
-}
-
-#[cfg(test)]
-pub async fn handle_event(
- event: RadrootsNostrEvent,
- tags: Vec<RadrootsNostrTag>,
- keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- runtime: TradeListingRuntime,
-) -> Result<(), TradeListingEventError> {
- handle_event_with_policy(
- event,
- tags,
- keys,
- client,
- runtime,
- &TradeValidationReceiptProverPolicy::default(),
- )
- .await
-}
-
-fn event_kind_u32(event: &RadrootsNostrEvent) -> Result<u32, TradeListingEventError> {
- match event.kind {
- RadrootsNostrKind::Custom(value) => Ok(u32::from(value)),
- _ => Err(TradeListingEventError::UnsupportedKind),
- }
-}
-
-fn map_trade_validation_receipt_job_error(
- error: TradeValidationReceiptJobError,
-) -> TradeListingEventError {
- match error {
- TradeValidationReceiptJobError::UnsupportedKind => TradeListingEventError::UnsupportedKind,
- TradeValidationReceiptJobError::MissingRecipient => {
- TradeListingEventError::MissingRecipient
- }
- TradeValidationReceiptJobError::Nostr(error) => TradeListingEventError::Nostr(error),
- other => TradeListingEventError::InvalidPayload(other.to_string()),
- }
-}
-
-fn map_order_parse_error(error: RadrootsOrderEnvelopeParseError) -> TradeListingEventError {
- match error {
- RadrootsOrderEnvelopeParseError::InvalidKind(_) => TradeListingEventError::UnsupportedKind,
- RadrootsOrderEnvelopeParseError::MissingTag(tag) => TradeListingEventError::MissingTag(tag),
- RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch => {
- TradeListingEventError::TagMismatch("a")
- }
- RadrootsOrderEnvelopeParseError::OrderIdTagMismatch => {
- TradeListingEventError::TagMismatch("d")
- }
- RadrootsOrderEnvelopeParseError::InvalidListingAddr(_) => {
- TradeListingEventError::InvalidListingAddr
- }
- RadrootsOrderEnvelopeParseError::InvalidEnvelope(error) => {
- TradeListingEventError::InvalidEnvelope(error.to_string())
- }
- other => TradeListingEventError::InvalidPayload(other.to_string()),
- }
-}
-
-async fn handle_listing_event(
- event: &RadrootsNostrEvent,
- state: &Arc<tokio::sync::Mutex<TradeListingState>>,
-) -> Result<(), TradeListingEventError> {
- let event_id = event.id.to_string();
- {
- let state = state.lock().await;
- if state.is_non_order_event_seen(&event_id) {
- return Ok(());
- }
- }
- let validated = validate_listing_event(&radroots_event_from_nostr(event))
- .map_err(|error| TradeListingEventError::InvalidPayload(error.to_string()))?;
- let kind = event_kind_u32(event)?;
- let mut state = state.lock().await;
- state.upsert_listing_event(&validated.listing_addr, &event_id, kind);
- state.mark_non_order_event_seen(&event_id);
- Ok(())
-}
-
-async fn handle_order_event(
- event: &RadrootsNostrEvent,
- kind: u32,
- keys: &RadrootsNostrKeys,
- client: &RadrootsNostrClient,
- runtime: &TradeListingRuntime,
- proof_policy: &TradeValidationReceiptProverPolicy,
-) -> Result<(), TradeListingEventError> {
- let state = runtime.state();
- match kind {
- KIND_ORDER_REQUEST => handle_order_request(event, client, &state).await,
- KIND_ORDER_DECISION | KIND_ORDER_CANCELLATION => {
- handle_order_workflow_event(event, client, runtime, keys, proof_policy).await
- }
- _ => Err(TradeListingEventError::UnsupportedKind),
- }
-}
-
-async fn handle_order_request(
- event: &RadrootsNostrEvent,
- client: &RadrootsNostrClient,
- state: &Arc<tokio::sync::Mutex<TradeListingState>>,
-) -> Result<(), TradeListingEventError> {
- let rr_event = radroots_event_from_nostr(event);
- let record = order_event_record_from_event(&rr_event).map_err(map_order_decode_error)?;
- let order_id = record.order_id().clone();
- let projection = reduce_order_event_records(&order_id, [record.clone()]);
- if projection.status != RadrootsTradeWorkflowState::Requested || !projection.issues.is_empty() {
- return Err(TradeListingEventError::Workflow(
- workflow_rejection_message(&projection),
- ));
- }
- let RadrootsOrderEventRecord::Request(request) = record else {
- return Err(TradeListingEventError::UnsupportedKind);
- };
- let listing_addr = parse_public_listing_address(&request.payload.listing_addr)
- .map_err(|_| TradeListingEventError::InvalidListingAddr)?;
- if request.payload.seller_pubkey != listing_addr.seller_pubkey {
- return Err(TradeListingEventError::InvalidListingAddr);
- }
- let rr_tags = rr_event.tags_as_vec();
- let listing_event = parse_order_listing_event_tag(&rr_tags)
- .map_err(|error| TradeListingEventError::InvalidPayload(error.to_string()))?
- .ok_or(TradeListingEventError::MissingTag("listing_event"))?;
- let listing_snapshot_event_id =
- ensure_listing_snapshot(&request.payload.listing_addr, &listing_event, client, state)
- .await?;
- let event_id = event.id.to_string();
- let mut state = state.lock().await;
- if state.order_exists(order_id.as_str()) {
- return Ok(());
- }
- let mut seen = std::collections::HashSet::new();
- seen.insert(event_id.clone());
- state.insert_order(TradeOrderState {
- order_id: order_id.to_string(),
- listing_addr: request.payload.listing_addr.to_string(),
- buyer_pubkey: request.payload.buyer_pubkey.to_string(),
- seller_pubkey: request.payload.seller_pubkey.to_string(),
- status: projection.status,
- listing_snapshot_event_id: Some(listing_snapshot_event_id),
- root_event_id: Some(event_id.clone()),
- last_event_id: Some(event_id),
- seen_event_ids: seen,
- });
- Ok(())
-}
-
-async fn ensure_listing_snapshot(
- listing_addr: &str,
- listing_event: &radroots_event::RadrootsEventPtr,
- client: &RadrootsNostrClient,
- state: &Arc<tokio::sync::Mutex<TradeListingState>>,
-) -> Result<String, TradeListingEventError> {
- {
- let state = state.lock().await;
- if state.listing_event_id(listing_addr) == Some(listing_event.id.as_str()) {
- return Ok(listing_event.id.clone());
- }
- }
- let event = fetch_event_by_id_io(client, &listing_event.id).await?;
- let (validated_listing_addr, _) = validate_listing_event_io(&event)
- .map_err(|error| TradeListingEventError::InvalidPayload(error.to_string()))?;
- if validated_listing_addr != listing_addr {
- return Err(TradeListingEventError::InvalidOrder);
- }
- let kind = event_kind_u32(&event)?;
- let mut state = state.lock().await;
- state.upsert_listing_event(listing_addr, &listing_event.id, kind);
- Ok(listing_event.id.clone())
-}
-
-async fn handle_order_workflow_event(
- event: &RadrootsNostrEvent,
- client: &RadrootsNostrClient,
- runtime: &TradeListingRuntime,
- keys: &RadrootsNostrKeys,
- proof_policy: &TradeValidationReceiptProverPolicy,
-) -> Result<(), TradeListingEventError> {
- let state = runtime.state();
- let rr_event = radroots_event_from_nostr(event);
- let current_record =
- order_event_record_from_event(&rr_event).map_err(map_order_decode_error)?;
- let order_id = current_record.order_id().clone();
- let event_id = event.id.to_string();
- let order_snapshot = {
- let state = state.lock().await;
- if state.is_event_seen(order_id.as_str(), &event_id) {
- return Ok(());
- }
- state
- .get_order(order_id.as_str())
- .cloned()
- .ok_or(TradeListingStateError::MissingOrder)?
- };
- let mut records =
- fetch_seen_order_records(client, &order_snapshot, event, current_record).await?;
- let projection = reduce_order_event_records(&order_id, records.drain(..));
- if projection.status != RadrootsTradeWorkflowState::Invalid && !projection.issues.is_empty() {
- return Err(TradeListingEventError::Workflow(
- workflow_rejection_message(&projection),
- ));
- }
- let mut state = state.lock().await;
- if state.is_event_seen(order_id.as_str(), &event_id) {
- return Ok(());
- }
- let order = state
- .get_order_mut(order_id.as_str())
- .ok_or(TradeListingStateError::MissingOrder)?;
- ensure_projection_binding(order, &projection)?;
- let projected_status = projection.status.clone();
- order.status = projected_status.clone();
- order.last_event_id = projection
- .last_event_id
- .map(|last_event_id| last_event_id.to_string())
- .or_else(|| Some(event_id.clone()));
- order.seen_event_ids.insert(event_id);
- drop(state);
- if projected_status == RadrootsTradeWorkflowState::AgreedPendingValidation {
- publish_validation_receipt(event, client, runtime, keys, proof_policy)
- .await
- .map_err(map_trade_validation_receipt_job_error)?;
- }
- Ok(())
-}
-
-async fn fetch_seen_order_records(
- client: &RadrootsNostrClient,
- order: &TradeOrderState,
- current_event: &RadrootsNostrEvent,
- current_record: RadrootsOrderEventRecord,
-) -> Result<Vec<RadrootsOrderEventRecord>, TradeListingEventError> {
- let current_event_id = current_event.id.to_string();
- let mut event_ids = order
- .seen_event_ids
- .iter()
- .filter(|event_id| event_id.as_str() != current_event_id)
- .cloned()
- .collect::<Vec<_>>();
- event_ids.sort();
- let mut records = Vec::with_capacity(event_ids.len() + 1);
- for event_id in event_ids {
- let event = fetch_event_by_id_io(client, &event_id).await?;
- let rr_event = radroots_event_from_nostr(&event);
- let record = order_event_record_from_event(&rr_event).map_err(map_order_decode_error)?;
- if record.order_id().as_str() != order.order_id {
- return Err(TradeListingEventError::InvalidOrder);
- }
- records.push(record);
- }
- records.push(current_record);
- Ok(records)
-}
-
-fn ensure_projection_binding(
- order: &TradeOrderState,
- projection: &RadrootsOrderProjection,
-) -> Result<(), TradeListingEventError> {
- if projection
- .listing_addr
- .as_ref()
- .is_some_and(|listing_addr| listing_addr.to_string() != order.listing_addr)
- || projection
- .buyer_pubkey
- .as_ref()
- .is_some_and(|buyer_pubkey| buyer_pubkey.to_string() != order.buyer_pubkey)
- || projection
- .seller_pubkey
- .as_ref()
- .is_some_and(|seller_pubkey| seller_pubkey.to_string() != order.seller_pubkey)
- {
- return Err(TradeListingEventError::InvalidOrder);
- }
- Ok(())
-}
-
-fn workflow_rejection_message(projection: &RadrootsOrderProjection) -> String {
- format!("{:?}:{:?}", projection.status, projection.issues)
-}
-
-fn map_order_decode_error(
- error: radroots_trade::order::RadrootsOrderEventDecodeError,
-) -> TradeListingEventError {
- match error {
- radroots_trade::order::RadrootsOrderEventDecodeError::Envelope(error) => {
- map_order_parse_error(error)
- }
- radroots_trade::order::RadrootsOrderEventDecodeError::UnsupportedKind { .. } => {
- TradeListingEventError::UnsupportedKind
- }
- other => TradeListingEventError::InvalidPayload(other.to_string()),
- }
-}
-
-#[cfg(test)]
-fn tag_has_value(tags: &[Vec<String>], key: &str, value: &str) -> bool {
- tags.iter().any(|tag| {
- tag.first().map(String::as_str) == Some(key)
- && tag.get(1).map(String::as_str) == Some(value)
- })
-}
-
-pub async fn handle_error(
- error: TradeListingEventError,
- event: &RadrootsNostrEvent,
- client: &RadrootsNostrClient,
-) -> Result<(), TradeListingEventError> {
- let request_event_id = RadrootsEventId::parse(event.id.to_hex())
- .map_err(|err| TradeListingEventError::InvalidPayload(err.to_string()))?;
- let customer_pubkey = RadrootsPublicKey::parse(event.pubkey.to_hex())
- .map_err(|err| TradeListingEventError::InvalidPayload(err.to_string()))?;
- let tags = vec![
- vec!["e".to_string(), request_event_id.into_string()],
- vec!["p".to_string(), customer_pubkey.into_string()],
- vec!["status".to_string(), "error".to_string()],
- ];
- let builder = radroots_nostr_build_event(KIND_JOB_FEEDBACK, error.to_string(), tags)?;
- send_event_io(client, builder).await
-}
-
-#[cfg(test)]
-#[cfg_attr(coverage_nightly, coverage(off))]
-mod tests {
- use super::{
- TradeListingEventError, TradeListingEventTestHooks, handle_error, handle_event,
- tag_has_value, trade_listing_event_test_hooks,
- };
- use crate::features::trade_listing::state::TradeListingRuntime;
- use radroots_core::{
- RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit,
- };
- use radroots_event::RadrootsEventPtr;
- use radroots_event::ids::{
- RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId,
- RadrootsOrderQuoteId, RadrootsPublicKey,
- };
- use radroots_event::kinds::{KIND_LISTING, KIND_ORDER_REQUEST};
- use radroots_event::order::{
- RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome,
- RadrootsOrderEconomicItem, RadrootsOrderEconomicLine, RadrootsOrderEconomics,
- RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPricingBasis,
- RadrootsOrderRequest,
- };
- use radroots_event_codec::order::{
- order_cancellation_event_build, order_decision_event_build, order_request_event_build,
- };
- use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrEventBuilder, RadrootsNostrKeys,
- RadrootsNostrKind, radroots_nostr_build_event,
- };
- use radroots_trade::workflow::RadrootsTradeWorkflowState;
- use tokio::sync::{Mutex, MutexGuard};
-
- static TEST_LOCK: Mutex<()> = Mutex::const_new(());
-
- async fn test_guard() -> MutexGuard<'static, ()> {
- let guard = TEST_LOCK.lock().await;
- *trade_listing_event_test_hooks().lock().expect("hooks") =
- TradeListingEventTestHooks::default();
- guard
- }
-
- fn listing_id() -> &'static str {
- "AAAAAAAAAAAAAAAAAAAAAg"
- }
-
- fn listing_addr(seller: &RadrootsNostrKeys) -> String {
- format!("{}:{}:{}", KIND_LISTING, seller.public_key(), listing_id())
- }
-
- fn listing_event_id() -> &'static str {
- "0000000000000000000000000000000000000000000000000000000000000001"
- }
-
- fn typed_listing_addr(seller: &RadrootsNostrKeys) -> RadrootsListingAddress {
- RadrootsListingAddress::parse(listing_addr(seller)).expect("listing address")
- }
-
- fn typed_order_id(order_id: &str) -> RadrootsOrderId {
- RadrootsOrderId::parse(order_id).expect("order id")
- }
-
- fn typed_quote_id(order_id: &str) -> RadrootsOrderQuoteId {
- RadrootsOrderQuoteId::parse(format!("{order_id}-quote")).expect("quote id")
- }
-
- fn typed_bin_id() -> RadrootsInventoryBinId {
- RadrootsInventoryBinId::parse("bin-1").expect("bin id")
- }
-
- fn typed_pubkey(keys: &RadrootsNostrKeys) -> RadrootsPublicKey {
- RadrootsPublicKey::parse(keys.public_key().to_string()).expect("public key")
- }
-
- fn typed_event_id(event: &RadrootsNostrEvent) -> RadrootsEventId {
- RadrootsEventId::parse(event.id.to_string()).expect("event id")
- }
-
- fn listing_event_ptr() -> RadrootsEventPtr {
- RadrootsEventPtr {
- id: listing_event_id().to_string(),
- relays: None,
- }
- }
-
- fn order_economics(order_id: &str) -> RadrootsOrderEconomics {
- RadrootsOrderEconomics {
- quote_id: typed_quote_id(order_id),
- quote_version: 1,
- pricing_basis: RadrootsOrderPricingBasis::ListingEvent,
- currency: RadrootsCoreCurrency::USD,
- items: vec![RadrootsOrderEconomicItem {
- bin_id: typed_bin_id(),
- bin_count: 2,
- quantity_amount: RadrootsCoreDecimal::from(1u32),
- quantity_unit: RadrootsCoreUnit::Each,
- unit_price_amount: RadrootsCoreDecimal::from(5u32),
- unit_price_currency: RadrootsCoreCurrency::USD,
- line_subtotal: RadrootsCoreMoney::new(
- RadrootsCoreDecimal::from(10u32),
- RadrootsCoreCurrency::USD,
- ),
- }],
- discounts: Vec::<RadrootsOrderEconomicLine>::new(),
- adjustments: Vec::<RadrootsOrderEconomicLine>::new(),
- subtotal: RadrootsCoreMoney::new(
- RadrootsCoreDecimal::from(10u32),
- RadrootsCoreCurrency::USD,
- ),
- discount_total: RadrootsCoreMoney::new(
- RadrootsCoreDecimal::from(0u32),
- RadrootsCoreCurrency::USD,
- ),
- adjustment_total: RadrootsCoreMoney::new(
- RadrootsCoreDecimal::from(0u32),
- RadrootsCoreCurrency::USD,
- ),
- total: RadrootsCoreMoney::new(
- RadrootsCoreDecimal::from(10u32),
- RadrootsCoreCurrency::USD,
- ),
- }
- }
-
- fn order_request(
- order_id: &str,
- buyer: &RadrootsNostrKeys,
- seller: &RadrootsNostrKeys,
- ) -> RadrootsOrderRequest {
- RadrootsOrderRequest {
- order_id: typed_order_id(order_id),
- listing_addr: typed_listing_addr(seller),
- buyer_pubkey: typed_pubkey(buyer),
- seller_pubkey: typed_pubkey(seller),
- items: vec![RadrootsOrderItem {
- bin_id: typed_bin_id(),
- bin_count: 2,
- }],
- economics: order_economics(order_id),
- }
- }
-
- fn order_decision(
- order_id: &str,
- buyer: &RadrootsNostrKeys,
- seller: &RadrootsNostrKeys,
- ) -> RadrootsOrderDecision {
- RadrootsOrderDecision {
- order_id: typed_order_id(order_id),
- listing_addr: typed_listing_addr(seller),
- buyer_pubkey: typed_pubkey(buyer),
- seller_pubkey: typed_pubkey(seller),
- decision: RadrootsOrderDecisionOutcome::Accepted {
- inventory_commitments: vec![RadrootsOrderInventoryCommitment {
- bin_id: typed_bin_id(),
- bin_count: 2,
- }],
- },
- }
- }
-
- fn order_cancellation(
- order_id: &str,
- buyer: &RadrootsNostrKeys,
- seller: &RadrootsNostrKeys,
- ) -> RadrootsOrderCancellation {
- RadrootsOrderCancellation {
- order_id: typed_order_id(order_id),
- listing_addr: typed_listing_addr(seller),
- buyer_pubkey: typed_pubkey(buyer),
- seller_pubkey: typed_pubkey(seller),
- reason: "cancel after agreement".to_string(),
- }
- }
-
- fn signed_order_request_event(
- buyer: &RadrootsNostrKeys,
- seller: &RadrootsNostrKeys,
- ) -> RadrootsNostrEvent {
- let payload = order_request("order-1", buyer, seller);
- let wire = order_request_event_build(&listing_event_ptr(), &payload).expect("wire");
- radroots_nostr_build_event(wire.kind, wire.content, wire.tags)
- .expect("builder")
- .sign_with_keys(buyer)
- .expect("event")
- }
-
- fn signed_order_decision_event(
- buyer: &RadrootsNostrKeys,
- seller: &RadrootsNostrKeys,
- request_event: &RadrootsNostrEvent,
- ) -> RadrootsNostrEvent {
- let payload = order_decision("order-1", buyer, seller);
- let root_event_id = typed_event_id(request_event);
- let wire =
- order_decision_event_build(&root_event_id, &root_event_id, &payload).expect("wire");
- radroots_nostr_build_event(wire.kind, wire.content, wire.tags)
- .expect("builder")
- .sign_with_keys(seller)
- .expect("event")
- }
-
- fn signed_order_cancellation_event(
- buyer: &RadrootsNostrKeys,
- seller: &RadrootsNostrKeys,
- request_event: &RadrootsNostrEvent,
- decision_event: &RadrootsNostrEvent,
- ) -> RadrootsNostrEvent {
- let payload = order_cancellation("order-1", buyer, seller);
- let root_event_id = typed_event_id(request_event);
- let prev_event_id = typed_event_id(decision_event);
- let wire =
- order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).expect("wire");
- radroots_nostr_build_event(wire.kind, wire.content, wire.tags)
- .expect("builder")
- .sign_with_keys(buyer)
- .expect("event")
- }
-
- #[tokio::test]
- async fn order_request_inserts_canonical_order_state() {
- let _guard = test_guard().await;
- let worker = RadrootsNostrKeys::generate();
- let buyer = RadrootsNostrKeys::generate();
- let seller = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(worker.clone());
- let runtime = TradeListingRuntime::new();
- let state = runtime.state();
- state.lock().await.upsert_listing_event(
- &listing_addr(&seller),
- listing_event_id(),
- KIND_LISTING,
- );
-
- let request_event = signed_order_request_event(&buyer, &seller);
- handle_event(request_event, Vec::new(), worker, client, runtime.clone())
- .await
- .expect("order request");
-
- let mut state = state.lock().await;
- let order = state.get_order_mut("order-1").expect("order");
- assert_eq!(order.status, RadrootsTradeWorkflowState::Requested);
- assert_eq!(order.buyer_pubkey, buyer.public_key().to_string());
- assert_eq!(order.seller_pubkey, seller.public_key().to_string());
- }
-
- #[tokio::test]
- async fn order_decision_uses_shared_workflow_pending_validation_state() {
- let _guard = test_guard().await;
- let worker = RadrootsNostrKeys::generate();
- let buyer = RadrootsNostrKeys::generate();
- let seller = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(worker.clone());
- let runtime = TradeListingRuntime::new();
- let state = runtime.state();
- state.lock().await.upsert_listing_event(
- &listing_addr(&seller),
- listing_event_id(),
- KIND_LISTING,
- );
- let request_event = signed_order_request_event(&buyer, &seller);
- handle_event(
- request_event.clone(),
- Vec::new(),
- worker.clone(),
- client.clone(),
- runtime.clone(),
- )
- .await
- .expect("order request");
- let decision_event = signed_order_decision_event(&buyer, &seller, &request_event);
- trade_listing_event_test_hooks()
- .lock()
- .expect("hooks")
- .fetch_event_by_id_results
- .push_back(Ok(request_event));
-
- let error = handle_event(decision_event, Vec::new(), worker, client, runtime.clone())
- .await
- .expect_err("missing validator-set policy must fail closed");
- assert!(matches!(error, TradeListingEventError::InvalidPayload(_)));
-
- let mut state = state.lock().await;
- let order = state.get_order_mut("order-1").expect("order");
- assert_eq!(
- order.status,
- RadrootsTradeWorkflowState::AgreedPendingValidation
- );
- }
-
- #[tokio::test]
- async fn cancellation_after_pending_agreement_uses_shared_workflow_invalid_state() {
- let _guard = test_guard().await;
- let worker = RadrootsNostrKeys::generate();
- let buyer = RadrootsNostrKeys::generate();
- let seller = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(worker.clone());
- let runtime = TradeListingRuntime::new();
- let state = runtime.state();
- state.lock().await.upsert_listing_event(
- &listing_addr(&seller),
- listing_event_id(),
- KIND_LISTING,
- );
- let request_event = signed_order_request_event(&buyer, &seller);
- handle_event(
- request_event.clone(),
- Vec::new(),
- worker.clone(),
- client.clone(),
- runtime.clone(),
- )
- .await
- .expect("order request");
- let decision_event = signed_order_decision_event(&buyer, &seller, &request_event);
- trade_listing_event_test_hooks()
- .lock()
- .expect("hooks")
- .fetch_event_by_id_results
- .push_back(Ok(request_event.clone()));
- let decision_error = handle_event(
- decision_event.clone(),
- Vec::new(),
- worker.clone(),
- client.clone(),
- runtime.clone(),
- )
- .await
- .expect_err("missing validator-set policy must fail closed");
- assert!(matches!(
- decision_error,
- TradeListingEventError::InvalidPayload(_)
- ));
- let cancellation_event =
- signed_order_cancellation_event(&buyer, &seller, &request_event, &decision_event);
- {
- let mut hooks = trade_listing_event_test_hooks().lock().expect("hooks");
- hooks.fetch_event_by_id_results.push_back(Ok(request_event));
- hooks
- .fetch_event_by_id_results
- .push_back(Ok(decision_event));
- }
-
- handle_event(
- cancellation_event,
- Vec::new(),
- worker,
- client,
- runtime.clone(),
- )
- .await
- .expect("order cancellation");
-
- let mut state = state.lock().await;
- let order = state.get_order_mut("order-1").expect("order");
- assert_eq!(order.status, RadrootsTradeWorkflowState::Invalid);
- }
-
- #[tokio::test]
- async fn unsupported_kind_is_rejected() {
- let _guard = test_guard().await;
- let worker = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(worker.clone());
- let runtime = TradeListingRuntime::new();
- let event = RadrootsNostrEventBuilder::new(RadrootsNostrKind::Custom(4999), "test")
- .sign_with_keys(&RadrootsNostrKeys::generate())
- .expect("event");
- assert!(matches!(
- handle_event(event, Vec::new(), worker, client, runtime).await,
- Err(TradeListingEventError::UnsupportedKind)
- ));
- }
-
- #[test]
- fn tag_helpers_cover_core_paths() {
- assert!(tag_has_value(
- &[vec!["p".to_string(), "pubkey".to_string()]],
- "p",
- "pubkey"
- ));
- assert!(!tag_has_value(
- &[vec!["p".to_string(), "pubkey".to_string()]],
- "p",
- "other"
- ));
- }
-
- #[tokio::test]
- async fn handle_error_uses_send_hook() {
- let _guard = test_guard().await;
- trade_listing_event_test_hooks()
- .lock()
- .expect("hooks")
- .send_event_results
- .push_back(Ok(()));
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let event = RadrootsNostrEventBuilder::new(
- RadrootsNostrKind::Custom(KIND_ORDER_REQUEST as u16),
- "bad",
- )
- .sign_with_keys(&keys)
- .expect("event");
- assert!(
- handle_error(TradeListingEventError::InvalidOrder, &event, &client)
- .await
- .is_ok()
- );
- }
-}
diff --git a/src/features/trade_listing/handlers/mod.rs b/src/features/trade_listing/handlers/mod.rs
@@ -1 +0,0 @@
-pub mod events;
diff --git a/src/features/trade_listing/mod.rs b/src/features/trade_listing/mod.rs
@@ -1,4 +0,0 @@
-pub mod handlers;
-pub mod processed_jobs;
-pub mod state;
-pub mod subscriber;
diff --git a/src/features/trade_listing/processed_jobs.rs b/src/features/trade_listing/processed_jobs.rs
@@ -1,1762 +0,0 @@
-#![forbid(unsafe_code)]
-
-use serde::{Deserialize, Serialize};
-use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow};
-use sqlx::{Row, SqlitePool};
-use std::path::{Path, PathBuf};
-use std::str::FromStr;
-use std::sync::Arc;
-use thiserror::Error;
-use tokio::sync::OnceCell;
-
-const RHI_PROCESSED_JOB_SCHEMA_VERSION: i64 = 3;
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum RhiProcessedJobStatus {
- Processing,
- ReceiptPublishing,
- ReceiptPublished,
- ResultPublishing,
- Completed,
- Failed,
-}
-
-impl RhiProcessedJobStatus {
- pub const fn as_str(self) -> &'static str {
- match self {
- Self::Processing => "processing",
- Self::ReceiptPublishing => "receipt_publishing",
- Self::ReceiptPublished => "receipt_published",
- Self::ResultPublishing => "result_publishing",
- Self::Completed => "completed",
- Self::Failed => "failed",
- }
- }
-
- fn parse(value: &str) -> Result<Self, RhiProcessedJobStoreError> {
- match value {
- "processing" => Ok(Self::Processing),
- "receipt_publishing" => Ok(Self::ReceiptPublishing),
- "receipt_published" => Ok(Self::ReceiptPublished),
- "result_publishing" => Ok(Self::ResultPublishing),
- "completed" => Ok(Self::Completed),
- "failed" => Ok(Self::Failed),
- _ => Err(RhiProcessedJobStoreError::InvalidStatus(value.to_owned())),
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct RhiProcessedJobState {
- pub request_id: String,
- pub request_kind: u32,
- pub request_hash: String,
- pub customer_pubkey: String,
- pub status: RhiProcessedJobStatus,
- #[serde(default)]
- pub receipt_event_id: Option<String>,
- #[serde(default)]
- pub receipt_event_json: Option<String>,
- #[serde(default)]
- pub result_event_id: Option<String>,
- #[serde(default)]
- pub result_event_json: Option<String>,
- #[serde(default)]
- pub proof_metadata_json: Option<String>,
- #[serde(default)]
- pub error_code: Option<String>,
- pub created_timestamp: u32,
- #[serde(default)]
- pub completed_timestamp: Option<u32>,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum RhiProcessedJobClaim {
- Execute,
- InProgress,
- Failed {
- error_code: String,
- },
- RecoverReceipt {
- receipt_event_id: String,
- receipt_event_json: String,
- },
- RecoverResult {
- receipt_event_id: String,
- receipt_event_json: String,
- result_event_id: Option<String>,
- result_event_json: Option<String>,
- proof_metadata_json: Option<String>,
- },
- Completed,
-}
-
-#[derive(Clone, Debug)]
-pub struct RhiProcessedJobStore {
- pool: SqlitePool,
- file_backed: bool,
- schema_ready: Arc<OnceCell<()>>,
-}
-
-#[derive(Debug, Error)]
-pub enum RhiProcessedJobStoreError {
- #[error("invalid rhi processed-job store path: {0}")]
- InvalidPath(PathBuf),
- #[error("unsupported rhi processed-job store schema version: {0}")]
- UnsupportedSchemaVersion(i64),
- #[error("rhi processed-job store io error: {0}")]
- Io(#[from] std::io::Error),
- #[error("rhi processed-job sqlite error: {0}")]
- Sqlite(#[from] sqlx::Error),
- #[error("duplicate conflicting processed job")]
- DuplicateConflictingJob,
- #[error("duplicate conflicting receipt")]
- DuplicateConflictingReceipt,
- #[error("duplicate conflicting result")]
- DuplicateConflictingResult,
- #[error("receipt publication was not claimed")]
- ReceiptPublicationNotClaimed,
- #[error("result publication was not claimed")]
- ResultPublicationNotClaimed,
- #[error("result recovery is missing stored receipt event json")]
- MissingReceiptEventJson,
- #[error("missing processed-job claim: {0}")]
- MissingProcessedJobClaim(String),
- #[error("invalid rhi processed-job status: {0}")]
- InvalidStatus(String),
- #[error("invalid rhi processed-job stored value: {0}")]
- InvalidStoredValue(&'static str),
-}
-
-impl RhiProcessedJobStore {
- pub fn open_memory() -> Result<Self, RhiProcessedJobStoreError> {
- let options = SqliteConnectOptions::from_str("sqlite::memory:")?;
- Ok(Self::from_options(options, false, 1))
- }
-
- pub async fn open_file(path: impl AsRef<Path>) -> Result<Self, RhiProcessedJobStoreError> {
- let path = path.as_ref();
- if path.file_name().is_none() {
- return Err(RhiProcessedJobStoreError::InvalidPath(path.to_path_buf()));
- }
- if let Some(parent) = path.parent()
- && !parent.as_os_str().is_empty()
- {
- tokio::fs::create_dir_all(parent).await?;
- }
- let options = SqliteConnectOptions::new()
- .filename(path)
- .create_if_missing(true);
- let store = Self::from_options(options, true, 5);
- store.ensure_schema().await?;
- Ok(store)
- }
-
- pub async fn claim_job(
- &self,
- job: &RhiProcessedJobState,
- now_ms: i64,
- lease_ms: i64,
- ) -> Result<RhiProcessedJobClaim, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let claim_expires_at_ms = now_ms.saturating_add(lease_ms.max(1));
- let mut tx = self.pool.begin().await?;
- let inserted = insert_claimed_job(&mut tx, job, now_ms, claim_expires_at_ms).await?;
- if inserted {
- tx.commit().await?;
- return Ok(RhiProcessedJobClaim::Execute);
- }
-
- let Some(existing) = select_job(&mut tx, job.request_id.as_str()).await? else {
- return Err(RhiProcessedJobStoreError::MissingProcessedJobClaim(
- job.request_id.clone(),
- ));
- };
- ensure_processed_job_matches(&existing, job)?;
- let claim = claim_for_existing_job(&mut tx, existing, now_ms, claim_expires_at_ms).await?;
- tx.commit().await?;
- Ok(claim)
- }
-
- pub async fn mark_receipt_publishing(
- &self,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- receipt_event_json: &str,
- proof_metadata_json: Option<&str>,
- now_ms: i64,
- ) -> Result<RhiProcessedJobState, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let mut tx = self.pool.begin().await?;
- let Some(mut existing) = select_job(&mut tx, job.request_id.as_str()).await? else {
- return Err(RhiProcessedJobStoreError::MissingProcessedJobClaim(
- job.request_id.clone(),
- ));
- };
- ensure_processed_job_matches(&existing, job)?;
- ensure_receipt_matches(&existing, receipt_event_id)?;
- ensure_receipt_event_json_matches(&existing, receipt_event_json)?;
- ensure_proof_metadata_matches(&existing, proof_metadata_json)?;
- existing.status = RhiProcessedJobStatus::ReceiptPublishing;
- existing.receipt_event_id = Some(receipt_event_id.to_owned());
- existing.receipt_event_json = Some(receipt_event_json.to_owned());
- existing.proof_metadata_json = proof_metadata_json.map(ToOwned::to_owned);
- update_job_without_claim_change(&mut tx, &existing, now_ms).await?;
- tx.commit().await?;
- Ok(existing)
- }
-
- pub async fn mark_receipt_published(
- &self,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- now_ms: i64,
- ) -> Result<RhiProcessedJobState, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let mut tx = self.pool.begin().await?;
- let Some(mut existing) = select_job(&mut tx, job.request_id.as_str()).await? else {
- return Err(RhiProcessedJobStoreError::MissingProcessedJobClaim(
- job.request_id.clone(),
- ));
- };
- ensure_processed_job_matches(&existing, job)?;
- ensure_receipt_matches(&existing, receipt_event_id)?;
- if existing.status != RhiProcessedJobStatus::ReceiptPublishing
- || existing.receipt_event_json.is_none()
- {
- return Err(RhiProcessedJobStoreError::ReceiptPublicationNotClaimed);
- }
- existing.status = RhiProcessedJobStatus::ReceiptPublished;
- existing.receipt_event_id = Some(receipt_event_id.to_owned());
- update_job(&mut tx, &existing, now_ms, None).await?;
- tx.commit().await?;
- Ok(existing)
- }
-
- pub async fn mark_receipt_completed(
- &self,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- receipt_event_json: &str,
- completed_timestamp: u32,
- now_ms: i64,
- ) -> Result<RhiProcessedJobState, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let mut tx = self.pool.begin().await?;
- let Some(mut existing) = select_job(&mut tx, job.request_id.as_str()).await? else {
- return Err(RhiProcessedJobStoreError::MissingProcessedJobClaim(
- job.request_id.clone(),
- ));
- };
- ensure_processed_job_matches(&existing, job)?;
- ensure_receipt_matches(&existing, receipt_event_id)?;
- ensure_receipt_event_json_matches(&existing, receipt_event_json)?;
- ensure_result_matches(&existing, receipt_event_id)?;
- ensure_result_event_json_matches(&existing, receipt_event_json)?;
- if existing.status == RhiProcessedJobStatus::Completed {
- tx.commit().await?;
- return Ok(existing);
- }
- if !matches!(
- existing.status,
- RhiProcessedJobStatus::ReceiptPublished | RhiProcessedJobStatus::ResultPublishing
- ) || existing.receipt_event_id.is_none()
- {
- return Err(RhiProcessedJobStoreError::ReceiptPublicationNotClaimed);
- }
- existing.status = RhiProcessedJobStatus::Completed;
- existing.receipt_event_id = Some(receipt_event_id.to_owned());
- existing.receipt_event_json = Some(receipt_event_json.to_owned());
- existing.result_event_id = Some(receipt_event_id.to_owned());
- existing.result_event_json = Some(receipt_event_json.to_owned());
- existing.completed_timestamp = Some(completed_timestamp);
- update_job(&mut tx, &existing, now_ms, None).await?;
- tx.commit().await?;
- Ok(existing)
- }
-
- pub async fn mark_completed(
- &self,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- result_event_id: &str,
- completed_timestamp: u32,
- now_ms: i64,
- ) -> Result<RhiProcessedJobState, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let mut tx = self.pool.begin().await?;
- let Some(mut existing) = select_job(&mut tx, job.request_id.as_str()).await? else {
- return Err(RhiProcessedJobStoreError::MissingProcessedJobClaim(
- job.request_id.clone(),
- ));
- };
- ensure_processed_job_matches(&existing, job)?;
- ensure_receipt_matches(&existing, receipt_event_id)?;
- ensure_result_matches(&existing, result_event_id)?;
- if existing.status == RhiProcessedJobStatus::Completed {
- tx.commit().await?;
- return Ok(existing);
- }
- if existing.status != RhiProcessedJobStatus::ResultPublishing
- || existing.result_event_id.is_none()
- {
- return Err(RhiProcessedJobStoreError::ResultPublicationNotClaimed);
- }
- existing.status = RhiProcessedJobStatus::Completed;
- existing.receipt_event_id = Some(receipt_event_id.to_owned());
- existing.result_event_id = Some(result_event_id.to_owned());
- existing.completed_timestamp = Some(completed_timestamp);
- update_job(&mut tx, &existing, now_ms, None).await?;
- tx.commit().await?;
- Ok(existing)
- }
-
- pub async fn mark_result_publishing(
- &self,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- result_event_id: &str,
- result_event_json: &str,
- now_ms: i64,
- ) -> Result<RhiProcessedJobState, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let mut tx = self.pool.begin().await?;
- let Some(mut existing) = select_job(&mut tx, job.request_id.as_str()).await? else {
- return Err(RhiProcessedJobStoreError::MissingProcessedJobClaim(
- job.request_id.clone(),
- ));
- };
- ensure_processed_job_matches(&existing, job)?;
- ensure_receipt_matches(&existing, receipt_event_id)?;
- ensure_result_matches(&existing, result_event_id)?;
- ensure_result_event_json_matches(&existing, result_event_json)?;
- if existing.status == RhiProcessedJobStatus::Completed {
- tx.commit().await?;
- return Ok(existing);
- }
- if existing.status != RhiProcessedJobStatus::ResultPublishing {
- return Err(RhiProcessedJobStoreError::ResultPublicationNotClaimed);
- }
- existing.receipt_event_id = Some(receipt_event_id.to_owned());
- existing.result_event_id = Some(result_event_id.to_owned());
- existing.result_event_json = Some(result_event_json.to_owned());
- sqlx::query(
- "UPDATE rhi_processed_jobs
- SET receipt_event_id = ?,
- result_event_id = ?,
- result_event_json = ?,
- updated_at_ms = ?
- WHERE request_id = ?",
- )
- .bind(existing.receipt_event_id.as_deref())
- .bind(existing.result_event_id.as_deref())
- .bind(existing.result_event_json.as_deref())
- .bind(now_ms)
- .bind(existing.request_id.as_str())
- .execute(&mut *tx)
- .await?;
- tx.commit().await?;
- Ok(existing)
- }
-
- pub async fn get_job(
- &self,
- request_id: &str,
- ) -> Result<Option<RhiProcessedJobState>, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- let mut tx = self.pool.begin().await?;
- let job = select_job(&mut tx, request_id).await?;
- tx.commit().await?;
- Ok(job)
- }
-
- pub async fn pragma_busy_timeout(&self) -> Result<i64, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- query_i64(&self.pool, "PRAGMA busy_timeout").await
- }
-
- pub async fn pragma_journal_mode(&self) -> Result<String, RhiProcessedJobStoreError> {
- self.ensure_schema().await?;
- query_string(&self.pool, "PRAGMA journal_mode").await
- }
-
- fn from_options(
- options: SqliteConnectOptions,
- file_backed: bool,
- max_connections: u32,
- ) -> Self {
- let pool = SqlitePoolOptions::new()
- .max_connections(max_connections)
- .connect_lazy_with(options);
- Self {
- pool,
- file_backed,
- schema_ready: Arc::new(OnceCell::new()),
- }
- }
-
- async fn ensure_schema(&self) -> Result<(), RhiProcessedJobStoreError> {
- self.schema_ready
- .get_or_try_init(|| async {
- configure_connection(&self.pool, self.file_backed).await?;
- apply_schema(&self.pool).await
- })
- .await?;
- Ok(())
- }
-}
-
-async fn configure_connection(
- pool: &SqlitePool,
- file_backed: bool,
-) -> Result<(), RhiProcessedJobStoreError> {
- sqlx::query("PRAGMA foreign_keys = ON")
- .execute(pool)
- .await?;
- sqlx::query("PRAGMA busy_timeout = 5000")
- .execute(pool)
- .await?;
- if file_backed {
- sqlx::query("PRAGMA journal_mode = WAL")
- .execute(pool)
- .await?;
- }
- Ok(())
-}
-
-async fn apply_schema(pool: &SqlitePool) -> Result<(), RhiProcessedJobStoreError> {
- sqlx::query(
- "CREATE TABLE IF NOT EXISTS rhi_processed_job_schema(
- schema_id INTEGER PRIMARY KEY CHECK(schema_id = 1),
- version INTEGER NOT NULL
- )",
- )
- .execute(pool)
- .await?;
- let existing_version: Option<i64> =
- sqlx::query("SELECT version FROM rhi_processed_job_schema WHERE schema_id = 1")
- .fetch_optional(pool)
- .await?
- .map(|row| row.try_get("version"))
- .transpose()?;
- match existing_version {
- Some(version) if version == RHI_PROCESSED_JOB_SCHEMA_VERSION => {}
- Some(version) => return Err(RhiProcessedJobStoreError::UnsupportedSchemaVersion(version)),
- None => {
- sqlx::query("INSERT INTO rhi_processed_job_schema(schema_id, version) VALUES (1, ?)")
- .bind(RHI_PROCESSED_JOB_SCHEMA_VERSION)
- .execute(pool)
- .await?;
- }
- }
- sqlx::query(
- "CREATE TABLE IF NOT EXISTS rhi_processed_jobs(
- request_id TEXT PRIMARY KEY,
- request_kind INTEGER NOT NULL,
- request_hash TEXT NOT NULL,
- customer_pubkey TEXT NOT NULL,
- status TEXT NOT NULL,
- receipt_event_id TEXT,
- receipt_event_json TEXT,
- result_event_id TEXT,
- result_event_json TEXT,
- proof_metadata_json TEXT,
- error_code TEXT,
- created_timestamp INTEGER NOT NULL,
- completed_timestamp INTEGER,
- claim_expires_at_ms INTEGER,
- inserted_at_ms INTEGER NOT NULL,
- updated_at_ms INTEGER NOT NULL,
- CHECK (
- (receipt_event_id IS NULL AND receipt_event_json IS NULL)
- OR (receipt_event_id IS NOT NULL AND receipt_event_json IS NOT NULL)
- ),
- CHECK (
- (result_event_id IS NULL AND result_event_json IS NULL)
- OR (result_event_id IS NOT NULL AND result_event_json IS NOT NULL)
- ),
- CHECK (
- proof_metadata_json IS NULL
- OR receipt_event_id IS NOT NULL
- ),
- CHECK (
- (
- status = 'processing'
- AND receipt_event_id IS NULL
- AND receipt_event_json IS NULL
- AND result_event_id IS NULL
- AND result_event_json IS NULL
- AND proof_metadata_json IS NULL
- AND error_code IS NULL
- AND completed_timestamp IS NULL
- AND claim_expires_at_ms IS NOT NULL
- )
- OR (
- status = 'receipt_publishing'
- AND receipt_event_id IS NOT NULL
- AND receipt_event_json IS NOT NULL
- AND result_event_id IS NULL
- AND result_event_json IS NULL
- AND error_code IS NULL
- AND completed_timestamp IS NULL
- AND claim_expires_at_ms IS NOT NULL
- )
- OR (
- status = 'receipt_published'
- AND receipt_event_id IS NOT NULL
- AND receipt_event_json IS NOT NULL
- AND result_event_id IS NULL
- AND result_event_json IS NULL
- AND error_code IS NULL
- AND completed_timestamp IS NULL
- AND claim_expires_at_ms IS NULL
- )
- OR (
- status = 'result_publishing'
- AND receipt_event_id IS NOT NULL
- AND receipt_event_json IS NOT NULL
- AND error_code IS NULL
- AND completed_timestamp IS NULL
- AND claim_expires_at_ms IS NOT NULL
- )
- OR (
- status = 'completed'
- AND receipt_event_id IS NOT NULL
- AND receipt_event_json IS NOT NULL
- AND result_event_id IS NOT NULL
- AND result_event_json IS NOT NULL
- AND error_code IS NULL
- AND completed_timestamp IS NOT NULL
- AND claim_expires_at_ms IS NULL
- )
- OR (
- status = 'failed'
- AND receipt_event_id IS NULL
- AND receipt_event_json IS NULL
- AND result_event_id IS NULL
- AND result_event_json IS NULL
- AND proof_metadata_json IS NULL
- AND error_code IS NOT NULL
- AND completed_timestamp IS NOT NULL
- AND claim_expires_at_ms IS NULL
- )
- )
- )",
- )
- .execute(pool)
- .await?;
- sqlx::query(
- "CREATE INDEX IF NOT EXISTS rhi_processed_jobs_status_idx
- ON rhi_processed_jobs(status)",
- )
- .execute(pool)
- .await?;
- sqlx::query(
- "CREATE UNIQUE INDEX IF NOT EXISTS rhi_processed_jobs_receipt_event_idx
- ON rhi_processed_jobs(receipt_event_id)
- WHERE receipt_event_id IS NOT NULL",
- )
- .execute(pool)
- .await?;
- sqlx::query(
- "CREATE UNIQUE INDEX IF NOT EXISTS rhi_processed_jobs_result_event_idx
- ON rhi_processed_jobs(result_event_id)
- WHERE result_event_id IS NOT NULL",
- )
- .execute(pool)
- .await?;
- Ok(())
-}
-
-async fn insert_claimed_job(
- tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
- job: &RhiProcessedJobState,
- now_ms: i64,
- claim_expires_at_ms: i64,
-) -> Result<bool, RhiProcessedJobStoreError> {
- let changed = sqlx::query(
- "INSERT INTO rhi_processed_jobs(
- request_id,
- request_kind,
- request_hash,
- customer_pubkey,
- status,
- receipt_event_id,
- receipt_event_json,
- result_event_id,
- result_event_json,
- proof_metadata_json,
- error_code,
- created_timestamp,
- completed_timestamp,
- claim_expires_at_ms,
- inserted_at_ms,
- updated_at_ms
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- ON CONFLICT(request_id) DO NOTHING",
- )
- .bind(job.request_id.as_str())
- .bind(i64::from(job.request_kind))
- .bind(job.request_hash.as_str())
- .bind(job.customer_pubkey.as_str())
- .bind(RhiProcessedJobStatus::Processing.as_str())
- .bind(job.receipt_event_id.as_deref())
- .bind(job.receipt_event_json.as_deref())
- .bind(job.result_event_id.as_deref())
- .bind(job.result_event_json.as_deref())
- .bind(job.proof_metadata_json.as_deref())
- .bind(job.error_code.as_deref())
- .bind(i64::from(job.created_timestamp))
- .bind(job.completed_timestamp.map(i64::from))
- .bind(claim_expires_at_ms)
- .bind(now_ms)
- .bind(now_ms)
- .execute(&mut **tx)
- .await?
- .rows_affected();
- Ok(changed == 1)
-}
-
-async fn select_job(
- tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
- request_id: &str,
-) -> Result<Option<RhiProcessedJobState>, RhiProcessedJobStoreError> {
- let job = sqlx::query(
- "SELECT
- request_id,
- request_kind,
- request_hash,
- customer_pubkey,
- status,
- receipt_event_id,
- receipt_event_json,
- result_event_id,
- result_event_json,
- proof_metadata_json,
- error_code,
- created_timestamp,
- completed_timestamp
- FROM rhi_processed_jobs
- WHERE request_id = ?",
- )
- .bind(request_id)
- .fetch_optional(&mut **tx)
- .await?
- .map(job_from_row)
- .transpose()?;
- if let Some(job) = job.as_ref() {
- validate_stored_job_state(job)?;
- }
- Ok(job)
-}
-
-async fn claim_for_existing_job(
- tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
- existing: RhiProcessedJobState,
- now_ms: i64,
- claim_expires_at_ms: i64,
-) -> Result<RhiProcessedJobClaim, RhiProcessedJobStoreError> {
- if existing.status == RhiProcessedJobStatus::Completed && existing.result_event_id.is_some() {
- return Ok(RhiProcessedJobClaim::Completed);
- }
- if existing.status == RhiProcessedJobStatus::Failed {
- return Ok(RhiProcessedJobClaim::Failed {
- error_code: existing.error_code.clone().ok_or(
- RhiProcessedJobStoreError::InvalidStoredValue("failed_error_code"),
- )?,
- });
- }
- if let Some(receipt_event_id) = existing.receipt_event_id.clone() {
- let current_claim_expires_at_ms =
- select_claim_expires_at_ms(tx, existing.request_id.as_str()).await?;
- if matches!(
- existing.status,
- RhiProcessedJobStatus::ReceiptPublishing | RhiProcessedJobStatus::ResultPublishing
- ) && current_claim_expires_at_ms.is_some_and(|expires_at_ms| expires_at_ms > now_ms)
- {
- return Ok(RhiProcessedJobClaim::InProgress);
- }
- if existing.status == RhiProcessedJobStatus::ReceiptPublishing {
- let receipt_event_json = existing
- .receipt_event_json
- .clone()
- .ok_or(RhiProcessedJobStoreError::ReceiptPublicationNotClaimed)?;
- let changed = sqlx::query(
- "UPDATE rhi_processed_jobs
- SET claim_expires_at_ms = ?,
- updated_at_ms = ?
- WHERE request_id = ?
- AND receipt_event_id = ?
- AND status = ?
- AND (
- claim_expires_at_ms IS NULL
- OR claim_expires_at_ms <= ?
- )",
- )
- .bind(claim_expires_at_ms)
- .bind(now_ms)
- .bind(existing.request_id.as_str())
- .bind(receipt_event_id.as_str())
- .bind(RhiProcessedJobStatus::ReceiptPublishing.as_str())
- .bind(now_ms)
- .execute(&mut **tx)
- .await?
- .rows_affected();
- if changed == 1 {
- return Ok(RhiProcessedJobClaim::RecoverReceipt {
- receipt_event_id,
- receipt_event_json,
- });
- }
- return Ok(RhiProcessedJobClaim::InProgress);
- }
- let receipt_event_json = existing
- .receipt_event_json
- .clone()
- .ok_or(RhiProcessedJobStoreError::MissingReceiptEventJson)?;
- let changed = sqlx::query(
- "UPDATE rhi_processed_jobs
- SET status = ?,
- claim_expires_at_ms = ?,
- updated_at_ms = ?
- WHERE request_id = ?
- AND receipt_event_id = ?
- AND status != ?
- AND (
- status != ?
- OR claim_expires_at_ms IS NULL
- OR claim_expires_at_ms <= ?
- )",
- )
- .bind(RhiProcessedJobStatus::ResultPublishing.as_str())
- .bind(claim_expires_at_ms)
- .bind(now_ms)
- .bind(existing.request_id.as_str())
- .bind(receipt_event_id.as_str())
- .bind(RhiProcessedJobStatus::Completed.as_str())
- .bind(RhiProcessedJobStatus::ResultPublishing.as_str())
- .bind(now_ms)
- .execute(&mut **tx)
- .await?
- .rows_affected();
- if changed == 1 {
- return Ok(RhiProcessedJobClaim::RecoverResult {
- receipt_event_id,
- receipt_event_json,
- result_event_id: existing.result_event_id,
- result_event_json: existing.result_event_json,
- proof_metadata_json: existing.proof_metadata_json,
- });
- }
- return Ok(RhiProcessedJobClaim::InProgress);
- }
-
- let current_claim_expires_at_ms: Option<i64> =
- sqlx::query("SELECT claim_expires_at_ms FROM rhi_processed_jobs WHERE request_id = ?")
- .bind(existing.request_id.as_str())
- .fetch_one(&mut **tx)
- .await?
- .try_get("claim_expires_at_ms")?;
- if existing.status == RhiProcessedJobStatus::Processing
- && current_claim_expires_at_ms.is_some_and(|expires_at_ms| expires_at_ms > now_ms)
- {
- return Ok(RhiProcessedJobClaim::InProgress);
- }
-
- let changed = sqlx::query(
- "UPDATE rhi_processed_jobs
- SET status = ?,
- claim_expires_at_ms = ?,
- updated_at_ms = ?
- WHERE request_id = ?
- AND (
- status != ?
- OR claim_expires_at_ms IS NULL
- OR claim_expires_at_ms <= ?
- )",
- )
- .bind(RhiProcessedJobStatus::Processing.as_str())
- .bind(claim_expires_at_ms)
- .bind(now_ms)
- .bind(existing.request_id.as_str())
- .bind(RhiProcessedJobStatus::Processing.as_str())
- .bind(now_ms)
- .execute(&mut **tx)
- .await?
- .rows_affected();
- if changed == 1 {
- Ok(RhiProcessedJobClaim::Execute)
- } else {
- Ok(RhiProcessedJobClaim::InProgress)
- }
-}
-
-async fn update_job(
- tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
- job: &RhiProcessedJobState,
- now_ms: i64,
- claim_expires_at_ms: Option<i64>,
-) -> Result<(), RhiProcessedJobStoreError> {
- sqlx::query(
- "UPDATE rhi_processed_jobs
- SET request_kind = ?,
- request_hash = ?,
- customer_pubkey = ?,
- status = ?,
- receipt_event_id = ?,
- receipt_event_json = ?,
- result_event_id = ?,
- result_event_json = ?,
- proof_metadata_json = ?,
- error_code = ?,
- created_timestamp = ?,
- completed_timestamp = ?,
- claim_expires_at_ms = ?,
- updated_at_ms = ?
- WHERE request_id = ?",
- )
- .bind(i64::from(job.request_kind))
- .bind(job.request_hash.as_str())
- .bind(job.customer_pubkey.as_str())
- .bind(job.status.as_str())
- .bind(job.receipt_event_id.as_deref())
- .bind(job.receipt_event_json.as_deref())
- .bind(job.result_event_id.as_deref())
- .bind(job.result_event_json.as_deref())
- .bind(job.proof_metadata_json.as_deref())
- .bind(job.error_code.as_deref())
- .bind(i64::from(job.created_timestamp))
- .bind(job.completed_timestamp.map(i64::from))
- .bind(claim_expires_at_ms)
- .bind(now_ms)
- .bind(job.request_id.as_str())
- .execute(&mut **tx)
- .await?;
- Ok(())
-}
-
-async fn update_job_without_claim_change(
- tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
- job: &RhiProcessedJobState,
- now_ms: i64,
-) -> Result<(), RhiProcessedJobStoreError> {
- sqlx::query(
- "UPDATE rhi_processed_jobs
- SET request_kind = ?,
- request_hash = ?,
- customer_pubkey = ?,
- status = ?,
- receipt_event_id = ?,
- receipt_event_json = ?,
- result_event_id = ?,
- result_event_json = ?,
- proof_metadata_json = ?,
- error_code = ?,
- created_timestamp = ?,
- completed_timestamp = ?,
- updated_at_ms = ?
- WHERE request_id = ?",
- )
- .bind(i64::from(job.request_kind))
- .bind(job.request_hash.as_str())
- .bind(job.customer_pubkey.as_str())
- .bind(job.status.as_str())
- .bind(job.receipt_event_id.as_deref())
- .bind(job.receipt_event_json.as_deref())
- .bind(job.result_event_id.as_deref())
- .bind(job.result_event_json.as_deref())
- .bind(job.proof_metadata_json.as_deref())
- .bind(job.error_code.as_deref())
- .bind(i64::from(job.created_timestamp))
- .bind(job.completed_timestamp.map(i64::from))
- .bind(now_ms)
- .bind(job.request_id.as_str())
- .execute(&mut **tx)
- .await?;
- Ok(())
-}
-
-fn ensure_processed_job_matches(
- existing: &RhiProcessedJobState,
- incoming: &RhiProcessedJobState,
-) -> Result<(), RhiProcessedJobStoreError> {
- if existing.request_kind != incoming.request_kind
- || existing.request_hash != incoming.request_hash
- || existing.customer_pubkey != incoming.customer_pubkey
- {
- return Err(RhiProcessedJobStoreError::DuplicateConflictingJob);
- }
- Ok(())
-}
-
-fn validate_stored_job_state(job: &RhiProcessedJobState) -> Result<(), RhiProcessedJobStoreError> {
- validate_stored_event_pair(
- job.receipt_event_id.as_ref(),
- job.receipt_event_json.as_ref(),
- "receipt_event",
- )?;
- validate_stored_event_pair(
- job.result_event_id.as_ref(),
- job.result_event_json.as_ref(),
- "result_event",
- )?;
- if job.proof_metadata_json.is_some() && job.receipt_event_id.is_none() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "proof_metadata_without_receipt",
- ));
- }
- match job.status {
- RhiProcessedJobStatus::Processing => {
- validate_no_receipt(job)?;
- validate_no_result(job)?;
- validate_no_terminal_fields(job)?;
- if job.proof_metadata_json.is_some() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "processing_proof_metadata",
- ));
- }
- }
- RhiProcessedJobStatus::ReceiptPublishing | RhiProcessedJobStatus::ReceiptPublished => {
- validate_receipt(job)?;
- validate_no_result(job)?;
- validate_no_terminal_fields(job)?;
- }
- RhiProcessedJobStatus::ResultPublishing => {
- validate_receipt(job)?;
- validate_no_terminal_fields(job)?;
- }
- RhiProcessedJobStatus::Completed => {
- validate_receipt(job)?;
- validate_result(job)?;
- if job.error_code.is_some() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "completed_error_code",
- ));
- }
- if job.completed_timestamp.is_none() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "completed_timestamp",
- ));
- }
- }
- RhiProcessedJobStatus::Failed => {
- validate_no_receipt(job)?;
- validate_no_result(job)?;
- if job.proof_metadata_json.is_some() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "failed_proof_metadata",
- ));
- }
- if job.error_code.is_none() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "failed_error_code",
- ));
- }
- if job.completed_timestamp.is_none() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "failed_completed_timestamp",
- ));
- }
- }
- }
- Ok(())
-}
-
-fn validate_stored_event_pair(
- event_id: Option<&String>,
- event_json: Option<&String>,
- field: &'static str,
-) -> Result<(), RhiProcessedJobStoreError> {
- if event_id.is_some() == event_json.is_some() {
- Ok(())
- } else {
- Err(RhiProcessedJobStoreError::InvalidStoredValue(field))
- }
-}
-
-fn validate_receipt(job: &RhiProcessedJobState) -> Result<(), RhiProcessedJobStoreError> {
- if job.receipt_event_id.is_some() && job.receipt_event_json.is_some() {
- Ok(())
- } else {
- Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "receipt_event",
- ))
- }
-}
-
-fn validate_result(job: &RhiProcessedJobState) -> Result<(), RhiProcessedJobStoreError> {
- if job.result_event_id.is_some() && job.result_event_json.is_some() {
- Ok(())
- } else {
- Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "result_event",
- ))
- }
-}
-
-fn validate_no_receipt(job: &RhiProcessedJobState) -> Result<(), RhiProcessedJobStoreError> {
- if job.receipt_event_id.is_none() && job.receipt_event_json.is_none() {
- Ok(())
- } else {
- Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "unexpected_receipt_event",
- ))
- }
-}
-
-fn validate_no_result(job: &RhiProcessedJobState) -> Result<(), RhiProcessedJobStoreError> {
- if job.result_event_id.is_none() && job.result_event_json.is_none() {
- Ok(())
- } else {
- Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "unexpected_result_event",
- ))
- }
-}
-
-fn validate_no_terminal_fields(
- job: &RhiProcessedJobState,
-) -> Result<(), RhiProcessedJobStoreError> {
- if job.error_code.is_some() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "active_error_code",
- ));
- }
- if job.completed_timestamp.is_some() {
- return Err(RhiProcessedJobStoreError::InvalidStoredValue(
- "active_completed_timestamp",
- ));
- }
- Ok(())
-}
-
-fn ensure_receipt_matches(
- existing: &RhiProcessedJobState,
- receipt_event_id: &str,
-) -> Result<(), RhiProcessedJobStoreError> {
- if existing
- .receipt_event_id
- .as_ref()
- .is_some_and(|existing| existing != receipt_event_id)
- {
- return Err(RhiProcessedJobStoreError::DuplicateConflictingReceipt);
- }
- Ok(())
-}
-
-fn ensure_receipt_event_json_matches(
- existing: &RhiProcessedJobState,
- receipt_event_json: &str,
-) -> Result<(), RhiProcessedJobStoreError> {
- if existing
- .receipt_event_json
- .as_ref()
- .is_some_and(|existing| existing != receipt_event_json)
- {
- return Err(RhiProcessedJobStoreError::DuplicateConflictingReceipt);
- }
- Ok(())
-}
-
-fn ensure_result_matches(
- existing: &RhiProcessedJobState,
- result_event_id: &str,
-) -> Result<(), RhiProcessedJobStoreError> {
- if existing
- .result_event_id
- .as_ref()
- .is_some_and(|existing| existing != result_event_id)
- {
- return Err(RhiProcessedJobStoreError::DuplicateConflictingResult);
- }
- Ok(())
-}
-
-fn ensure_result_event_json_matches(
- existing: &RhiProcessedJobState,
- result_event_json: &str,
-) -> Result<(), RhiProcessedJobStoreError> {
- if existing
- .result_event_json
- .as_ref()
- .is_some_and(|existing| existing != result_event_json)
- {
- return Err(RhiProcessedJobStoreError::DuplicateConflictingResult);
- }
- Ok(())
-}
-
-fn ensure_proof_metadata_matches(
- existing: &RhiProcessedJobState,
- proof_metadata_json: Option<&str>,
-) -> Result<(), RhiProcessedJobStoreError> {
- if existing
- .proof_metadata_json
- .as_deref()
- .is_some_and(|existing| Some(existing) != proof_metadata_json)
- {
- return Err(RhiProcessedJobStoreError::DuplicateConflictingReceipt);
- }
- Ok(())
-}
-
-async fn select_claim_expires_at_ms(
- tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
- request_id: &str,
-) -> Result<Option<i64>, RhiProcessedJobStoreError> {
- sqlx::query("SELECT claim_expires_at_ms FROM rhi_processed_jobs WHERE request_id = ?")
- .bind(request_id)
- .fetch_one(&mut **tx)
- .await?
- .try_get("claim_expires_at_ms")
- .map_err(Into::into)
-}
-
-fn job_from_row(row: SqliteRow) -> Result<RhiProcessedJobState, RhiProcessedJobStoreError> {
- Ok(RhiProcessedJobState {
- request_id: row.try_get("request_id")?,
- request_kind: u32_from_i64(row.try_get("request_kind")?, "request_kind")?,
- request_hash: row.try_get("request_hash")?,
- customer_pubkey: row.try_get("customer_pubkey")?,
- status: RhiProcessedJobStatus::parse(row.try_get::<String, _>("status")?.as_str())?,
- receipt_event_id: row.try_get("receipt_event_id")?,
- receipt_event_json: row.try_get("receipt_event_json")?,
- result_event_id: row.try_get("result_event_id")?,
- result_event_json: row.try_get("result_event_json")?,
- proof_metadata_json: row.try_get("proof_metadata_json")?,
- error_code: row.try_get("error_code")?,
- created_timestamp: u32_from_i64(row.try_get("created_timestamp")?, "created_timestamp")?,
- completed_timestamp: row
- .try_get::<Option<i64>, _>("completed_timestamp")?
- .map(|value| u32_from_i64(value, "completed_timestamp"))
- .transpose()?,
- })
-}
-
-fn u32_from_i64(value: i64, field: &'static str) -> Result<u32, RhiProcessedJobStoreError> {
- u32::try_from(value).map_err(|_| RhiProcessedJobStoreError::InvalidStoredValue(field))
-}
-
-async fn query_i64(pool: &SqlitePool, sql: &'static str) -> Result<i64, RhiProcessedJobStoreError> {
- let row = sqlx::query(sql).fetch_one(pool).await?;
- Ok(row.try_get(0)?)
-}
-
-async fn query_string(
- pool: &SqlitePool,
- sql: &'static str,
-) -> Result<String, RhiProcessedJobStoreError> {
- let row = sqlx::query(sql).fetch_one(pool).await?;
- Ok(row.try_get(0)?)
-}
-
-#[cfg(test)]
-#[cfg_attr(coverage_nightly, coverage(off))]
-mod tests {
- use super::{
- RhiProcessedJobClaim, RhiProcessedJobState, RhiProcessedJobStatus, RhiProcessedJobStore,
- RhiProcessedJobStoreError,
- };
-
- fn job(request_id: &str) -> RhiProcessedJobState {
- RhiProcessedJobState {
- request_id: request_id.to_owned(),
- request_kind: 5322,
- request_hash: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- .to_owned(),
- customer_pubkey: "customer".to_owned(),
- status: RhiProcessedJobStatus::Processing,
- receipt_event_id: None,
- receipt_event_json: None,
- result_event_id: None,
- result_event_json: None,
- proof_metadata_json: None,
- error_code: None,
- created_timestamp: 1_700_000_000,
- completed_timestamp: None,
- }
- }
-
- fn receipt_json(value: &str) -> String {
- format!(r#"{{"kind":"receipt","value":"{value}"}}"#)
- }
-
- fn result_json(value: &str) -> String {
- format!(r#"{{"kind":"result","value":"{value}"}}"#)
- }
-
- fn proof_json(value: &str) -> String {
- format!(r#"{{"proof":"{value}"}}"#)
- }
-
- async fn disable_store_constraints(store: &RhiProcessedJobStore) {
- sqlx::query("PRAGMA ignore_check_constraints = ON")
- .execute(&store.pool)
- .await
- .expect("disable check constraints");
- }
-
- async fn enable_store_constraints(store: &RhiProcessedJobStore) {
- sqlx::query("PRAGMA ignore_check_constraints = OFF")
- .execute(&store.pool)
- .await
- .expect("enable check constraints");
- }
-
- async fn corrupt_job(
- store: &RhiProcessedJobStore,
- job: &RhiProcessedJobState,
- sql: &'static str,
- ) {
- disable_store_constraints(store).await;
- sqlx::query(sql)
- .bind(job.request_id.as_str())
- .execute(&store.pool)
- .await
- .expect("corrupt job");
- enable_store_constraints(store).await;
- }
-
- async fn complete_job(store: &RhiProcessedJobStore, job: &RhiProcessedJobState) {
- store.claim_job(job, 10, 100).await.expect("claim");
- store
- .mark_receipt_publishing(
- job,
- "receipt-complete",
- receipt_json("complete").as_str(),
- None,
- 20,
- )
- .await
- .expect("receipt intent");
- store
- .mark_receipt_published(job, "receipt-complete", 30)
- .await
- .expect("receipt published");
- store.claim_job(job, 131, 100).await.expect("result claim");
- store
- .mark_result_publishing(
- job,
- "receipt-complete",
- "result-complete",
- result_json("complete").as_str(),
- 140,
- )
- .await
- .expect("result intent");
- store
- .mark_completed(
- job,
- "receipt-complete",
- "result-complete",
- 1_700_000_001,
- 150,
- )
- .await
- .expect("complete");
- }
-
- #[tokio::test]
- async fn processed_job_store_claims_updates_and_reopens_completed_jobs() {
- let tempdir = tempfile::tempdir().expect("tempdir");
- let path = tempdir.path().join("processed_jobs.sqlite");
- let store = RhiProcessedJobStore::open_file(path.as_path())
- .await
- .expect("store");
- assert_eq!(store.pragma_busy_timeout().await.expect("timeout"), 5000);
- assert_eq!(
- store.pragma_journal_mode().await.expect("journal"),
- "wal".to_owned()
- );
- let job = job("request-1");
-
- assert_eq!(
- store.claim_job(&job, 1_000, 10_000).await.expect("claim"),
- RhiProcessedJobClaim::Execute
- );
- let receipt_intent = store
- .mark_receipt_publishing(
- &job,
- "receipt-1",
- receipt_json("one").as_str(),
- Some(proof_json("one").as_str()),
- 1_050,
- )
- .await
- .expect("receipt intent");
- assert_eq!(
- receipt_intent.status,
- RhiProcessedJobStatus::ReceiptPublishing
- );
- assert_eq!(
- receipt_intent.receipt_event_json.as_deref(),
- Some(receipt_json("one").as_str())
- );
- let published = store
- .mark_receipt_published(&job, "receipt-1", 1_100)
- .await
- .expect("receipt");
- assert_eq!(published.status, RhiProcessedJobStatus::ReceiptPublished);
- let error = store
- .mark_completed(&job, "receipt-1", "result-1", 1_700_000_001, 1_150)
- .await
- .expect_err("unclaimed result completion");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::ResultPublicationNotClaimed
- ));
- assert_eq!(
- store
- .claim_job(&job, 1_160, 10_000)
- .await
- .expect("result claim"),
- RhiProcessedJobClaim::RecoverResult {
- receipt_event_id: "receipt-1".to_owned(),
- receipt_event_json: receipt_json("one"),
- result_event_id: None,
- result_event_json: None,
- proof_metadata_json: Some(proof_json("one")),
- }
- );
- let publishing = store
- .mark_result_publishing(
- &job,
- "receipt-1",
- "result-1",
- result_json("one").as_str(),
- 1_170,
- )
- .await
- .expect("result intent");
- assert_eq!(publishing.status, RhiProcessedJobStatus::ResultPublishing);
- assert_eq!(publishing.result_event_id.as_deref(), Some("result-1"));
- assert_eq!(
- publishing.result_event_json.as_deref(),
- Some(result_json("one").as_str())
- );
- let completed = store
- .mark_completed(&job, "receipt-1", "result-1", 1_700_000_001, 1_200)
- .await
- .expect("complete");
- assert_eq!(completed.status, RhiProcessedJobStatus::Completed);
-
- let reopened = RhiProcessedJobStore::open_file(path.as_path())
- .await
- .expect("reopen");
- let stored = reopened
- .get_job("request-1")
- .await
- .expect("stored")
- .expect("job");
- assert_eq!(stored.status, RhiProcessedJobStatus::Completed);
- assert_eq!(stored.receipt_event_id.as_deref(), Some("receipt-1"));
- assert_eq!(
- stored.receipt_event_json.as_deref(),
- Some(receipt_json("one").as_str())
- );
- assert_eq!(stored.result_event_id.as_deref(), Some("result-1"));
- assert_eq!(
- stored.result_event_json.as_deref(),
- Some(result_json("one").as_str())
- );
- }
-
- #[tokio::test]
- async fn processed_job_store_prevent_unexpired_duplicate_claims_and_reclaims_expired_claims() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("request-2");
-
- assert_eq!(
- store.claim_job(&job, 10, 100).await.expect("first claim"),
- RhiProcessedJobClaim::Execute
- );
- assert_eq!(
- store
- .claim_job(&job, 20, 100)
- .await
- .expect("duplicate claim"),
- RhiProcessedJobClaim::InProgress
- );
- assert_eq!(
- store
- .claim_job(&job, 111, 100)
- .await
- .expect("expired claim"),
- RhiProcessedJobClaim::Execute
- );
- }
-
- #[tokio::test]
- async fn processed_job_store_recovers_expired_receipt_publication_intent() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("request-receipt-recover");
- store.claim_job(&job, 10, 100).await.expect("claim");
- store
- .mark_receipt_publishing(
- &job,
- "receipt-recover",
- receipt_json("recover").as_str(),
- Some(proof_json("recover").as_str()),
- 20,
- )
- .await
- .expect("receipt intent");
-
- assert_eq!(
- store
- .claim_job(&job, 30, 100)
- .await
- .expect("unexpired receipt intent"),
- RhiProcessedJobClaim::InProgress
- );
- assert_eq!(
- store
- .claim_job(&job, 111, 100)
- .await
- .expect("expired receipt intent"),
- RhiProcessedJobClaim::RecoverReceipt {
- receipt_event_id: "receipt-recover".to_owned(),
- receipt_event_json: receipt_json("recover"),
- }
- );
- }
-
- #[tokio::test]
- async fn processed_job_store_claims_result_publication_and_rejects_conflicting_result_ids() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("request-2-result");
- store.claim_job(&job, 10, 100).await.expect("claim");
- store
- .mark_receipt_publishing(&job, "receipt-1", receipt_json("two").as_str(), None, 15)
- .await
- .expect("receipt intent");
- store
- .mark_receipt_published(&job, "receipt-1", 20)
- .await
- .expect("receipt");
- assert_eq!(
- store.claim_job(&job, 30, 100).await.expect("result claim"),
- RhiProcessedJobClaim::RecoverResult {
- receipt_event_id: "receipt-1".to_owned(),
- receipt_event_json: receipt_json("two"),
- result_event_id: None,
- result_event_json: None,
- proof_metadata_json: None,
- }
- );
- store
- .mark_result_publishing(
- &job,
- "receipt-1",
- "result-1",
- result_json("two").as_str(),
- 40,
- )
- .await
- .expect("result intent");
- assert_eq!(
- store
- .claim_job(&job, 50, 100)
- .await
- .expect("duplicate result claim"),
- RhiProcessedJobClaim::InProgress
- );
- assert_eq!(
- store
- .claim_job(&job, 131, 100)
- .await
- .expect("expired result claim"),
- RhiProcessedJobClaim::RecoverResult {
- receipt_event_id: "receipt-1".to_owned(),
- receipt_event_json: receipt_json("two"),
- result_event_id: Some("result-1".to_owned()),
- result_event_json: Some(result_json("two")),
- proof_metadata_json: None,
- }
- );
- let error = store
- .mark_result_publishing(
- &job,
- "receipt-1",
- "result-2",
- result_json("other").as_str(),
- 140,
- )
- .await
- .expect_err("conflicting result");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::DuplicateConflictingResult
- ));
- let error = store
- .mark_completed(&job, "receipt-1", "result-2", 1_700_000_001, 150)
- .await
- .expect_err("conflicting completion");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::DuplicateConflictingResult
- ));
- }
-
- #[tokio::test]
- async fn processed_job_store_rejects_result_recovery_without_receipt_event_json() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("request-missing-receipt-json");
- store.claim_job(&job, 10, 100).await.expect("claim");
- store
- .mark_receipt_publishing(
- &job,
- "receipt-1",
- receipt_json("missing").as_str(),
- None,
- 15,
- )
- .await
- .expect("receipt intent");
- store
- .mark_receipt_published(&job, "receipt-1", 20)
- .await
- .expect("receipt");
-
- corrupt_job(
- &store,
- &job,
- "UPDATE rhi_processed_jobs SET receipt_event_json = NULL WHERE request_id = ?",
- )
- .await;
-
- let error = store
- .claim_job(&job, 131, 100)
- .await
- .expect_err("invalid receipt event json");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::InvalidStoredValue("receipt_event")
- ));
- }
-
- #[tokio::test]
- async fn processed_job_store_rejects_malformed_completed_rows_before_claim() {
- for (name, sql) in [
- (
- "completed without result id",
- "UPDATE rhi_processed_jobs SET result_event_id = NULL WHERE request_id = ?",
- ),
- (
- "completed without result json",
- "UPDATE rhi_processed_jobs SET result_event_json = NULL WHERE request_id = ?",
- ),
- (
- "completed with receipt only",
- "UPDATE rhi_processed_jobs SET result_event_id = NULL, result_event_json = NULL WHERE request_id = ?",
- ),
- ] {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job(name);
- complete_job(&store, &job).await;
- corrupt_job(&store, &job, sql).await;
-
- let error = store
- .claim_job(&job, 200, 100)
- .await
- .expect_err("invalid completed row");
- assert!(
- matches!(
- error,
- RhiProcessedJobStoreError::InvalidStoredValue("result_event")
- ),
- "{name}: {error:?}"
- );
- }
- }
-
- #[tokio::test]
- async fn processed_job_store_rejects_malformed_failed_rows_before_claim() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let missing_error = job("failed-without-error");
- store
- .claim_job(&missing_error, 10, 100)
- .await
- .expect("claim");
- corrupt_job(
- &store,
- &missing_error,
- "UPDATE rhi_processed_jobs
- SET status = 'failed',
- completed_timestamp = 1700000001,
- claim_expires_at_ms = NULL
- WHERE request_id = ?",
- )
- .await;
- let error = store
- .claim_job(&missing_error, 200, 100)
- .await
- .expect_err("failed row without error");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::InvalidStoredValue("failed_error_code")
- ));
-
- let partial = job("failed-with-partial-evidence");
- store.claim_job(&partial, 10, 100).await.expect("claim");
- store
- .mark_receipt_publishing(
- &partial,
- "receipt-partial",
- receipt_json("partial").as_str(),
- None,
- 20,
- )
- .await
- .expect("receipt intent");
- store
- .mark_receipt_published(&partial, "receipt-partial", 30)
- .await
- .expect("receipt published");
- corrupt_job(
- &store,
- &partial,
- "UPDATE rhi_processed_jobs
- SET status = 'failed',
- error_code = 'proof_failed',
- completed_timestamp = 1700000001,
- claim_expires_at_ms = NULL
- WHERE request_id = ?",
- )
- .await;
- let error = store
- .claim_job(&partial, 200, 100)
- .await
- .expect_err("failed row with partial evidence");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::InvalidStoredValue("unexpected_receipt_event")
- ));
- }
-
- #[tokio::test]
- async fn processed_job_store_treats_failed_rows_as_terminal() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("failed-terminal");
- store.claim_job(&job, 10, 100).await.expect("claim");
- sqlx::query(
- "UPDATE rhi_processed_jobs
- SET status = 'failed',
- error_code = 'proof_failed',
- completed_timestamp = 1700000001,
- claim_expires_at_ms = NULL
- WHERE request_id = ?",
- )
- .bind(job.request_id.as_str())
- .execute(&store.pool)
- .await
- .expect("valid failed row");
-
- assert_eq!(
- store
- .claim_job(&job, 200, 100)
- .await
- .expect("terminal failed claim"),
- RhiProcessedJobClaim::Failed {
- error_code: "proof_failed".to_owned()
- }
- );
- }
-
- #[tokio::test]
- async fn processed_job_store_schema_rejects_malformed_completed_rows() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("completed-schema-check");
- complete_job(&store, &job).await;
-
- let error = sqlx::query(
- "UPDATE rhi_processed_jobs SET result_event_json = NULL WHERE request_id = ?",
- )
- .bind(job.request_id.as_str())
- .execute(&store.pool)
- .await
- .expect_err("schema check rejects malformed completed row");
-
- assert!(matches!(error, sqlx::Error::Database(_)));
- }
-
- #[tokio::test]
- async fn processed_job_store_rejects_conflicting_duplicate_jobs() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("request-3");
- store.claim_job(&job, 10, 100).await.expect("claim");
- let mut conflicting = job.clone();
- conflicting.request_hash =
- "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_owned();
-
- let error = store
- .claim_job(&conflicting, 111, 100)
- .await
- .expect_err("conflicting job");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::DuplicateConflictingJob
- ));
- }
-
- #[tokio::test]
- async fn processed_job_store_rejects_conflicting_receipt_updates() {
- let store = RhiProcessedJobStore::open_memory().expect("store");
- let job = job("request-4");
- store.claim_job(&job, 10, 100).await.expect("claim");
- store
- .mark_receipt_publishing(&job, "receipt-1", receipt_json("four").as_str(), None, 15)
- .await
- .expect("receipt intent");
- store
- .mark_receipt_published(&job, "receipt-1", 20)
- .await
- .expect("receipt");
-
- let error = store
- .mark_receipt_publishing(&job, "receipt-2", receipt_json("other").as_str(), None, 30)
- .await
- .expect_err("conflicting receipt");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::DuplicateConflictingReceipt
- ));
- }
-
- #[tokio::test]
- async fn processed_job_store_rejects_unsupported_old_schema_versions() {
- let tempdir = tempfile::tempdir().expect("tempdir");
- let path = tempdir.path().join("processed_jobs_old.sqlite");
- let options = sqlx::sqlite::SqliteConnectOptions::new()
- .filename(path.as_path())
- .create_if_missing(true);
- let pool = sqlx::sqlite::SqlitePoolOptions::new()
- .max_connections(1)
- .connect_with(options)
- .await
- .expect("open sqlite");
- sqlx::query(
- "CREATE TABLE rhi_processed_job_schema(
- schema_id INTEGER PRIMARY KEY CHECK(schema_id = 1),
- version INTEGER NOT NULL
- )",
- )
- .execute(&pool)
- .await
- .expect("schema table");
- sqlx::query("INSERT INTO rhi_processed_job_schema(schema_id, version) VALUES (1, 2)")
- .execute(&pool)
- .await
- .expect("schema version");
- pool.close().await;
-
- let error = RhiProcessedJobStore::open_file(path.as_path())
- .await
- .expect_err("old schema rejected");
- assert!(matches!(
- error,
- RhiProcessedJobStoreError::UnsupportedSchemaVersion(2)
- ));
- }
-}
diff --git a/src/features/trade_listing/state.rs b/src/features/trade_listing/state.rs
@@ -1,583 +0,0 @@
-#![forbid(unsafe_code)]
-
-use std::collections::{HashMap, HashSet};
-use std::path::{Path, PathBuf};
-use std::sync::Arc;
-
-use radroots_nostr::prelude::{RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrTimestamp};
-use radroots_trade::workflow::RadrootsTradeWorkflowState;
-use serde::{Deserialize, Serialize};
-use thiserror::Error;
-use tokio::sync::Mutex;
-
-use crate::features::trade_listing::processed_jobs::{
- RhiProcessedJobStore, RhiProcessedJobStoreError,
-};
-
-pub type SharedTradeListingState = Arc<Mutex<TradeListingState>>;
-
-const TRADE_LISTING_STATE_VERSION: u32 = 3;
-const PROCESSED_JOB_STORE_FILE_NAME: &str = "processed_jobs.sqlite";
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct TradeOrderState {
- pub order_id: String,
- pub listing_addr: String,
- pub buyer_pubkey: String,
- pub seller_pubkey: String,
- pub status: RadrootsTradeWorkflowState,
- #[serde(default)]
- pub listing_snapshot_event_id: Option<String>,
- #[serde(default)]
- pub root_event_id: Option<String>,
- #[serde(default)]
- pub last_event_id: Option<String>,
- pub seen_event_ids: HashSet<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-pub struct ValidatedListingState {
- pub event_id: String,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-pub struct ListingEventState {
- pub event_id: String,
- pub kind: u32,
-}
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize)]
-#[serde(deny_unknown_fields)]
-pub struct TradeListingState {
- #[serde(default)]
- validated_listing_events: HashMap<String, ValidatedListingState>,
- #[serde(default)]
- listing_events: HashMap<String, ListingEventState>,
- #[serde(default)]
- seen_non_order_event_ids: HashSet<String>,
- orders: HashMap<String, TradeOrderState>,
- last_event_created_at: Option<u32>,
-}
-
-#[derive(Clone, Debug)]
-pub struct TradeListingRuntime {
- state: SharedTradeListingState,
- config: TradeListingRuntimeConfig,
- persistence: Option<Arc<TradeListingStatePersistence>>,
- processed_jobs: Arc<RhiProcessedJobStore>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct TradeListingRuntimeConfig {
- pub state_path: PathBuf,
- pub replay_window_secs: u64,
- pub replay_overlap_secs: u64,
-}
-
-#[derive(Clone, Debug)]
-struct TradeListingStatePersistence {
- path: PathBuf,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-struct PersistedTradeListingState {
- version: u32,
- state: TradeListingState,
-}
-
-impl Default for TradeListingRuntimeConfig {
- fn default() -> Self {
- Self {
- state_path: crate::paths::default_subscriber_state_path_for_process()
- .expect("resolve canonical rhi trade-listing state path"),
- replay_window_secs: 24 * 60 * 60,
- replay_overlap_secs: 5 * 60,
- }
- }
-}
-
-impl Default for TradeListingRuntime {
- fn default() -> Self {
- Self {
- state: Arc::new(Mutex::new(TradeListingState::default())),
- config: TradeListingRuntimeConfig::default(),
- persistence: None,
- processed_jobs: Arc::new(
- RhiProcessedJobStore::open_memory()
- .expect("open in-memory rhi processed-job store"),
- ),
- }
- }
-}
-
-impl TradeListingRuntime {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub async fn load(config: TradeListingRuntimeConfig) -> Result<Self, TradeListingRuntimeError> {
- let persistence = Arc::new(TradeListingStatePersistence::new(config.state_path.clone()));
- let processed_jobs = Arc::new(
- RhiProcessedJobStore::open_file(processed_job_store_path_for_state_path(
- &config.state_path,
- )?)
- .await?,
- );
- let state = persistence.load().await?;
- Ok(Self {
- state: Arc::new(Mutex::new(state)),
- config,
- persistence: Some(persistence),
- processed_jobs,
- })
- }
-
- pub fn state(&self) -> SharedTradeListingState {
- Arc::clone(&self.state)
- }
-
- pub fn processed_jobs(&self) -> Arc<RhiProcessedJobStore> {
- Arc::clone(&self.processed_jobs)
- }
-
- pub async fn persist(&self) -> Result<(), TradeListingRuntimeError> {
- let Some(persistence) = &self.persistence else {
- return Ok(());
- };
- let snapshot = self.state.lock().await.clone();
- persistence.persist(&snapshot).await
- }
-
- pub async fn mark_processed_event(
- &self,
- created_at: u32,
- ) -> Result<(), TradeListingRuntimeError> {
- {
- let mut state = self.state.lock().await;
- state.observe_event_created_at(created_at);
- }
- self.persist().await
- }
-
- pub async fn recovery_filter(&self, kinds: Vec<RadrootsNostrKind>) -> RadrootsNostrFilter {
- let since = {
- let state = self.state.lock().await;
- state.replay_since(
- RadrootsNostrTimestamp::now().as_secs(),
- self.config.replay_window_secs,
- self.config.replay_overlap_secs,
- )
- };
- RadrootsNostrFilter::new()
- .kinds(kinds)
- .since(RadrootsNostrTimestamp::from(since))
- }
-}
-
-impl TradeListingState {
- pub fn upsert_listing_event(&mut self, listing_addr: &str, event_id: &str, kind: u32) {
- self.listing_events.insert(
- listing_addr.to_string(),
- ListingEventState {
- event_id: event_id.to_string(),
- kind,
- },
- );
- }
-
- pub fn listing_event_id(&self, listing_addr: &str) -> Option<&str> {
- self.listing_events
- .get(listing_addr)
- .map(|listing| listing.event_id.as_str())
- }
-
- pub fn mark_listing_validated(&mut self, listing_addr: &str, event_id: &str) {
- self.validated_listing_events.insert(
- listing_addr.to_string(),
- ValidatedListingState {
- event_id: event_id.to_string(),
- },
- );
- }
-
- pub fn clear_listing_validation(&mut self, listing_addr: &str) {
- self.validated_listing_events.remove(listing_addr);
- }
-
- pub fn validated_listing_event_id(&self, listing_addr: &str) -> Option<&str> {
- self.validated_listing_events
- .get(listing_addr)
- .map(|validated| validated.event_id.as_str())
- }
-
- pub fn is_listing_validated(&self, listing_addr: &str) -> bool {
- self.validated_listing_event_id(listing_addr).is_some()
- }
-
- pub fn order_exists(&self, order_id: &str) -> bool {
- self.orders.contains_key(order_id)
- }
-
- pub fn get_order_mut(&mut self, order_id: &str) -> Option<&mut TradeOrderState> {
- self.orders.get_mut(order_id)
- }
-
- pub fn get_order(&self, order_id: &str) -> Option<&TradeOrderState> {
- self.orders.get(order_id)
- }
-
- pub fn insert_order(&mut self, order: TradeOrderState) {
- self.orders.insert(order.order_id.clone(), order);
- }
-
- pub fn mark_event_seen(&mut self, order_id: &str, event_id: &str) -> bool {
- if let Some(state) = self.orders.get_mut(order_id) {
- state.seen_event_ids.insert(event_id.to_string())
- } else {
- false
- }
- }
-
- pub fn is_event_seen(&self, order_id: &str, event_id: &str) -> bool {
- self.orders
- .get(order_id)
- .map(|state| state.seen_event_ids.contains(event_id))
- .unwrap_or(false)
- }
-
- pub fn mark_non_order_event_seen(&mut self, event_id: &str) -> bool {
- self.seen_non_order_event_ids.insert(event_id.to_string())
- }
-
- pub fn is_non_order_event_seen(&self, event_id: &str) -> bool {
- self.seen_non_order_event_ids.contains(event_id)
- }
-
- pub fn observe_event_created_at(&mut self, created_at: u32) {
- self.last_event_created_at = Some(
- self.last_event_created_at
- .map_or(created_at, |current| current.max(created_at)),
- );
- }
-
- pub fn last_event_created_at(&self) -> Option<u32> {
- self.last_event_created_at
- }
-
- pub fn replay_since(
- &self,
- now_secs: u64,
- replay_window_secs: u64,
- replay_overlap_secs: u64,
- ) -> u64 {
- match self.last_event_created_at {
- Some(last) => u64::from(last).saturating_sub(replay_overlap_secs),
- None => now_secs.saturating_sub(replay_window_secs),
- }
- }
-}
-
-impl TradeListingStatePersistence {
- fn new(path: PathBuf) -> Self {
- Self { path }
- }
-
- async fn load(&self) -> Result<TradeListingState, TradeListingRuntimeError> {
- if !tokio::fs::try_exists(&self.path).await? {
- return Ok(TradeListingState::default());
- }
-
- let payload = tokio::fs::read_to_string(&self.path).await?;
- let snapshot: PersistedTradeListingState = serde_json::from_str(&payload)?;
- if snapshot.version != TRADE_LISTING_STATE_VERSION {
- return Err(TradeListingRuntimeError::UnsupportedStateVersion(
- snapshot.version,
- ));
- }
- Ok(snapshot.state)
- }
-
- async fn persist(&self, state: &TradeListingState) -> Result<(), TradeListingRuntimeError> {
- if let Some(parent) = self.path.parent() {
- if !parent.as_os_str().is_empty() {
- tokio::fs::create_dir_all(parent).await?;
- }
- }
-
- let snapshot = PersistedTradeListingState {
- version: TRADE_LISTING_STATE_VERSION,
- state: state.clone(),
- };
- let payload = serde_json::to_vec_pretty(&snapshot)?;
- let temp_path = temp_state_path(&self.path)?;
- tokio::fs::write(&temp_path, payload).await?;
- tokio::fs::rename(&temp_path, &self.path).await?;
- Ok(())
- }
-}
-
-fn temp_state_path(path: &Path) -> Result<PathBuf, TradeListingRuntimeError> {
- let file_name = path
- .file_name()
- .ok_or_else(|| TradeListingRuntimeError::InvalidStatePath(path.to_path_buf()))?;
- Ok(path.with_file_name(format!("{}.tmp", file_name.to_string_lossy())))
-}
-
-fn processed_job_store_path_for_state_path(
- path: &Path,
-) -> Result<PathBuf, TradeListingRuntimeError> {
- path.file_name()
- .ok_or_else(|| TradeListingRuntimeError::InvalidStatePath(path.to_path_buf()))?;
- Ok(path.with_file_name(PROCESSED_JOB_STORE_FILE_NAME))
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum TradeListingStateError {
- MissingOrder,
-}
-
-impl core::fmt::Display for TradeListingStateError {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- match self {
- TradeListingStateError::MissingOrder => write!(f, "missing order state"),
- }
- }
-}
-
-impl std::error::Error for TradeListingStateError {}
-
-#[derive(Debug, Error)]
-pub enum TradeListingRuntimeError {
- #[error("invalid trade listing state path: {0}")]
- InvalidStatePath(PathBuf),
- #[error("unsupported trade listing state version: {0}")]
- UnsupportedStateVersion(u32),
- #[error("trade listing state io error: {0}")]
- Io(#[from] std::io::Error),
- #[error("trade listing state json error: {0}")]
- Json(#[from] serde_json::Error),
- #[error("rhi processed-job store error: {0}")]
- ProcessedJobStore(#[from] RhiProcessedJobStoreError),
-}
-
-#[cfg(test)]
-#[cfg_attr(coverage_nightly, coverage(off))]
-mod tests {
- use super::{
- ListingEventState, PersistedTradeListingState, TradeListingRuntime,
- TradeListingRuntimeConfig, TradeListingRuntimeError, TradeListingState,
- TradeListingStateError, TradeOrderState, ValidatedListingState,
- processed_job_store_path_for_state_path,
- };
- use radroots_trade::workflow::RadrootsTradeWorkflowState;
- use std::collections::{HashMap, HashSet};
- use std::path::Path;
-
- fn unique_state_path(suffix: &str) -> std::path::PathBuf {
- let nanos = std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)
- .expect("time")
- .as_nanos();
- let path = std::env::temp_dir()
- .join(format!("rhi-trade-state-{suffix}-{nanos}"))
- .join("state.json");
- std::fs::create_dir_all(path.parent().expect("state parent")).expect("state parent dir");
- path
- }
-
- #[test]
- fn state_tracks_listings_events_and_replay_anchor() {
- let mut state = TradeListingState::default();
- assert!(!state.is_listing_validated("addr"));
- state.mark_listing_validated("addr", "evt-listing-1");
- assert!(state.is_listing_validated("addr"));
- assert_eq!(
- state.validated_listing_event_id("addr"),
- Some("evt-listing-1")
- );
-
- let order = TradeOrderState {
- order_id: "order-1".into(),
- listing_addr: "addr".into(),
- buyer_pubkey: "buyer".into(),
- seller_pubkey: "seller".into(),
- status: RadrootsTradeWorkflowState::Requested,
- listing_snapshot_event_id: Some("evt-listing-1".into()),
- root_event_id: Some("evt-root-1".into()),
- last_event_id: Some("evt-root-1".into()),
- seen_event_ids: Default::default(),
- };
- state.insert_order(order);
- assert!(!state.is_event_seen("order-1", "evt"));
- assert!(state.mark_event_seen("order-1", "evt"));
- assert!(state.is_event_seen("order-1", "evt"));
- assert!(!state.is_non_order_event_seen("evt-non-order"));
- assert!(state.mark_non_order_event_seen("evt-non-order"));
- assert!(state.is_non_order_event_seen("evt-non-order"));
- state.upsert_listing_event("addr", "evt-listing-1", 30402);
- assert_eq!(state.listing_event_id("addr"), Some("evt-listing-1"));
- assert_eq!(state.replay_since(1_000, 300, 60), 700);
-
- state.observe_event_created_at(900);
- assert_eq!(state.last_event_created_at(), Some(900));
- assert_eq!(state.replay_since(1_000, 300, 60), 840);
- }
-
- #[test]
- fn state_covers_missing_order_paths_and_error_display() {
- let mut state = TradeListingState::default();
- assert!(!state.order_exists("missing"));
- assert!(state.get_order_mut("missing").is_none());
- assert!(!state.mark_event_seen("missing", "evt-1"));
- assert!(!state.is_event_seen("missing", "evt-1"));
- assert!(!state.is_non_order_event_seen("evt-2"));
-
- assert_eq!(
- TradeListingStateError::MissingOrder.to_string(),
- "missing order state"
- );
-
- assert!(state.get_order("missing").is_none());
- }
-
- #[tokio::test]
- async fn runtime_reuses_shared_trade_listing_state() {
- let runtime = TradeListingRuntime::new();
- let state = runtime.state();
- state
- .lock()
- .await
- .mark_listing_validated("addr", "evt-listing-1");
-
- assert!(runtime.state().lock().await.is_listing_validated("addr"));
- }
-
- #[tokio::test]
- async fn runtime_persists_and_loads_trade_listing_state() {
- let path = unique_state_path("roundtrip");
- let processed_jobs_path = processed_job_store_path_for_state_path(&path).expect("job path");
- let config = TradeListingRuntimeConfig {
- state_path: path.clone(),
- replay_window_secs: 600,
- replay_overlap_secs: 30,
- };
- let runtime = TradeListingRuntime::load(config.clone())
- .await
- .expect("runtime");
-
- {
- let state_handle = runtime.state();
- let mut state = state_handle.lock().await;
- state.mark_listing_validated("addr", "evt-listing-1");
- state.mark_non_order_event_seen("evt-validate-1");
- state.observe_event_created_at(456);
- }
- runtime.persist().await.expect("persist");
-
- let loaded = TradeListingRuntime::load(config).await.expect("load");
- let loaded_state_handle = loaded.state();
- let loaded_state = loaded_state_handle.lock().await;
- assert!(loaded_state.is_listing_validated("addr"));
- assert_eq!(
- loaded_state.validated_listing_event_id("addr"),
- Some("evt-listing-1")
- );
- assert!(loaded_state.is_non_order_event_seen("evt-validate-1"));
- assert_eq!(loaded_state.last_event_created_at(), Some(456));
- assert!(
- tokio::fs::try_exists(processed_jobs_path.as_path())
- .await
- .expect("processed job store exists check")
- );
-
- let _ = tokio::fs::remove_file(path).await;
- let _ = tokio::fs::remove_file(processed_jobs_path).await;
- }
-
- #[tokio::test]
- async fn runtime_load_rejects_unsupported_snapshot_version() {
- let path = unique_state_path("version");
- let payload = PersistedTradeListingState {
- version: 99,
- state: TradeListingState::default(),
- };
- tokio::fs::write(&path, serde_json::to_vec(&payload).expect("payload"))
- .await
- .expect("write");
-
- let err = TradeListingRuntime::load(TradeListingRuntimeConfig {
- state_path: path.clone(),
- replay_window_secs: 600,
- replay_overlap_secs: 30,
- })
- .await
- .expect_err("unsupported snapshot should fail");
- assert!(matches!(
- err,
- TradeListingRuntimeError::UnsupportedStateVersion(99)
- ));
-
- let _ = tokio::fs::remove_file(path).await;
- }
-
- #[tokio::test]
- async fn runtime_rejects_previous_state_versions() {
- let path = unique_state_path("previous-version");
- let payload = PersistedTradeListingState {
- version: 1,
- state: TradeListingState::default(),
- };
- tokio::fs::write(&path, serde_json::to_vec(&payload).expect("payload"))
- .await
- .expect("write");
-
- let err = TradeListingRuntime::load(TradeListingRuntimeConfig {
- state_path: path.clone(),
- replay_window_secs: 600,
- replay_overlap_secs: 30,
- })
- .await
- .expect_err("previous snapshot version should fail");
- assert!(matches!(
- err,
- TradeListingRuntimeError::UnsupportedStateVersion(1)
- ));
-
- let _ = tokio::fs::remove_file(path).await;
- }
-
- #[test]
- fn state_can_clear_listing_validation() {
- let mut state = TradeListingState {
- validated_listing_events: HashMap::from([(
- "addr".to_string(),
- ValidatedListingState {
- event_id: "evt-listing-1".to_string(),
- },
- )]),
- listing_events: HashMap::from([(
- "addr".to_string(),
- ListingEventState {
- event_id: "evt-listing-1".to_string(),
- kind: 30402,
- },
- )]),
- seen_non_order_event_ids: HashSet::new(),
- orders: HashMap::new(),
- last_event_created_at: None,
- };
- assert!(state.is_listing_validated("addr"));
- state.clear_listing_validation("addr");
- assert!(!state.is_listing_validated("addr"));
- assert_eq!(state.validated_listing_event_id("addr"), None);
- }
-
- #[test]
- fn processed_job_store_path_lives_next_to_configured_state_snapshot() {
- assert_eq!(
- processed_job_store_path_for_state_path(Path::new("state/trade-listing-state.json"))
- .expect("processed job path"),
- Path::new("state/processed_jobs.sqlite")
- );
- }
-}
diff --git a/src/features/trade_listing/subscriber.rs b/src/features/trade_listing/subscriber.rs
@@ -1,835 +0,0 @@
-#![forbid(unsafe_code)]
-#![cfg_attr(coverage_nightly, coverage(off))]
-
-use std::convert::TryFrom;
-use std::time::Duration;
-
-use anyhow::{Result, anyhow};
-use radroots_event::kinds::{
- KIND_LISTING, ORDER_EVENT_KINDS, TRADE_VALIDATION_EVENT_KINDS,
- is_trade_validation_service_event_kind,
-};
-use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrFilter, RadrootsNostrKeys,
- RadrootsNostrKind, RadrootsNostrRelayPoolNotification, RadrootsNostrSubscriptionId,
- RadrootsNostrTag, radroots_nostr_tags_resolve,
-};
-use tokio::sync::watch;
-use tokio::time::sleep;
-use tracing::{info, warn};
-
-use crate::features::trade_listing::{
- handlers::events::{TradeListingEventError, handle_error, handle_event_with_policy},
- state::TradeListingRuntime,
-};
-use crate::features::trade_validation_receipt::TradeValidationReceiptProverPolicy;
-
-#[cfg(test)]
-#[derive(Default)]
-struct SubscriberTestHooks {
- subscribe_results: std::collections::VecDeque<Result<RadrootsNostrSubscriptionId, ()>>,
- unsubscribe_results: std::collections::VecDeque<()>,
- notifications: std::collections::VecDeque<Result<RadrootsNostrRelayPoolNotification, ()>>,
- delay_before_event_handle: std::collections::VecDeque<bool>,
- resolve_tags_results: std::collections::VecDeque<
- Result<Vec<RadrootsNostrTag>, radroots_nostr::error::RadrootsNostrTagsResolveError>,
- >,
- handle_event_results: std::collections::VecDeque<Result<(), TradeListingEventError>>,
- handle_error_results: std::collections::VecDeque<Result<(), TradeListingEventError>>,
-}
-
-#[cfg(test)]
-static SUBSCRIBER_TEST_HOOKS: std::sync::OnceLock<std::sync::Mutex<SubscriberTestHooks>> =
- std::sync::OnceLock::new();
-
-#[cfg(test)]
-fn subscriber_test_hooks() -> &'static std::sync::Mutex<SubscriberTestHooks> {
- SUBSCRIBER_TEST_HOOKS.get_or_init(|| std::sync::Mutex::new(SubscriberTestHooks::default()))
-}
-
-#[cfg(test)]
-fn pop_subscribe_hook() -> Option<Result<RadrootsNostrSubscriptionId, ()>> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .subscribe_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn pop_unsubscribe_hook() -> Option<()> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .unsubscribe_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn pop_notification_hook() -> Option<Result<RadrootsNostrRelayPoolNotification, ()>> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .notifications
- .pop_front()
-}
-
-#[cfg(test)]
-fn pop_delay_hook() -> Option<bool> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .delay_before_event_handle
- .pop_front()
-}
-
-#[cfg(test)]
-fn take_delay_hook() -> Option<bool> {
- pop_delay_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_delay_hook() -> Option<bool> {
- None
-}
-
-#[cfg(test)]
-fn pop_resolve_tags_hook()
--> Option<Result<Vec<RadrootsNostrTag>, radroots_nostr::error::RadrootsNostrTagsResolveError>> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .resolve_tags_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn take_resolve_tags_hook()
--> Option<Result<Vec<RadrootsNostrTag>, radroots_nostr::error::RadrootsNostrTagsResolveError>> {
- pop_resolve_tags_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_resolve_tags_hook()
--> Option<Result<Vec<RadrootsNostrTag>, radroots_nostr::error::RadrootsNostrTagsResolveError>> {
- None
-}
-
-#[cfg(test)]
-fn pop_handle_event_hook() -> Option<Result<(), TradeListingEventError>> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .handle_event_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn take_handle_event_hook() -> Option<Result<(), TradeListingEventError>> {
- pop_handle_event_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_handle_event_hook() -> Option<Result<(), TradeListingEventError>> {
- None
-}
-
-#[cfg(test)]
-fn pop_handle_error_hook() -> Option<Result<(), TradeListingEventError>> {
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .handle_error_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn take_handle_error_hook() -> Option<Result<(), TradeListingEventError>> {
- pop_handle_error_hook()
-}
-
-#[cfg(not(test))]
-#[cfg_attr(coverage_nightly, coverage(off))]
-fn take_handle_error_hook() -> Option<Result<(), TradeListingEventError>> {
- None
-}
-
-fn resolve_tags_io(
- event: &RadrootsNostrEvent,
- keys: &RadrootsNostrKeys,
-) -> Result<Vec<RadrootsNostrTag>, radroots_nostr::error::RadrootsNostrTagsResolveError> {
- let resolved = match take_resolve_tags_hook() {
- Some(result) => result?,
- None => return radroots_nostr_tags_resolve(event, keys),
- };
- Ok(resolved)
-}
-
-fn map_notification_recv_result(
- result: Result<RadrootsNostrRelayPoolNotification, tokio::sync::broadcast::error::RecvError>,
-) -> Result<RadrootsNostrRelayPoolNotification, ()> {
- result.map_err(|_| ())
-}
-
-async fn subscribe_io(
- client: &RadrootsNostrClient,
- filter: RadrootsNostrFilter,
-) -> Result<RadrootsNostrSubscriptionId> {
- #[cfg(test)]
- if let Some(result) = pop_subscribe_hook() {
- return result.map_err(|_| anyhow!("trade_listing subscriber subscribe failed"));
- }
- let subscription = client.subscribe(filter, None).await?;
- Ok(subscription.val)
-}
-
-async fn unsubscribe_io(
- client: &RadrootsNostrClient,
- subscription_id: &RadrootsNostrSubscriptionId,
-) {
- #[cfg(test)]
- if pop_unsubscribe_hook().is_some() {
- return;
- }
- client.unsubscribe(subscription_id).await;
-}
-
-async fn handle_event_io(
- event: RadrootsNostrEvent,
- resolved_tags: Vec<RadrootsNostrTag>,
- keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- runtime: TradeListingRuntime,
- proof_policy: TradeValidationReceiptProverPolicy,
-) -> Result<(), TradeListingEventError> {
- let result = match take_handle_event_hook() {
- Some(result) => result,
- None => {
- handle_event_with_policy(event, resolved_tags, keys, client, runtime, &proof_policy)
- .await
- }
- };
- result?;
- Ok(())
-}
-
-async fn handle_error_io(
- err: TradeListingEventError,
- event: &RadrootsNostrEvent,
- client: &RadrootsNostrClient,
-) -> Result<(), TradeListingEventError> {
- let result = match take_handle_error_hook() {
- Some(result) => result,
- None => handle_error(err, event, client).await,
- };
- result?;
- Ok(())
-}
-
-fn should_delay_before_event_handle() -> bool {
- if let Some(delay) = take_delay_hook() {
- return delay;
- }
- cfg!(all(debug_assertions, not(test)))
-}
-
-#[cfg_attr(all(not(test), coverage_nightly), coverage(off))]
-async fn process_event_notification(
- event: RadrootsNostrEvent,
- keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- runtime: TradeListingRuntime,
- proof_policy: TradeValidationReceiptProverPolicy,
-) -> Result<()> {
- let created_at = u32::try_from(event.created_at.as_secs()).unwrap_or(u32::MAX);
- if should_delay_before_event_handle() {
- sleep(Duration::from_millis(200)).await;
- }
-
- let resolved_tags = match resolve_tags_io(&event, &keys) {
- Ok(tags) => tags,
- Err(err) => {
- warn!("trade_listing: failed to resolve tags: {err}");
- return Ok(());
- }
- };
-
- let event_kind = match event.kind {
- RadrootsNostrKind::Custom(v) => Some(u32::from(v)),
- _ => None,
- };
- if let Err(err) = handle_event_io(
- event.clone(),
- resolved_tags,
- keys,
- client.clone(),
- runtime.clone(),
- proof_policy,
- )
- .await
- {
- match err {
- TradeListingEventError::MissingRecipient | TradeListingEventError::UnsupportedKind => {}
- other => {
- if event_kind.is_some_and(is_trade_validation_service_event_kind) {
- if let Err(err) = handle_error_io(other, &event, &client).await {
- warn!("trade_listing: failed to send error feedback: {err}");
- }
- } else {
- warn!("trade_listing: rejected public trade event: {other}");
- }
- runtime.mark_processed_event(created_at).await?;
- }
- }
- return Ok(());
- }
-
- runtime.mark_processed_event(created_at).await?;
- Ok(())
-}
-
-async fn dispatch_event_processing(
- event: RadrootsNostrEvent,
- keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- runtime: TradeListingRuntime,
- proof_policy: TradeValidationReceiptProverPolicy,
-) -> Result<()> {
- process_event_notification(event, keys, client, runtime, proof_policy).await
-}
-
-pub async fn subscriber(
- client: RadrootsNostrClient,
- keys: RadrootsNostrKeys,
- runtime: TradeListingRuntime,
- proof_policy: TradeValidationReceiptProverPolicy,
- mut stop_rx: watch::Receiver<bool>,
-) -> Result<()> {
- let subscribed_kinds = [KIND_LISTING]
- .into_iter()
- .chain(ORDER_EVENT_KINDS)
- .chain(TRADE_VALIDATION_EVENT_KINDS)
- .collect::<Vec<_>>();
- info!(
- "Starting subscriber for trade listing, order, and trade validation kinds: {:?}",
- subscribed_kinds
- );
-
- let kinds: Vec<RadrootsNostrKind> = subscribed_kinds
- .iter()
- .map(|kind| u16::try_from(*kind).expect("trade listing kinds fit in nostr custom range"))
- .map(RadrootsNostrKind::Custom)
- .collect();
- let filter: RadrootsNostrFilter = runtime.recovery_filter(kinds).await;
-
- if *stop_rx.borrow() {
- return Ok(());
- }
-
- let subscription_id = subscribe_io(&client, filter).await?;
- let mut notifications = client.notifications();
-
- let mut notifications_closed = false;
-
- loop {
- tokio::select! {
- _ = stop_rx.changed() => {
- break;
- }
- msg = async {
- #[cfg(test)]
- if let Some(result) = pop_notification_hook() {
- return result;
- }
- map_notification_recv_result(notifications.recv().await)
- } => {
- let n = match msg {
- Ok(n) => n,
- Err(_) => {
- notifications_closed = true;
- break;
- }
- };
-
- if let RadrootsNostrRelayPoolNotification::Event { event, .. } = n {
- let event = (*event).clone();
- let keys = keys.clone();
- let client = client.clone();
- let runtime = runtime.clone();
- let proof_policy = proof_policy.clone();
- dispatch_event_processing(event, keys, client, runtime, proof_policy).await?;
- }
- }
- }
- }
-
- unsubscribe_io(&client, &subscription_id).await;
- if notifications_closed {
- return Err(anyhow!("trade_listing subscriber notifications closed"));
- }
- Ok(())
-}
-
-#[cfg(test)]
-#[cfg_attr(coverage_nightly, coverage(off))]
-mod tests {
- use super::{
- SubscriberTestHooks, handle_error_io, handle_event_io, map_notification_recv_result,
- process_event_notification, resolve_tags_io, subscriber, subscriber_test_hooks,
- };
- use crate::features::trade_listing::handlers::events::TradeListingEventError;
- use crate::features::trade_listing::state::TradeListingRuntime;
- use crate::features::trade_validation_receipt::TradeValidationReceiptProverPolicy;
- use radroots_nostr::error::RadrootsNostrTagsResolveError;
- use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrEventBuilder, RadrootsNostrKeys, RadrootsNostrKind,
- RadrootsNostrRelayPoolNotification, RadrootsNostrRelayUrl, RadrootsNostrSubscriptionId,
- RadrootsNostrTag,
- };
- use tokio::sync::{Mutex, MutexGuard, watch};
-
- static TEST_LOCK: Mutex<()> = Mutex::const_new(());
-
- async fn test_guard() -> MutexGuard<'static, ()> {
- let guard = TEST_LOCK.lock().await;
- *subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner) = SubscriberTestHooks::default();
- guard
- }
-
- fn scripted_event_notification(keys: &RadrootsNostrKeys) -> RadrootsNostrRelayPoolNotification {
- let event = RadrootsNostrEventBuilder::new(RadrootsNostrKind::Custom(6000), "test")
- .sign_with_keys(keys)
- .expect("event");
- RadrootsNostrRelayPoolNotification::Event {
- relay_url: RadrootsNostrRelayUrl::parse("wss://relay.example.com").expect("relay"),
- subscription_id: RadrootsNostrSubscriptionId::new("sub-1"),
- event: Box::new(event),
- }
- }
-
- fn scripted_shutdown_notification() -> RadrootsNostrRelayPoolNotification {
- RadrootsNostrRelayPoolNotification::Shutdown
- }
-
- fn prime_subscription_hooks() {
- let mut hooks = subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- hooks
- .subscribe_results
- .push_back(Ok(RadrootsNostrSubscriptionId::new("sub-hook")));
- hooks.unsubscribe_results.push_back(());
- }
-
- fn shared_runtime() -> TradeListingRuntime {
- TradeListingRuntime::new()
- }
-
- fn proof_policy() -> TradeValidationReceiptProverPolicy {
- TradeValidationReceiptProverPolicy::default()
- }
-
- #[test]
- fn notification_recv_result_mapping_covers_ok_and_err() {
- let keys = RadrootsNostrKeys::generate();
- assert!(map_notification_recv_result(Ok(scripted_event_notification(&keys))).is_ok());
- assert!(
- map_notification_recv_result(Err(tokio::sync::broadcast::error::RecvError::Closed))
- .is_err()
- );
- }
-
- #[tokio::test]
- async fn subscriber_io_wrappers_cover_direct_and_hook_paths() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let event = RadrootsNostrEventBuilder::new(RadrootsNostrKind::TextNote, "test")
- .sign_with_keys(&keys)
- .expect("event");
-
- let _ = resolve_tags_io(&event, &keys);
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .resolve_tags_results
- .push_back(Ok(Vec::<RadrootsNostrTag>::new()));
- assert!(resolve_tags_io(&event, &keys).is_ok());
-
- let runtime = shared_runtime();
- assert!(matches!(
- handle_event_io(
- event.clone(),
- Vec::new(),
- keys.clone(),
- client.clone(),
- runtime.clone(),
- proof_policy()
- )
- .await,
- Err(TradeListingEventError::UnsupportedKind)
- ));
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .handle_event_results
- .push_back(Ok(()));
- assert!(
- handle_event_io(
- event.clone(),
- Vec::new(),
- keys.clone(),
- client.clone(),
- runtime,
- proof_policy()
- )
- .await
- .is_ok()
- );
-
- let _ = handle_error_io(TradeListingEventError::InvalidOrder, &event, &client).await;
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .handle_error_results
- .push_back(Ok(()));
- assert!(
- handle_error_io(TradeListingEventError::InvalidOrder, &event, &client)
- .await
- .is_ok()
- );
- }
-
- #[tokio::test]
- async fn subscriber_returns_ok_when_stop_is_pre_requested() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let (_tx, rx) = watch::channel(true);
- assert!(
- subscriber(client, keys, shared_runtime(), proof_policy(), rx)
- .await
- .is_ok()
- );
- }
-
- #[tokio::test]
- async fn subscriber_reuses_runtime_owned_state_across_runs() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let runtime = shared_runtime();
- let state = runtime.state();
- state
- .lock()
- .await
- .mark_listing_validated("addr", "evt-listing-1");
-
- let (_tx_first, rx_first) = watch::channel(true);
- assert!(
- subscriber(
- client.clone(),
- keys.clone(),
- runtime.clone(),
- proof_policy(),
- rx_first
- )
- .await
- .is_ok()
- );
- assert!(state.lock().await.is_listing_validated("addr"));
-
- let (_tx_second, rx_second) = watch::channel(true);
- assert!(
- subscriber(client, keys, runtime.clone(), proof_policy(), rx_second)
- .await
- .is_ok()
- );
- assert!(state.lock().await.is_listing_validated("addr"));
- }
-
- #[tokio::test]
- async fn subscriber_returns_err_when_no_relays_are_configured() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let (_tx, rx) = watch::channel(false);
- let err = subscriber(client, keys, shared_runtime(), proof_policy(), rx)
- .await
- .expect_err("expected relay error");
- let msg = format!("{err:#}");
- assert!(msg.contains("relay"));
- }
-
- #[tokio::test]
- async fn subscriber_can_stop_after_start_when_subscription_is_ready() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- prime_subscription_hooks();
- let (tx, rx) = watch::channel(false);
- let join = tokio::spawn(subscriber(
- client,
- keys,
- shared_runtime(),
- proof_policy(),
- rx,
- ));
- tokio::time::sleep(std::time::Duration::from_millis(20)).await;
- let _ = tx.send(true);
- let _ = join.await;
- }
-
- #[tokio::test]
- async fn subscriber_covers_notification_closed_path() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- prime_subscription_hooks();
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .notifications
- .push_back(Err(()));
- let (_tx, rx) = watch::channel(false);
- let err = subscriber(client, keys, shared_runtime(), proof_policy(), rx)
- .await
- .expect_err("closed notifications");
- let msg = format!("{err:#}");
- assert!(msg.contains("notifications closed"));
- }
-
- #[tokio::test]
- async fn subscriber_covers_non_event_notification_and_stop_ok_path() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- prime_subscription_hooks();
-
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .notifications
- .push_back(Ok(scripted_shutdown_notification()));
-
- let (tx, rx) = watch::channel(false);
- let join = tokio::spawn(subscriber(
- client,
- keys,
- shared_runtime(),
- proof_policy(),
- rx,
- ));
- tokio::time::sleep(std::time::Duration::from_millis(30)).await;
- let _ = tx.send(true);
- let result = join.await.expect("subscriber join");
- assert!(result.is_ok());
- }
-
- #[tokio::test]
- async fn subscriber_covers_event_processing_paths() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- prime_subscription_hooks();
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .notifications
- .push_back(Ok(scripted_event_notification(&keys)));
- subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .resolve_tags_results
- .push_back(Err(RadrootsNostrTagsResolveError::DecryptionError(
- "resolve-failed".to_string(),
- )));
- let (tx, rx) = watch::channel(false);
- let join = tokio::spawn(subscriber(
- client,
- keys,
- shared_runtime(),
- proof_policy(),
- rx,
- ));
- tokio::time::sleep(std::time::Duration::from_millis(30)).await;
- let _ = tx.send(true);
- let _ = join.await;
- }
-
- #[tokio::test]
- async fn subscriber_covers_handle_event_and_error_paths() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- prime_subscription_hooks();
-
- let mut hooks = subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- hooks
- .notifications
- .push_back(Ok(scripted_event_notification(&keys)));
- hooks
- .notifications
- .push_back(Ok(scripted_event_notification(&keys)));
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks
- .handle_event_results
- .push_back(Err(TradeListingEventError::MissingRecipient));
- hooks
- .handle_event_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- hooks
- .handle_error_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- drop(hooks);
-
- let (tx, rx) = watch::channel(false);
- let join = tokio::spawn(subscriber(
- client,
- keys,
- shared_runtime(),
- proof_policy(),
- rx,
- ));
- tokio::time::sleep(std::time::Duration::from_millis(40)).await;
- let _ = tx.send(true);
- let _ = join.await;
- }
-
- #[tokio::test]
- async fn subscriber_covers_delay_and_error_feedback_warn_path() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- prime_subscription_hooks();
-
- let mut hooks = subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- hooks
- .notifications
- .push_back(Ok(scripted_event_notification(&keys)));
- hooks.notifications.push_back(Err(()));
- hooks.delay_before_event_handle.push_back(true);
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks
- .handle_event_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- hooks
- .handle_error_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- drop(hooks);
-
- let (_tx, rx) = watch::channel(false);
- let err = subscriber(client, keys, shared_runtime(), proof_policy(), rx)
- .await
- .expect_err("notifications closed");
- let msg = format!("{err:#}");
- assert!(msg.contains("notifications closed"));
- }
-
- #[tokio::test]
- async fn handled_domain_errors_advance_replay_anchor() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let runtime = shared_runtime();
- let event = RadrootsNostrEventBuilder::new(RadrootsNostrKind::Custom(6000), "test")
- .custom_created_at(1_234_u64.into())
- .sign_with_keys(&keys)
- .expect("event");
-
- let mut hooks = subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks
- .handle_event_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- hooks.handle_error_results.push_back(Ok(()));
- drop(hooks);
-
- process_event_notification(event, keys, client, runtime.clone(), proof_policy())
- .await
- .expect("notification");
-
- assert_eq!(
- runtime.state().lock().await.last_event_created_at(),
- Some(1_234)
- );
- }
-
- #[tokio::test]
- async fn subscriber_process_event_feedback_error_branches_are_covered() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let event = RadrootsNostrEventBuilder::new(RadrootsNostrKind::Custom(6000), "event")
- .sign_with_keys(&keys)
- .expect("event");
- let runtime = shared_runtime();
-
- let mut hooks = subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks
- .handle_event_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- hooks
- .handle_error_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- drop(hooks);
-
- process_event_notification(event, keys, client, runtime, proof_policy())
- .await
- .expect("processing");
- }
-
- #[tokio::test]
- async fn subscriber_process_event_feedback_non_error_branches_are_covered() {
- let _guard = test_guard().await;
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
- let event_ok = RadrootsNostrEventBuilder::new(RadrootsNostrKind::Custom(6000), "ok")
- .sign_with_keys(&keys)
- .expect("event ok");
- let event_err = RadrootsNostrEventBuilder::new(RadrootsNostrKind::Custom(6000), "err")
- .sign_with_keys(&keys)
- .expect("event err");
- let runtime = shared_runtime();
-
- let mut hooks = subscriber_test_hooks()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks.handle_event_results.push_back(Ok(()));
- hooks.resolve_tags_results.push_back(Ok(Vec::new()));
- hooks
- .handle_event_results
- .push_back(Err(TradeListingEventError::InvalidOrder));
- hooks.handle_error_results.push_back(Ok(()));
- drop(hooks);
-
- process_event_notification(
- event_ok,
- keys.clone(),
- client.clone(),
- runtime.clone(),
- proof_policy(),
- )
- .await
- .expect("ok path");
- process_event_notification(event_err, keys, client, runtime, proof_policy())
- .await
- .expect("error path");
- }
-}
diff --git a/src/features/trade_validation_receipt.rs b/src/features/trade_validation_receipt.rs
@@ -1,848 +0,0 @@
-#![forbid(unsafe_code)]
-#![cfg_attr(coverage_nightly, coverage(off))]
-
-use radroots_event::ids::{RadrootsAddressableCoordinate, RadrootsEventId};
-use radroots_event::kinds::KIND_ORDER_DECISION;
-use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrEvent, RadrootsNostrKeys, RadrootsNostrKind,
- RadrootsNostrTimestamp, radroots_event_from_nostr, radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
-};
-use radroots_trade::order::{RadrootsOrderEventRecord, order_event_record_from_event};
-use radroots_trade::validation_receipt::{
- RadrootsTradeValidationReceipt, RadrootsValidationReceiptError,
- RadrootsValidationReceiptExpectedBinding, RadrootsValidationReceiptProof,
- RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult,
- RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, VALIDATION_RECEIPT_DOMAIN,
- VALIDATION_RECEIPT_VERSION, validation_receipt_event_build,
- validation_receipt_public_values_hash_hex, validator_set_address_from_str,
- verify_validation_receipt_event,
-};
-use radroots_trade::workflow::RadrootsTradeWorkflowState;
-use radroots_trade_sp1_host::RadrootsSp1TradeProofMode;
-use serde::{Deserialize, Serialize};
-use sha2::{Digest, Sha256};
-use std::time::{SystemTime, UNIX_EPOCH};
-use thiserror::Error;
-
-use crate::features::trade_listing::processed_jobs::{
- RhiProcessedJobClaim, RhiProcessedJobState, RhiProcessedJobStatus, RhiProcessedJobStoreError,
-};
-use crate::features::trade_listing::state::{
- TradeListingRuntime, TradeListingRuntimeError, TradeOrderState,
-};
-
-const RHI_PROCESSED_JOB_CLAIM_LEASE_MS: i64 = 10 * 60 * 1000;
-const ZERO_ERROR_BITMAP: &str = "0x00000000000000000000000000000000";
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum TradeValidationReceiptProverBackend {
- #[default]
- LocalExecute,
- LocalCpuProve,
- LocalCudaProve,
- RemoteHttpProve,
-}
-
-impl TradeValidationReceiptProverBackend {
- pub const fn as_str(self) -> &'static str {
- match self {
- Self::LocalExecute => "local_execute",
- Self::LocalCpuProve => "local_cpu_prove",
- Self::LocalCudaProve => "local_cuda_prove",
- Self::RemoteHttpProve => "remote_http_prove",
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(deny_unknown_fields)]
-pub struct TradeValidationReceiptProverPolicy {
- #[serde(default)]
- pub backend: TradeValidationReceiptProverBackend,
- #[serde(default = "default_proof_mode")]
- pub proof_mode: RadrootsSp1TradeProofMode,
- #[serde(default)]
- pub validator_set_addr: Option<String>,
- #[serde(default)]
- pub validator_set_event_id: Option<String>,
- #[serde(default)]
- pub expected_sp1_program_hash: Option<String>,
- #[serde(default)]
- pub expected_sp1_verifying_key_hash: Option<String>,
- #[serde(default)]
- pub remote_http: Option<TradeValidationReceiptRemoteHttpProverConfig>,
-}
-
-fn default_proof_mode() -> RadrootsSp1TradeProofMode {
- RadrootsSp1TradeProofMode::None
-}
-
-impl Default for TradeValidationReceiptProverPolicy {
- fn default() -> Self {
- Self {
- backend: TradeValidationReceiptProverBackend::LocalExecute,
- proof_mode: RadrootsSp1TradeProofMode::None,
- validator_set_addr: None,
- validator_set_event_id: None,
- expected_sp1_program_hash: None,
- expected_sp1_verifying_key_hash: None,
- remote_http: None,
- }
- }
-}
-
-impl TradeValidationReceiptProverPolicy {
- pub fn validate(&self) -> Result<(), TradeValidationReceiptJobError> {
- validate_optional_hash32(&self.expected_sp1_program_hash)?;
- validate_optional_hash32(&self.expected_sp1_verifying_key_hash)?;
- match self.backend {
- TradeValidationReceiptProverBackend::LocalExecute => {
- if self.proof_mode != RadrootsSp1TradeProofMode::None {
- return Err(TradeValidationReceiptJobError::ProverBackendRequiresNone);
- }
- if self.expected_sp1_program_hash.is_some()
- || self.expected_sp1_verifying_key_hash.is_some()
- {
- return Err(
- TradeValidationReceiptJobError::Sp1IdentityConstraintsRequireSp1Proof,
- );
- }
- Ok(())
- }
- TradeValidationReceiptProverBackend::LocalCpuProve
- | TradeValidationReceiptProverBackend::LocalCudaProve
- | TradeValidationReceiptProverBackend::RemoteHttpProve => {
- if self.proof_mode == RadrootsSp1TradeProofMode::None {
- return Err(TradeValidationReceiptJobError::ProverBackendRequiresSp1Proof);
- }
- if self.expected_sp1_program_hash.is_none()
- || self.expected_sp1_verifying_key_hash.is_none()
- {
- return Err(TradeValidationReceiptJobError::Sp1IdentityPolicyRequired);
- }
- match self.backend {
- TradeValidationReceiptProverBackend::LocalCpuProve
- | TradeValidationReceiptProverBackend::RemoteHttpProve => {
- if !cfg!(feature = "sp1_verify") {
- return Err(TradeValidationReceiptJobError::ProverBackendUnavailable(
- self.backend.as_str(),
- ));
- }
- }
- TradeValidationReceiptProverBackend::LocalCudaProve => {
- return Err(TradeValidationReceiptJobError::ProverBackendUnavailable(
- self.backend.as_str(),
- ));
- }
- TradeValidationReceiptProverBackend::LocalExecute => {}
- }
- if self.backend == TradeValidationReceiptProverBackend::RemoteHttpProve
- && self.remote_http.is_none()
- {
- return Err(TradeValidationReceiptJobError::RemoteHttpConfigRequired);
- }
- Ok(())
- }
- }
- }
-
- pub fn validator_binding(
- &self,
- ) -> Result<(RadrootsAddressableCoordinate, String), TradeValidationReceiptJobError> {
- let validator_set_addr = self.validator_set_addr.as_deref().ok_or(
- TradeValidationReceiptJobError::MissingValidatorSetBinding("validator_set_addr"),
- )?;
- let validator_set_event_id = self.validator_set_event_id.as_deref().ok_or(
- TradeValidationReceiptJobError::MissingValidatorSetBinding("validator_set_event_id"),
- )?;
- let validator_set_addr =
- validator_set_address_from_str(validator_set_addr).map_err(|_| {
- TradeValidationReceiptJobError::InvalidValidatorSetBinding("validator_set_addr")
- })?;
- let validator_set_event_id = RadrootsEventId::parse(validator_set_event_id)
- .map_err(|_| {
- TradeValidationReceiptJobError::InvalidValidatorSetBinding("validator_set_event_id")
- })?
- .into_string();
- Ok((validator_set_addr, validator_set_event_id))
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(deny_unknown_fields)]
-pub struct TradeValidationReceiptRemoteHttpProverConfig {
- pub endpoint_url: String,
- #[serde(default)]
- pub auth: Option<TradeValidationReceiptRemoteHttpAuth>,
- #[serde(default)]
- pub max_response_bytes: Option<usize>,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case", deny_unknown_fields)]
-pub enum TradeValidationReceiptRemoteHttpAuth {
- BearerEnv { env_var: String },
-}
-
-#[derive(Debug, Error)]
-pub enum TradeValidationReceiptJobError {
- #[error("event kind not supported")]
- UnsupportedKind,
- #[error("missing recipient tag")]
- MissingRecipient,
- #[error("trade validation receipt policy is missing {0}")]
- MissingValidatorSetBinding(&'static str),
- #[error("trade validation receipt policy has invalid {0}")]
- InvalidValidatorSetBinding(&'static str),
- #[error("trade validation receipt policy backend requires proof_mode none")]
- ProverBackendRequiresNone,
- #[error("trade validation receipt policy backend requires SP1 proof mode")]
- ProverBackendRequiresSp1Proof,
- #[error("trade validation receipt policy backend is unavailable: {0}")]
- ProverBackendUnavailable(&'static str),
- #[error("trade validation receipt policy requires SP1 program and verifying-key hashes")]
- Sp1IdentityPolicyRequired,
- #[error("SP1 identity constraints require an SP1 proof mode")]
- Sp1IdentityConstraintsRequireSp1Proof,
- #[error("remote HTTP prover config is required")]
- RemoteHttpConfigRequired,
- #[error("invalid configured hash field")]
- InvalidHashField,
- #[error("invalid active trade event: {0}")]
- InvalidActiveTradeEvent(String),
- #[error("invalid signed event")]
- InvalidSignedEvent,
- #[error("duplicate conflicting processed job")]
- DuplicateConflictingJob,
- #[error("duplicate conflicting receipt")]
- DuplicateConflictingReceipt,
- #[error("duplicate conflicting result")]
- DuplicateConflictingResult,
- #[error("stored processed job failed: {0}")]
- StoredProcessedJobFailed(String),
- #[error("nostr error: {0}")]
- Nostr(#[from] radroots_nostr::error::RadrootsNostrError),
- #[error("serde error: {0}")]
- Serde(#[from] serde_json::Error),
- #[error("validation receipt error: {0}")]
- ValidationReceipt(#[from] RadrootsValidationReceiptError),
- #[error("order decode error: {0}")]
- OrderDecode(#[from] radroots_trade::order::RadrootsOrderEventDecodeError),
- #[error("runtime state error: {0}")]
- Runtime(#[from] TradeListingRuntimeError),
- #[error("processed-job store error: {0}")]
- ProcessedJobStore(#[from] RhiProcessedJobStoreError),
-}
-
-#[derive(Clone, Debug, Serialize)]
-struct DeterministicReceiptPublicValues {
- changed_records_root: String,
- event_set_root: String,
- listing_event_id: String,
- new_state_root: String,
- order_id: String,
- previous_state_root: String,
- proof_system: String,
- result: String,
- schema_version: u32,
- target_event_id: String,
- validator_set_addr: String,
- validator_set_event_id: String,
-}
-
-pub async fn publish_validation_receipt(
- accepted_event: &RadrootsNostrEvent,
- client: &RadrootsNostrClient,
- runtime: &TradeListingRuntime,
- keys: &RadrootsNostrKeys,
- policy: &TradeValidationReceiptProverPolicy,
-) -> Result<(), TradeValidationReceiptJobError> {
- policy.validate()?;
- if policy.backend != TradeValidationReceiptProverBackend::LocalExecute
- || policy.proof_mode != RadrootsSp1TradeProofMode::None
- {
- return Err(TradeValidationReceiptJobError::ProverBackendUnavailable(
- policy.backend.as_str(),
- ));
- }
- let (validator_set_addr, validator_set_event_id) = policy.validator_binding()?;
- let kind = event_kind_u32(accepted_event)?;
- if kind != KIND_ORDER_DECISION {
- return Err(TradeValidationReceiptJobError::UnsupportedKind);
- }
- let rr_event = radroots_event_from_nostr(accepted_event);
- let record = order_event_record_from_event(&rr_event)?;
- let RadrootsOrderEventRecord::Decision(decision) = record else {
- return Err(TradeValidationReceiptJobError::UnsupportedKind);
- };
- if !matches!(
- decision.payload.decision,
- radroots_event::order::RadrootsOrderDecisionOutcome::Accepted { .. }
- ) {
- return Err(TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "order decision is not accepted".to_string(),
- ));
- }
- let order = {
- let state = runtime.state();
- let state = state.lock().await;
- state
- .get_order(decision.payload.order_id.as_str())
- .cloned()
- .ok_or_else(|| {
- TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "order is missing from RHI state".to_string(),
- )
- })?
- };
- if order.status != RadrootsTradeWorkflowState::AgreedPendingValidation {
- return Err(TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "order is not awaiting validation".to_string(),
- ));
- }
- let root_event_id = order.root_event_id.as_deref().ok_or_else(|| {
- TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "order is missing root event id".to_string(),
- )
- })?;
- let listing_event_id = order.listing_snapshot_event_id.as_deref().ok_or_else(|| {
- TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "order is missing listing snapshot event id".to_string(),
- )
- })?;
- let target_event_id = accepted_event.id.to_hex();
- if order.last_event_id.as_deref() != Some(target_event_id.as_str()) {
- return Err(TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "accepted event is not the current order head".to_string(),
- ));
- }
- let root_event = fetch_event_by_id_io(client, root_event_id).await?;
- let listing_event = fetch_event_by_id_io(client, listing_event_id).await?;
- verify_root_event(&root_event, &order)?;
- let receipt = deterministic_receipt_for_acceptance(
- &order,
- listing_event_id,
- root_event_id,
- target_event_id.as_str(),
- validator_set_addr,
- validator_set_event_id.as_str(),
- [&listing_event, &root_event, accepted_event],
- )?;
- let receipt_parts = validation_receipt_event_build(&order.order_id, &receipt)?;
- let receipt_event = signed_event_from_parts(
- keys,
- receipt_parts.kind,
- receipt_parts.content,
- receipt_parts.tags,
- Some(accepted_event.created_at.as_secs()),
- )?;
- verify_validation_receipt_event(
- &radroots_event_from_nostr(&receipt_event),
- RadrootsValidationReceiptExpectedBinding {
- order_id: Some(order.order_id.as_str()),
- listing_event_id: Some(listing_event_id),
- root_event_id: Some(root_event_id),
- target_event_id: Some(target_event_id.as_str()),
- validator_set_addr: policy.validator_set_addr.as_deref(),
- validator_set_event_id: Some(validator_set_event_id.as_str()),
- proof_system: Some(RadrootsValidationReceiptProofSystem::None),
- public_values_hash: Some(receipt.public_values_hash.as_str()),
- ..RadrootsValidationReceiptExpectedBinding::default()
- },
- )?;
- publish_receipt_with_processed_job(runtime, client, accepted_event, &order, receipt_event).await
-}
-
-async fn publish_receipt_with_processed_job(
- runtime: &TradeListingRuntime,
- client: &RadrootsNostrClient,
- accepted_event: &RadrootsNostrEvent,
- order: &TradeOrderState,
- receipt_event: RadrootsNostrEvent,
-) -> Result<(), TradeValidationReceiptJobError> {
- let receipt_event_id = receipt_event.id.to_hex();
- let receipt_event_json = serde_json::to_string(&receipt_event)?;
- let job = processed_job_for_receipt(accepted_event, order)?;
- match runtime
- .processed_jobs()
- .claim_job(&job, now_unix_ms(), RHI_PROCESSED_JOB_CLAIM_LEASE_MS)
- .await
- .map_err(processed_job_store_error)?
- {
- RhiProcessedJobClaim::Execute => {
- mark_receipt_publishing(
- runtime,
- &job,
- receipt_event_id.as_str(),
- receipt_event_json.as_str(),
- )
- .await?;
- let published_event_id = publish_signed_event_io(client, receipt_event).await?;
- if published_event_id != receipt_event_id {
- return Err(TradeValidationReceiptJobError::DuplicateConflictingReceipt);
- }
- mark_receipt_published(runtime, &job, receipt_event_id.as_str()).await?;
- mark_receipt_completed(
- runtime,
- &job,
- receipt_event_id.as_str(),
- receipt_event_json.as_str(),
- accepted_event_created_at(accepted_event),
- )
- .await
- }
- RhiProcessedJobClaim::InProgress | RhiProcessedJobClaim::Completed => Ok(()),
- RhiProcessedJobClaim::Failed { error_code } => Err(
- TradeValidationReceiptJobError::StoredProcessedJobFailed(error_code),
- ),
- RhiProcessedJobClaim::RecoverReceipt {
- receipt_event_id,
- receipt_event_json,
- }
- | RhiProcessedJobClaim::RecoverResult {
- receipt_event_id,
- receipt_event_json,
- ..
- } => {
- let receipt_event = signed_event_from_json(receipt_event_json.as_str())?;
- let published_event_id = publish_signed_event_io(client, receipt_event).await?;
- if published_event_id != receipt_event_id {
- return Err(TradeValidationReceiptJobError::DuplicateConflictingReceipt);
- }
- mark_receipt_published(runtime, &job, receipt_event_id.as_str()).await?;
- mark_receipt_completed(
- runtime,
- &job,
- receipt_event_id.as_str(),
- receipt_event_json.as_str(),
- accepted_event_created_at(accepted_event),
- )
- .await
- }
- }
-}
-
-fn deterministic_receipt_for_acceptance<'a>(
- order: &TradeOrderState,
- listing_event_id: &str,
- root_event_id: &str,
- target_event_id: &str,
- validator_set_addr: RadrootsAddressableCoordinate,
- validator_set_event_id: &str,
- events: impl IntoIterator<Item = &'a RadrootsNostrEvent>,
-) -> Result<RadrootsTradeValidationReceipt, TradeValidationReceiptJobError> {
- let canonical_event_json = events
- .into_iter()
- .map(serde_json::to_string)
- .collect::<Result<Vec<_>, _>>()?;
- let event_set_root = hash32_for_parts(
- "radroots:rhi-validation-event-set:v1",
- canonical_event_json.iter().map(String::as_str),
- );
- let previous_state_root = hash32_for_parts(
- "radroots:rhi-validation-previous-state:v1",
- [
- order.order_id.as_str(),
- root_event_id,
- workflow_state_label(RadrootsTradeWorkflowState::Requested),
- ],
- );
- let new_state_root = hash32_for_parts(
- "radroots:rhi-validation-new-state:v1",
- [
- order.order_id.as_str(),
- target_event_id,
- workflow_state_label(RadrootsTradeWorkflowState::AgreedPendingValidation),
- ],
- );
- let changed_records_root = hash32_for_parts(
- "radroots:rhi-validation-changed-records:v1",
- [order.order_id.as_str(), target_event_id],
- );
- let public_values = DeterministicReceiptPublicValues {
- changed_records_root: changed_records_root.clone(),
- event_set_root: event_set_root.clone(),
- listing_event_id: listing_event_id.to_string(),
- new_state_root: new_state_root.clone(),
- order_id: order.order_id.clone(),
- previous_state_root: previous_state_root.clone(),
- proof_system: RadrootsValidationReceiptProofSystem::None
- .as_str()
- .to_string(),
- result: "valid".to_string(),
- schema_version: 1,
- target_event_id: target_event_id.to_string(),
- validator_set_addr: validator_set_addr.as_str().to_string(),
- validator_set_event_id: validator_set_event_id.to_string(),
- };
- let public_values_json = serde_json::to_string(&public_values)?;
- Ok(RadrootsTradeValidationReceipt {
- changed_records_root,
- domain: VALIDATION_RECEIPT_DOMAIN.to_string(),
- error_bitmap: ZERO_ERROR_BITMAP.to_string(),
- event_set_root,
- new_state_root,
- previous_state_root,
- proof: RadrootsValidationReceiptProof {
- inline_proof_base64: None,
- mode: None,
- program_hash: None,
- proof_reference: None,
- system: RadrootsValidationReceiptProofSystem::None,
- verifying_key_hash: None,
- },
- public_values_hash: validation_receipt_public_values_hash_hex(
- public_values_json.as_bytes(),
- ),
- receipt_type: RadrootsValidationReceiptType::TradeTransition,
- result: RadrootsValidationReceiptResult::Valid,
- statement: RadrootsValidationReceiptStatement {
- listing_event_id: listing_event_id.to_string(),
- root_event_id: root_event_id.to_string(),
- target_event_id: target_event_id.to_string(),
- validator_set_addr,
- validator_set_event_id: validator_set_event_id.to_string(),
- statement_type: RadrootsValidationReceiptType::TradeTransition,
- },
- version: VALIDATION_RECEIPT_VERSION,
- })
-}
-
-fn verify_root_event(
- root_event: &RadrootsNostrEvent,
- order: &TradeOrderState,
-) -> Result<(), TradeValidationReceiptJobError> {
- let rr_event = radroots_event_from_nostr(root_event);
- let record = order_event_record_from_event(&rr_event)?;
- if record.order_id().as_str() != order.order_id {
- return Err(TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "root event order id mismatch".to_string(),
- ));
- }
- if !matches!(record, RadrootsOrderEventRecord::Request(_)) {
- return Err(TradeValidationReceiptJobError::InvalidActiveTradeEvent(
- "root event is not an order request".to_string(),
- ));
- }
- Ok(())
-}
-
-fn workflow_state_label(state: RadrootsTradeWorkflowState) -> &'static str {
- match state {
- RadrootsTradeWorkflowState::Missing => "missing",
- RadrootsTradeWorkflowState::Requested => "requested",
- RadrootsTradeWorkflowState::AgreedPendingValidation => "agreed_pending_validation",
- RadrootsTradeWorkflowState::Committed => "committed",
- RadrootsTradeWorkflowState::Declined => "declined",
- RadrootsTradeWorkflowState::Cancelled => "cancelled",
- RadrootsTradeWorkflowState::ValidationExpired => "validation_expired",
- RadrootsTradeWorkflowState::Invalid => "invalid",
- }
-}
-
-fn processed_job_for_receipt(
- accepted_event: &RadrootsNostrEvent,
- order: &TradeOrderState,
-) -> Result<RhiProcessedJobState, TradeValidationReceiptJobError> {
- Ok(RhiProcessedJobState {
- request_id: accepted_event.id.to_hex(),
- request_kind: event_kind_u32(accepted_event)?,
- request_hash: event_hash_hex(accepted_event)?,
- customer_pubkey: order.buyer_pubkey.clone(),
- status: RhiProcessedJobStatus::Processing,
- receipt_event_id: None,
- receipt_event_json: None,
- result_event_id: None,
- result_event_json: None,
- proof_metadata_json: None,
- error_code: None,
- created_timestamp: accepted_event_created_at(accepted_event),
- completed_timestamp: None,
- })
-}
-
-async fn mark_receipt_publishing(
- runtime: &TradeListingRuntime,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- receipt_event_json: &str,
-) -> Result<(), TradeValidationReceiptJobError> {
- runtime
- .processed_jobs()
- .mark_receipt_publishing(
- job,
- receipt_event_id,
- receipt_event_json,
- None,
- now_unix_ms(),
- )
- .await
- .map_err(processed_job_store_error)?;
- Ok(())
-}
-
-async fn mark_receipt_published(
- runtime: &TradeListingRuntime,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
-) -> Result<(), TradeValidationReceiptJobError> {
- runtime
- .processed_jobs()
- .mark_receipt_published(job, receipt_event_id, now_unix_ms())
- .await
- .map_err(processed_job_store_error)?;
- Ok(())
-}
-
-async fn mark_receipt_completed(
- runtime: &TradeListingRuntime,
- job: &RhiProcessedJobState,
- receipt_event_id: &str,
- receipt_event_json: &str,
- completed_timestamp: u32,
-) -> Result<(), TradeValidationReceiptJobError> {
- runtime
- .processed_jobs()
- .mark_receipt_completed(
- job,
- receipt_event_id,
- receipt_event_json,
- completed_timestamp,
- now_unix_ms(),
- )
- .await
- .map_err(processed_job_store_error)?;
- Ok(())
-}
-
-async fn fetch_event_by_id_io(
- client: &RadrootsNostrClient,
- event_id: &str,
-) -> Result<RadrootsNostrEvent, TradeValidationReceiptJobError> {
- #[cfg(test)]
- if let Some(event) = pop_fetch_event_by_id_hook() {
- return event;
- }
- Ok(radroots_nostr_fetch_event_by_id(client, event_id).await?)
-}
-
-async fn publish_signed_event_io(
- client: &RadrootsNostrClient,
- event: RadrootsNostrEvent,
-) -> Result<String, TradeValidationReceiptJobError> {
- #[cfg(test)]
- if let Some(result) = pop_publish_signed_event_hook(&event) {
- result?;
- return Ok(event.id.to_hex());
- }
- let output = client.send_event(&event).await?;
- Ok(output.val.to_hex())
-}
-
-fn signed_event_from_parts(
- keys: &RadrootsNostrKeys,
- kind: u32,
- content: String,
- tags: Vec<Vec<String>>,
- created_at_secs: Option<u64>,
-) -> Result<RadrootsNostrEvent, TradeValidationReceiptJobError> {
- let mut builder = radroots_nostr_build_event(kind, content, tags)?;
- if let Some(created_at_secs) = created_at_secs {
- builder = builder.custom_created_at(RadrootsNostrTimestamp::from_secs(created_at_secs));
- }
- builder
- .sign_with_keys(keys)
- .map_err(|_| TradeValidationReceiptJobError::InvalidSignedEvent)
-}
-
-fn signed_event_from_json(
- value: &str,
-) -> Result<RadrootsNostrEvent, TradeValidationReceiptJobError> {
- let event: RadrootsNostrEvent = serde_json::from_str(value)?;
- event
- .verify()
- .map_err(|_| TradeValidationReceiptJobError::InvalidSignedEvent)?;
- Ok(event)
-}
-
-fn event_kind_u32(event: &RadrootsNostrEvent) -> Result<u32, TradeValidationReceiptJobError> {
- match event.kind {
- RadrootsNostrKind::Custom(value) => Ok(u32::from(value)),
- _ => Err(TradeValidationReceiptJobError::UnsupportedKind),
- }
-}
-
-fn accepted_event_created_at(event: &RadrootsNostrEvent) -> u32 {
- u32::try_from(event.created_at.as_secs()).unwrap_or(u32::MAX)
-}
-
-fn event_hash_hex(event: &RadrootsNostrEvent) -> Result<String, TradeValidationReceiptJobError> {
- let canonical_event_json = serde_json::to_string(event)?;
- Ok(hash_hex_for_bytes(canonical_event_json.as_bytes()))
-}
-
-fn hash32_for_parts<'a>(domain: &str, parts: impl IntoIterator<Item = &'a str>) -> String {
- let mut hasher = Sha256::new();
- hasher.update(domain.as_bytes());
- for part in parts {
- hasher.update([0]);
- hasher.update(part.as_bytes());
- }
- format!("0x{}", lower_hex(hasher.finalize().as_slice()))
-}
-
-fn hash_hex_for_bytes(bytes: &[u8]) -> String {
- let mut hasher = Sha256::new();
- hasher.update(bytes);
- lower_hex(hasher.finalize().as_slice())
-}
-
-fn lower_hex(bytes: &[u8]) -> String {
- const HEX: &[u8; 16] = b"0123456789abcdef";
- let mut value = String::with_capacity(bytes.len() * 2);
- for byte in bytes {
- value.push(HEX[(byte >> 4) as usize] as char);
- value.push(HEX[(byte & 0x0f) as usize] as char);
- }
- value
-}
-
-fn validate_optional_hash32(value: &Option<String>) -> Result<(), TradeValidationReceiptJobError> {
- if let Some(value) = value
- && (value.len() != 66 || !value.starts_with("0x") || !is_lower_hex(&value[2..]))
- {
- return Err(TradeValidationReceiptJobError::InvalidHashField);
- }
- Ok(())
-}
-
-fn is_lower_hex(value: &str) -> bool {
- value
- .bytes()
- .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
-}
-
-fn now_unix_ms() -> i64 {
- let Ok(elapsed) = SystemTime::now().duration_since(UNIX_EPOCH) else {
- return 0;
- };
- i64::try_from(elapsed.as_millis()).unwrap_or(i64::MAX)
-}
-
-fn processed_job_store_error(error: RhiProcessedJobStoreError) -> TradeValidationReceiptJobError {
- match error {
- RhiProcessedJobStoreError::DuplicateConflictingJob => {
- TradeValidationReceiptJobError::DuplicateConflictingJob
- }
- RhiProcessedJobStoreError::DuplicateConflictingReceipt => {
- TradeValidationReceiptJobError::DuplicateConflictingReceipt
- }
- RhiProcessedJobStoreError::DuplicateConflictingResult => {
- TradeValidationReceiptJobError::DuplicateConflictingResult
- }
- other => TradeValidationReceiptJobError::ProcessedJobStore(other),
- }
-}
-
-#[cfg(test)]
-#[derive(Default)]
-struct TradeValidationReceiptTestHooks {
- fetch_event_by_id_results:
- std::collections::VecDeque<Result<RadrootsNostrEvent, TradeValidationReceiptJobError>>,
- publish_event_results:
- std::collections::VecDeque<Result<String, TradeValidationReceiptJobError>>,
- published_events: Vec<RadrootsNostrEvent>,
-}
-
-#[cfg(test)]
-static TRADE_VALIDATION_RECEIPT_TEST_HOOKS: std::sync::OnceLock<
- std::sync::Mutex<TradeValidationReceiptTestHooks>,
-> = std::sync::OnceLock::new();
-
-#[cfg(test)]
-fn trade_validation_receipt_test_hooks()
--> &'static std::sync::Mutex<TradeValidationReceiptTestHooks> {
- TRADE_VALIDATION_RECEIPT_TEST_HOOKS
- .get_or_init(|| std::sync::Mutex::new(TradeValidationReceiptTestHooks::default()))
-}
-
-#[cfg(test)]
-fn pop_fetch_event_by_id_hook() -> Option<Result<RadrootsNostrEvent, TradeValidationReceiptJobError>>
-{
- trade_validation_receipt_test_hooks()
- .lock()
- .expect("trade validation receipt hooks lock")
- .fetch_event_by_id_results
- .pop_front()
-}
-
-#[cfg(test)]
-fn pop_publish_signed_event_hook(
- event: &RadrootsNostrEvent,
-) -> Option<Result<String, TradeValidationReceiptJobError>> {
- let mut hooks = trade_validation_receipt_test_hooks()
- .lock()
- .expect("trade validation receipt hooks lock");
- hooks.published_events.push(event.clone());
- hooks.publish_event_results.pop_front()
-}
-
-#[cfg(test)]
-mod tests {
- use super::{
- TradeValidationReceiptProverBackend, TradeValidationReceiptProverPolicy,
- trade_validation_receipt_test_hooks,
- };
- use radroots_trade_sp1_host::RadrootsSp1TradeProofMode;
-
- #[test]
- fn default_policy_is_enabled_deterministic_receipt_path() {
- let policy = TradeValidationReceiptProverPolicy::default();
- assert_eq!(
- policy.backend,
- TradeValidationReceiptProverBackend::LocalExecute
- );
- assert_eq!(policy.proof_mode, RadrootsSp1TradeProofMode::None);
- }
-
- #[test]
- fn validator_binding_requires_address_and_event_id() {
- let policy = TradeValidationReceiptProverPolicy::default();
- let error = policy.validator_binding().expect_err("binding must fail");
- assert!(error.to_string().contains("validator_set_addr"));
- }
-
- #[test]
- fn validator_binding_accepts_canonical_set_reference() {
- let policy = TradeValidationReceiptProverPolicy {
- validator_set_addr: Some(
- "30381:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:018f3d99-7d35-7c0c-8a0f-7f3b645abcde"
- .to_string(),
- ),
- validator_set_event_id: Some(
- "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
- .to_string(),
- ),
- ..TradeValidationReceiptProverPolicy::default()
- };
- let binding = policy.validator_binding().expect("validator binding");
- assert_eq!(
- binding.0.as_str(),
- "30381:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:018f3d99-7d35-7c0c-8a0f-7f3b645abcde"
- );
- assert_eq!(
- binding.1,
- "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
- );
- }
-
- #[test]
- fn test_hooks_reset() {
- *trade_validation_receipt_test_hooks()
- .lock()
- .expect("trade validation receipt hooks lock") =
- super::TradeValidationReceiptTestHooks::default();
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
@@ -6,17 +6,18 @@ pub mod config;
pub mod features;
pub mod identity_storage;
pub mod paths;
-pub mod proof_smoke;
-pub mod remote_prove;
pub mod rhi;
pub use cli::Args as cli_args;
use anyhow::Result;
-use radroots_event::kinds::{KIND_LISTING, ORDER_EVENT_KINDS, TRADE_VALIDATION_EVENT_KINDS};
+use radroots_event::kinds::TRADE_MUTATION_EVENT_KINDS;
use std::time::Duration;
-use crate::features::trade_listing::state::{TradeListingRuntime, TradeListingRuntimeConfig};
+use crate::features::trade_agreement_attestation::{
+ TradeAgreementAttestationRuntime, TradeAgreementAttestationRuntimeConfig,
+ trade_mutation_subscription_kinds,
+};
use crate::identity_storage::load_service_identity;
use crate::rhi::{Rhi, start_subscriber_with_policy};
use radroots_identity::RadrootsIdentity;
@@ -116,17 +117,18 @@ pub async fn run_rhi(settings: &config::Settings, args: &cli_args) -> Result<()>
args.service.allow_generate_identity,
)?;
let keys = identity.keys().clone();
- let trade_listing_runtime = TradeListingRuntime::load(TradeListingRuntimeConfig {
- state_path: settings.config.subscriber.state.path.clone(),
- replay_window_secs: settings.config.subscriber.state.replay_window_secs,
- replay_overlap_secs: settings.config.subscriber.state.replay_overlap_secs,
- })
- .await?;
-
- let rhi = Rhi::with_trade_listing_runtime_and_policy(
+ let agreement_attestation_runtime =
+ TradeAgreementAttestationRuntime::load(TradeAgreementAttestationRuntimeConfig {
+ state_path: settings.config.subscriber.state.path.clone(),
+ replay_window_secs: settings.config.subscriber.state.replay_window_secs,
+ replay_overlap_secs: settings.config.subscriber.state.replay_overlap_secs,
+ })
+ .await?;
+
+ let rhi = Rhi::with_agreement_attestation_runtime_and_policy(
keys.clone(),
- trade_listing_runtime,
- settings.config.trade_validation_receipt.clone(),
+ agreement_attestation_runtime,
+ settings.config.trade_agreement_attestation.clone(),
);
let client = rhi.client.clone();
let service_cfg = settings.config.service.clone();
@@ -139,11 +141,7 @@ pub async fn run_rhi(settings: &config::Settings, args: &cli_args) -> Result<()>
let md = settings.metadata.clone();
if !relays.is_empty() {
- let handler_kinds = [KIND_LISTING]
- .into_iter()
- .chain(ORDER_EVENT_KINDS)
- .chain(TRADE_VALIDATION_EVENT_KINDS)
- .collect();
+ let handler_kinds = trade_mutation_subscription_kinds();
let handler_spec = RadrootsNostrApplicationHandlerSpec {
kinds: handler_kinds,
identifier: service_cfg.nip89_identifier.clone(),
@@ -167,8 +165,8 @@ pub async fn run_rhi(settings: &config::Settings, args: &cli_args) -> Result<()>
let handle = start_subscriber_with_policy(
client.clone(),
keys.clone(),
- rhi.trade_listing_runtime.clone(),
- rhi.trade_validation_receipt_policy.clone(),
+ rhi.agreement_attestation_runtime.clone(),
+ rhi.agreement_attestation_policy.clone(),
settings.config.subscriber.backoff.clone(),
)
.await;
@@ -182,7 +180,7 @@ pub async fn run_rhi(settings: &config::Settings, args: &cli_args) -> Result<()>
match wait_for_shutdown_or_stopped(handle).await {
RunRhiWaitOutcome::Shutdown => {
- info!("Shutting down…");
+ info!("Shutting down");
stop_handle.stop();
}
RunRhiWaitOutcome::Stopped => {}
@@ -194,14 +192,19 @@ pub async fn run_rhi(settings: &config::Settings, args: &cli_args) -> Result<()>
Ok(())
}
+pub fn release_product_handler_kinds() -> &'static [u32] {
+ &TRADE_MUTATION_EVENT_KINDS
+}
+
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{
- RUN_RHI_AUTO_STOP, RUN_RHI_SKIP_SUBSCRIBER, RunRhiWaitOutcome, bootstrap_presence, run_rhi,
- run_rhi_bootstrap_hook, run_rhi_wait_hook,
+ RUN_RHI_AUTO_STOP, RUN_RHI_SKIP_SUBSCRIBER, RunRhiWaitOutcome, bootstrap_presence,
+ release_product_handler_kinds, run_rhi, run_rhi_bootstrap_hook, run_rhi_wait_hook,
};
use crate::{cli_args, config};
+ use radroots_event::kinds::TRADE_MUTATION_EVENT_KINDS;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::{Mutex, MutexGuard};
@@ -253,8 +256,8 @@ mod tests {
..Default::default()
},
},
- trade_validation_receipt:
- crate::features::trade_validation_receipt::TradeValidationReceiptProverPolicy::default(),
+ trade_agreement_attestation:
+ crate::features::trade_agreement_attestation::TradeAgreementAttestationPolicy::default(),
},
}
}
@@ -278,11 +281,6 @@ mod tests {
std::env::temp_dir().join(format!("rhi-{suffix}-{nanos}.secret.json"))
}
- fn cleanup_identity_artifacts(path: &std::path::Path) {
- let _ = std::fs::remove_file(path);
- let _ = std::fs::remove_file(crate::identity_storage::encrypted_identity_key_path(path));
- }
-
fn unique_state_path(suffix: &str) -> PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -294,118 +292,64 @@ mod tests {
}
#[tokio::test]
- async fn run_rhi_completes_with_auto_stop_and_empty_relays() {
+ async fn run_rhi_starts_and_stops_without_relays() {
let _guard = test_guard();
RUN_RHI_AUTO_STOP.store(true, Ordering::Relaxed);
- RUN_RHI_SKIP_SUBSCRIBER.store(false, Ordering::Relaxed);
- let path = unique_identity_path("empty");
- let args = args_for_identity(path.clone());
+ let identity_path = unique_identity_path("no-relays");
+ let args = args_for_identity(identity_path);
let settings = settings_with_relays(Vec::new());
- let result = run_rhi(&settings, &args).await;
- RUN_RHI_AUTO_STOP.store(false, Ordering::Relaxed);
- cleanup_identity_artifacts(&path);
- assert!(result.is_ok());
+ run_rhi(&settings, &args).await.expect("run rhi");
}
#[tokio::test]
- async fn run_rhi_covers_presence_success_and_failure_branches() {
+ async fn run_rhi_bootstraps_release_product_handler_kinds_when_relays_exist() {
let _guard = test_guard();
- RUN_RHI_AUTO_STOP.store(true, Ordering::Relaxed);
RUN_RHI_SKIP_SUBSCRIBER.store(true, Ordering::Relaxed);
-
- let path_ok = unique_identity_path("presence-ok");
- let args_ok = args_for_identity(path_ok.clone());
- let settings_ok = settings_with_relays(vec!["wss://relay.example.com".to_string()]);
*run_rhi_bootstrap_hook()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(Ok(()));
- let ok = run_rhi(&settings_ok, &args_ok).await;
- cleanup_identity_artifacts(&path_ok);
- assert!(ok.is_ok());
-
- let path_err = unique_identity_path("presence-err");
- let args_err = args_for_identity(path_err.clone());
- let settings_err = settings_with_relays(vec!["wss://relay.example.com".to_string()]);
- *run_rhi_bootstrap_hook()
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner) =
- Some(Err("presence failure".to_string()));
- let err = run_rhi(&settings_err, &args_err).await;
- RUN_RHI_AUTO_STOP.store(false, Ordering::Relaxed);
- RUN_RHI_SKIP_SUBSCRIBER.store(false, Ordering::Relaxed);
- cleanup_identity_artifacts(&path_err);
- assert!(err.is_ok());
+ let identity_path = unique_identity_path("relays");
+ let args = args_for_identity(identity_path);
+ let settings = settings_with_relays(vec!["wss://relay.example.com".to_string()]);
+ run_rhi(&settings, &args).await.expect("run rhi");
+ assert_eq!(release_product_handler_kinds(), TRADE_MUTATION_EVENT_KINDS);
}
#[tokio::test]
- async fn bootstrap_presence_application_handler_path_is_callable() {
+ async fn run_rhi_stops_on_wait_hook() {
let _guard = test_guard();
- let identity_path = unique_identity_path("bootstrap");
- let identity = crate::identity_storage::load_service_identity(Some(&identity_path), true)
- .expect("identity");
- let client = radroots_nostr::prelude::RadrootsNostrClient::new(identity.keys().clone());
- let metadata: radroots_nostr::prelude::RadrootsNostrMetadata =
- serde_json::from_str(r#"{"name":"bootstrap"}"#).expect("bootstrap metadata");
- let handler_spec = radroots_nostr::prelude::RadrootsNostrApplicationHandlerSpec {
- kinds: vec![30402],
- identifier: Some("rhi".to_string()),
- metadata: Some(metadata.clone()),
- extra_tags: Vec::new(),
- relays: vec!["wss://relay.example.com".to_string()],
- nostrconnect_url: None,
- };
- let result = bootstrap_presence(&client, &identity, &metadata, &handler_spec).await;
- cleanup_identity_artifacts(&identity_path);
- assert!(result.is_err());
- }
-
- #[tokio::test]
- async fn run_rhi_covers_shutdown_wait_branch() {
- let _guard = test_guard();
- RUN_RHI_AUTO_STOP.store(false, Ordering::Relaxed);
- RUN_RHI_SKIP_SUBSCRIBER.store(false, Ordering::Relaxed);
*run_rhi_wait_hook()
.lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(RunRhiWaitOutcome::Shutdown);
-
- let path = unique_identity_path("shutdown");
- let args = args_for_identity(path.clone());
+ .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(RunRhiWaitOutcome::Stopped);
+ let identity_path = unique_identity_path("wait-hook");
+ let args = args_for_identity(identity_path);
let settings = settings_with_relays(Vec::new());
- let result = run_rhi(&settings, &args).await;
- cleanup_identity_artifacts(&path);
- assert!(result.is_ok());
+ run_rhi(&settings, &args).await.expect("run rhi");
}
#[tokio::test]
- async fn run_rhi_returns_error_when_relay_configuration_is_invalid() {
+ async fn bootstrap_presence_reports_hook_error() {
let _guard = test_guard();
- RUN_RHI_AUTO_STOP.store(false, Ordering::Relaxed);
- RUN_RHI_SKIP_SUBSCRIBER.store(false, Ordering::Relaxed);
-
- let path = unique_identity_path("invalid-relay");
- let args = args_for_identity(path.clone());
- let settings = settings_with_relays(vec!["not-a-relay-url".to_string()]);
- let result = run_rhi(&settings, &args).await;
- cleanup_identity_artifacts(&path);
- assert!(result.is_err());
- }
-
- #[tokio::test]
- async fn run_rhi_returns_error_when_identity_is_missing() {
- let _guard = test_guard();
- RUN_RHI_AUTO_STOP.store(false, Ordering::Relaxed);
- RUN_RHI_SKIP_SUBSCRIBER.store(false, Ordering::Relaxed);
-
- let args = cli_args {
- command: None,
- service: radroots_runtime::RadrootsServiceCliArgs {
- config: Some(PathBuf::from("config.toml")),
- identity: Some(PathBuf::from("/tmp/rhi-lib-missing-identity.secret.json")),
- allow_generate_identity: false,
- },
+ *run_rhi_bootstrap_hook()
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner) =
+ Some(Err("forced bootstrap failure".to_string()));
+ let keys = radroots_nostr::prelude::RadrootsNostrKeys::generate();
+ let client = radroots_nostr::prelude::RadrootsNostrClient::new(keys.clone());
+ let identity = radroots_identity::RadrootsIdentity::new(keys);
+ let metadata: radroots_nostr::prelude::RadrootsNostrMetadata =
+ serde_json::from_str(r#"{"name":"rhi-test"}"#).expect("metadata");
+ let spec = radroots_nostr::prelude::RadrootsNostrApplicationHandlerSpec {
+ kinds: TRADE_MUTATION_EVENT_KINDS.to_vec(),
+ identifier: Some("rhi".to_string()),
+ metadata: Some(metadata.clone()),
+ extra_tags: Vec::new(),
+ relays: Vec::new(),
+ nostrconnect_url: None,
};
- let settings = settings_with_relays(Vec::new());
- let result = run_rhi(&settings, &args).await;
- assert!(result.is_err());
+ let err = bootstrap_presence(&client, &identity, &metadata, &spec)
+ .await
+ .expect_err("forced error");
+ assert!(format!("{err:#}").contains("forced bootstrap failure"));
}
}
diff --git a/src/main.rs b/src/main.rs
@@ -9,9 +9,9 @@ use clap::Parser;
use radroots_log::{LogFileLayout, LoggingOptions};
#[cfg(not(test))]
use rhi::cli::Command;
-use rhi::{cli_args, config, paths, run_rhi};
#[cfg(not(test))]
-use rhi::{proof_smoke, remote_prove};
+use rhi::features::trade_agreement_attestation::run_smoke_cli_command;
+use rhi::{cli_args, config, paths, run_rhi};
use std::path::PathBuf;
use std::process::ExitCode;
use tracing::info;
@@ -217,8 +217,7 @@ async fn run() -> Result<()> {
let args = cli_args::try_parse().map_err(radroots_runtime::RuntimeCliError::from)?;
if let Some(command) = args.command {
return match command {
- Command::ProofSmoke { .. } => proof_smoke::run_cli_command(command).await,
- Command::RemoteProve { .. } => remote_prove::run_cli_command(command).await,
+ Command::AttestationSmoke { .. } => run_smoke_cli_command(command).await,
};
}
}
@@ -245,7 +244,7 @@ mod tests {
runtime_startup_report,
};
use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrKeys};
- use rhi::features::trade_listing::state::TradeListingRuntime;
+ use rhi::features::trade_agreement_attestation::TradeAgreementAttestationRuntime;
use rhi::{cli_args, config, paths};
use std::path::PathBuf;
use std::process::ExitCode;
@@ -276,8 +275,8 @@ mod tests {
stdout: true,
},
subscriber: config::SubscriberConfig::default(),
- trade_validation_receipt:
- rhi::features::trade_validation_receipt::TradeValidationReceiptProverPolicy::default(),
+ trade_agreement_attestation:
+ rhi::features::trade_agreement_attestation::TradeAgreementAttestationPolicy::default(),
},
}
}
@@ -311,7 +310,7 @@ mod tests {
"/home/treesap/.radroots/secrets/workers/rhi/identity.secret.json",
),
canonical_subscriber_state_path: PathBuf::from(
- "/home/treesap/.radroots/data/workers/rhi/trade-listing/state.json",
+ "/home/treesap/.radroots/data/workers/rhi/trade-agreement-attestation/state.json",
),
}
}
@@ -392,7 +391,7 @@ mod tests {
let handle = rhi::rhi::start_subscriber(
client,
keys,
- TradeListingRuntime::new(),
+ TradeAgreementAttestationRuntime::new(),
radroots_runtime::BackoffConfig {
base_ms: 1,
max_ms: 2,
@@ -444,7 +443,7 @@ mod tests {
subscriber_state_path: PathBuf::from("/tmp/rhi/state.json"),
subscriber_state_path_source: "config_artifact".to_string(),
canonical_subscriber_state_path: PathBuf::from(
- "/home/treesap/.radroots/data/workers/rhi/trade-listing/state.json"
+ "/home/treesap/.radroots/data/workers/rhi/trade-agreement-attestation/state.json"
),
path_overrides: sample_runtime_contract().path_overrides,
default_shared_secret_backend: "encrypted_file".to_string(),
diff --git a/src/paths.rs b/src/paths.rs
@@ -8,7 +8,7 @@ use radroots_runtime_paths::{
use serde::Serialize;
const RHI_RUNTIME_ID: &str = "rhi";
-const SUBSCRIBER_STATE_DIR_NAME: &str = "trade-listing";
+const SUBSCRIBER_STATE_DIR_NAME: &str = "trade-agreement-attestation";
const SUBSCRIBER_STATE_FILE_NAME: &str = "state.json";
const RHI_PATHS_PROFILE_ENV: &str = "RHI_PATHS_PROFILE";
const RHI_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RHI_PATHS_REPO_LOCAL_ROOT";
diff --git a/src/proof_smoke.rs b/src/proof_smoke.rs
@@ -1,638 +0,0 @@
-#![forbid(unsafe_code)]
-#![cfg_attr(coverage_nightly, coverage(off))]
-
-use crate::cli::Command;
-use radroots_trade_sp1_guest::{
- RADROOTS_SP1_TRADE_KIND_LISTING, RADROOTS_SP1_TRADE_KIND_ORDER_DECISION,
- RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST, RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET,
- RADROOTS_SP1_TRADE_PROTOCOL_VERSION, RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH,
- RADROOTS_SP1_TRADE_WITNESS_VERSION, RadrootsSp1TradeCanonicalEventEvidence,
- RadrootsSp1TradeEventEvidenceRole, RadrootsSp1TradeEventWorkflowPosition,
- RadrootsSp1TradeInventoryBinWitness, RadrootsSp1TradeInventoryCommitmentWitness,
- RadrootsSp1TradeOrderAcceptanceWitness, RadrootsSp1TradeOrderDecisionEventWitness,
- RadrootsSp1TradeOrderDecisionWitness, RadrootsSp1TradeOrderItemWitness,
- RadrootsSp1TradeOrderRequestWitness,
-};
-use radroots_trade_sp1_host::{
- RadrootsSp1TradeProofMode, generate_order_acceptance_proof,
- verify_order_acceptance_proof_artifact_structure,
-};
-use serde::{Deserialize, Serialize};
-use std::path::Path;
-use std::time::Instant;
-use thiserror::Error;
-
-const PROTOCOL_VERSION: &str = "radroots.rhi.proof_smoke.v0";
-const WORKER_NAME: &str = "rhi";
-const SP1_VERSION: &str = "6.2.1";
-const ORDER_ACCEPTANCE_TINY_FIXTURE: &str = "order_acceptance_tiny_v1";
-
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(deny_unknown_fields)]
-pub struct RhiProofSmokeRequest {
- pub protocol_version: String,
- pub operation: RhiProofSmokeOperation,
- pub backend: RhiProofSmokeBackend,
- #[serde(default)]
- pub fixture: Option<String>,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum RhiProofSmokeOperation {
- Health,
- ProofSmoke,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum RhiProofSmokeBackend {
- ValidatorSetDeterministic,
- LocalExecute,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct RhiProofSmokeResponse {
- pub ok: bool,
- pub protocol_version: String,
- pub operation: RhiProofSmokeOperation,
- pub worker_name: String,
- pub worker_version: String,
- pub git_rev: String,
- pub sp1_version: String,
- pub backend: RhiProofSmokeBackend,
- pub capabilities: Vec<String>,
- pub proof_generated: bool,
- pub public_values_hash: Option<String>,
- pub sp1_program_hash: Option<String>,
- pub sp1_verifying_key_hash: Option<String>,
- pub event_set_root: Option<String>,
- pub reducer_output_root: Option<String>,
- pub elapsed_ms: u128,
- pub warnings: Vec<String>,
- pub error: Option<String>,
-}
-
-#[derive(Debug, Error, PartialEq, Eq)]
-pub enum RhiProofSmokeError {
- #[error("invalid protocol version")]
- InvalidProtocolVersion,
- #[error("proof_smoke requires fixture order_acceptance_tiny_v1")]
- InvalidFixture,
- #[error("local_execute backend is unavailable in this build")]
- LocalExecuteUnavailable,
- #[error("deterministic proof smoke failed: {0}")]
- Deterministic(String),
- #[error("SP1 execute proof smoke failed: {0}")]
- Sp1Execute(String),
-}
-
-pub async fn run_cli_command(command: Command) -> anyhow::Result<()> {
- let Command::ProofSmoke { input, output } = command else {
- return Err(anyhow::anyhow!("proof-smoke command expected"));
- };
- let request_bytes = read_input(input.as_deref())?;
- let response = handle_request_bytes(&request_bytes).await;
- let response_bytes = serde_json::to_vec_pretty(&response)?;
- write_output(output.as_deref(), &response_bytes)?;
- if response.ok {
- Ok(())
- } else {
- Err(anyhow::anyhow!(
- "{}",
- response
- .error
- .as_deref()
- .unwrap_or("proof smoke request failed")
- ))
- }
-}
-
-pub async fn handle_request_bytes(bytes: &[u8]) -> RhiProofSmokeResponse {
- let started = Instant::now();
- match serde_json::from_slice::<RhiProofSmokeRequest>(bytes) {
- Ok(request) => response_for_request(request, started).await,
- Err(error) => response_for_error(
- RhiProofSmokeOperation::Health,
- RhiProofSmokeBackend::ValidatorSetDeterministic,
- started,
- error.to_string(),
- ),
- }
-}
-
-async fn response_for_request(
- request: RhiProofSmokeRequest,
- started: Instant,
-) -> RhiProofSmokeResponse {
- if request.protocol_version != PROTOCOL_VERSION {
- return response_for_error(
- request.operation,
- request.backend,
- started,
- RhiProofSmokeError::InvalidProtocolVersion.to_string(),
- );
- }
-
- match request.operation {
- RhiProofSmokeOperation::Health => response_for_success(
- request.operation,
- request.backend,
- started,
- None,
- None,
- None,
- None,
- None,
- Vec::new(),
- ),
- RhiProofSmokeOperation::ProofSmoke => {
- match run_proof_smoke(request.backend, request.fixture).await {
- Ok(output) => response_for_success(
- request.operation,
- request.backend,
- started,
- Some(output.public_values_hash),
- output.sp1_program_hash,
- output.sp1_verifying_key_hash,
- Some(output.event_set_root),
- Some(output.reducer_output_root),
- output.warnings,
- ),
- Err(error) => response_for_error(
- request.operation,
- request.backend,
- started,
- error.to_string(),
- ),
- }
- }
- }
-}
-
-struct RhiProofSmokeOutput {
- public_values_hash: String,
- sp1_program_hash: Option<String>,
- sp1_verifying_key_hash: Option<String>,
- event_set_root: String,
- reducer_output_root: String,
- warnings: Vec<String>,
-}
-
-async fn run_proof_smoke(
- backend: RhiProofSmokeBackend,
- fixture: Option<String>,
-) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> {
- if fixture.as_deref() != Some(ORDER_ACCEPTANCE_TINY_FIXTURE) {
- return Err(RhiProofSmokeError::InvalidFixture);
- }
-
- let witness = order_acceptance_tiny_witness();
- match backend {
- RhiProofSmokeBackend::ValidatorSetDeterministic => deterministic_smoke(&witness),
- RhiProofSmokeBackend::LocalExecute => local_execute_smoke(&witness).await,
- }
-}
-
-fn deterministic_smoke(
- witness: &RadrootsSp1TradeOrderAcceptanceWitness,
-) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> {
- let bundle = generate_order_acceptance_proof(witness, RadrootsSp1TradeProofMode::None)
- .map_err(|error| RhiProofSmokeError::Deterministic(error.to_string()))?;
- verify_order_acceptance_proof_artifact_structure(&bundle.execution, &bundle.proof)
- .map_err(|error| RhiProofSmokeError::Deterministic(error.to_string()))?;
- Ok(RhiProofSmokeOutput {
- public_values_hash: canonical_hex_64(&bundle.execution.public_values_hash)?,
- sp1_program_hash: None,
- sp1_verifying_key_hash: None,
- event_set_root: canonical_hex_64(&bundle.execution.public_values.event_set_root)?,
- reducer_output_root: canonical_hex_64(&bundle.execution.public_values.new_state_root)?,
- warnings: Vec::new(),
- })
-}
-
-#[cfg(feature = "sp1_proving")]
-async fn local_execute_smoke(
- witness: &RadrootsSp1TradeOrderAcceptanceWitness,
-) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> {
- if !radroots_trade_sp1_host::order_acceptance_sp1_guest_elf_available() {
- return Err(RhiProofSmokeError::LocalExecuteUnavailable);
- }
- let execution = radroots_trade_sp1_host::execute_order_acceptance_sp1_public_values(witness)
- .await
- .map_err(|error| RhiProofSmokeError::Sp1Execute(error.to_string()))?
- .execution;
- Ok(RhiProofSmokeOutput {
- public_values_hash: canonical_hex_64(&execution.public_values_hash)?,
- sp1_program_hash: execution
- .public_values
- .sp1_program_hash
- .as_deref()
- .map(canonical_hex_64)
- .transpose()?,
- sp1_verifying_key_hash: execution
- .public_values
- .sp1_verifying_key_hash
- .as_deref()
- .map(canonical_hex_64)
- .transpose()?,
- event_set_root: canonical_hex_64(&execution.public_values.event_set_root)?,
- reducer_output_root: canonical_hex_64(&execution.public_values.new_state_root)?,
- warnings: Vec::new(),
- })
-}
-
-#[cfg(not(feature = "sp1_proving"))]
-async fn local_execute_smoke(
- _witness: &RadrootsSp1TradeOrderAcceptanceWitness,
-) -> Result<RhiProofSmokeOutput, RhiProofSmokeError> {
- Err(RhiProofSmokeError::LocalExecuteUnavailable)
-}
-
-fn response_for_success(
- operation: RhiProofSmokeOperation,
- backend: RhiProofSmokeBackend,
- started: Instant,
- public_values_hash: Option<String>,
- sp1_program_hash: Option<String>,
- sp1_verifying_key_hash: Option<String>,
- event_set_root: Option<String>,
- reducer_output_root: Option<String>,
- warnings: Vec<String>,
-) -> RhiProofSmokeResponse {
- RhiProofSmokeResponse {
- ok: true,
- protocol_version: PROTOCOL_VERSION.to_string(),
- operation,
- worker_name: WORKER_NAME.to_string(),
- worker_version: env!("CARGO_PKG_VERSION").to_string(),
- git_rev: option_env!("RADROOTS_GIT_REV")
- .unwrap_or("unknown")
- .to_string(),
- sp1_version: SP1_VERSION.to_string(),
- backend,
- capabilities: capabilities(),
- proof_generated: false,
- public_values_hash,
- sp1_program_hash,
- sp1_verifying_key_hash,
- event_set_root,
- reducer_output_root,
- elapsed_ms: started.elapsed().as_millis(),
- warnings,
- error: None,
- }
-}
-
-fn canonical_hex_64(value: &str) -> Result<String, RhiProofSmokeError> {
- let candidate = value.strip_prefix("0x").unwrap_or(value);
- if candidate.len() == 64 && candidate.bytes().all(|byte| byte.is_ascii_hexdigit()) {
- return Ok(candidate.to_ascii_lowercase());
- }
- Err(RhiProofSmokeError::Deterministic(
- "public value is not canonical 32-byte hex".to_string(),
- ))
-}
-
-fn response_for_error(
- operation: RhiProofSmokeOperation,
- backend: RhiProofSmokeBackend,
- started: Instant,
- error: String,
-) -> RhiProofSmokeResponse {
- let mut response = response_for_success(
- operation,
- backend,
- started,
- None,
- None,
- None,
- None,
- None,
- Vec::new(),
- );
- response.ok = false;
- response.error = Some(error);
- response
-}
-
-fn capabilities() -> Vec<String> {
- let mut values = vec![
- "health".to_string(),
- "proof_smoke".to_string(),
- "validator_set_deterministic".to_string(),
- ];
- if cfg!(feature = "sp1_proving")
- && radroots_trade_sp1_host::order_acceptance_sp1_guest_elf_available()
- {
- values.push("local_execute".to_string());
- }
- values
-}
-
-pub(crate) fn order_acceptance_tiny_witness() -> RadrootsSp1TradeOrderAcceptanceWitness {
- RadrootsSp1TradeOrderAcceptanceWitness {
- witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION,
- proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(),
- listing_event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- .to_string(),
- request_event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
- .to_string(),
- decision_event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
- .to_string(),
- validator_set_addr:
- "30381:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd:018f3d99-7d35-7c0c-8a0f-7f3b645abcde"
- .to_string(),
- validator_set_event_id: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
- .to_string(),
- event_evidence: order_acceptance_tiny_event_evidence(),
- request: RadrootsSp1TradeOrderRequestWitness {
- order_id: "order-1".to_string(),
- listing_addr:
- "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1"
- .to_string(),
- buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222"
- .to_string(),
- seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
- .to_string(),
- items: vec![RadrootsSp1TradeOrderItemWitness {
- bin_id: "bin-1".to_string(),
- bin_count: 2,
- }],
- },
- decision: RadrootsSp1TradeOrderDecisionEventWitness {
- order_id: "order-1".to_string(),
- listing_addr:
- "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1"
- .to_string(),
- buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222"
- .to_string(),
- seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
- .to_string(),
- decision: RadrootsSp1TradeOrderDecisionWitness::Accepted {
- inventory_commitments: vec![RadrootsSp1TradeInventoryCommitmentWitness {
- bin_id: "bin-1".to_string(),
- bin_count: 2,
- }],
- },
- },
- inventory_bins: vec![RadrootsSp1TradeInventoryBinWitness {
- bin_id: "bin-1".to_string(),
- listing_capacity: 5,
- previous_reserved: 1,
- }],
- inventory_sequence: 7,
- previous_state_root: None,
- reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(),
- radroots_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(),
- sp1_program_hash: None,
- sp1_verifying_key_hash: None,
- }
-}
-
-fn order_acceptance_tiny_event_evidence() -> Vec<RadrootsSp1TradeCanonicalEventEvidence> {
- vec![
- RadrootsSp1TradeCanonicalEventEvidence {
- event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- .to_string(),
- signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
- .to_string(),
- kind: RADROOTS_SP1_TRADE_KIND_LISTING,
- canonical_event_hash:
- "0x1010101010101010101010101010101010101010101010101010101010101010".to_string(),
- signature_hash: "0x1111111111111111111111111111111111111111111111111111111111111111"
- .to_string(),
- preverified_signature: true,
- role: RadrootsSp1TradeEventEvidenceRole::Seller,
- workflow_position: RadrootsSp1TradeEventWorkflowPosition::Listing,
- content_hash: "0x1212121212121212121212121212121212121212121212121212121212121212"
- .to_string(),
- tags_hash: "0x1313131313131313131313131313131313131313131313131313131313131313"
- .to_string(),
- ordering_key: "001:listing".to_string(),
- },
- RadrootsSp1TradeCanonicalEventEvidence {
- event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
- .to_string(),
- signer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222"
- .to_string(),
- kind: RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST,
- canonical_event_hash:
- "0x2020202020202020202020202020202020202020202020202020202020202020".to_string(),
- signature_hash: "0x2121212121212121212121212121212121212121212121212121212121212121"
- .to_string(),
- preverified_signature: true,
- role: RadrootsSp1TradeEventEvidenceRole::Buyer,
- workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderRequest,
- content_hash: "0x2222222222222222222222222222222222222222222222222222222222222222"
- .to_string(),
- tags_hash: "0x2323232323232323232323232323232323232323232323232323232323232323"
- .to_string(),
- ordering_key: "002:order_request".to_string(),
- },
- RadrootsSp1TradeCanonicalEventEvidence {
- event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
- .to_string(),
- signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111"
- .to_string(),
- kind: RADROOTS_SP1_TRADE_KIND_ORDER_DECISION,
- canonical_event_hash:
- "0x3030303030303030303030303030303030303030303030303030303030303030".to_string(),
- signature_hash: "0x3131313131313131313131313131313131313131313131313131313131313131"
- .to_string(),
- preverified_signature: true,
- role: RadrootsSp1TradeEventEvidenceRole::Seller,
- workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderDecision,
- content_hash: "0x3232323232323232323232323232323232323232323232323232323232323232"
- .to_string(),
- tags_hash: "0x3333333333333333333333333333333333333333333333333333333333333333"
- .to_string(),
- ordering_key: "003:order_decision".to_string(),
- },
- ]
-}
-
-fn read_input(input: Option<&Path>) -> anyhow::Result<Vec<u8>> {
- match input {
- Some(path) => Ok(std::fs::read(path)?),
- None => {
- use std::io::Read;
- let mut bytes = Vec::new();
- std::io::stdin().read_to_end(&mut bytes)?;
- Ok(bytes)
- }
- }
-}
-
-fn write_output(output: Option<&Path>, bytes: &[u8]) -> anyhow::Result<()> {
- match output {
- Some(path) => {
- std::fs::write(path, bytes)?;
- Ok(())
- }
- None => {
- println!("{}", String::from_utf8_lossy(bytes));
- Ok(())
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::{
- PROTOCOL_VERSION, RhiProofSmokeBackend, RhiProofSmokeOperation, RhiProofSmokeRequest,
- RhiProofSmokeResponse, handle_request_bytes,
- };
-
- fn request(operation: RhiProofSmokeOperation, backend: RhiProofSmokeBackend) -> Vec<u8> {
- serde_json::to_vec(&RhiProofSmokeRequest {
- protocol_version: PROTOCOL_VERSION.to_string(),
- operation,
- backend,
- fixture: Some("order_acceptance_tiny_v1".to_string()),
- })
- .expect("request json")
- }
-
- #[tokio::test]
- async fn health_returns_worker_capabilities() {
- let bytes = serde_json::to_vec(&RhiProofSmokeRequest {
- protocol_version: PROTOCOL_VERSION.to_string(),
- operation: RhiProofSmokeOperation::Health,
- backend: RhiProofSmokeBackend::ValidatorSetDeterministic,
- fixture: None,
- })
- .expect("request json");
- let response: RhiProofSmokeResponse = handle_request_bytes(&bytes).await;
- assert!(response.ok, "{:?}", response.error);
- assert_eq!(response.worker_name, "rhi");
- assert!(response.capabilities.contains(&"health".to_string()));
- assert!(!response.proof_generated);
- }
-
- #[tokio::test]
- async fn deterministic_proof_smoke_returns_public_values() {
- let response = handle_request_bytes(&request(
- RhiProofSmokeOperation::ProofSmoke,
- RhiProofSmokeBackend::ValidatorSetDeterministic,
- ))
- .await;
- assert!(response.ok, "{:?}", response.error);
- assert_eq!(response.operation, RhiProofSmokeOperation::ProofSmoke);
- assert!(response.public_values_hash.is_some());
- assert!(response.sp1_program_hash.is_none());
- assert!(response.sp1_verifying_key_hash.is_none());
- assert!(response.event_set_root.is_some());
- assert!(response.reducer_output_root.is_some());
- for value in [
- response.public_values_hash.as_deref(),
- response.event_set_root.as_deref(),
- response.reducer_output_root.as_deref(),
- ] {
- let value = value.expect("public value");
- assert_eq!(value.len(), 64);
- assert!(!value.starts_with("0x"));
- assert!(value.bytes().all(|byte| byte.is_ascii_hexdigit()));
- }
- assert!(!response.proof_generated);
- }
-
- #[tokio::test]
- async fn proof_smoke_rejects_unknown_fixture() {
- let response = handle_request_bytes(
- &serde_json::to_vec(&RhiProofSmokeRequest {
- protocol_version: PROTOCOL_VERSION.to_string(),
- operation: RhiProofSmokeOperation::ProofSmoke,
- backend: RhiProofSmokeBackend::ValidatorSetDeterministic,
- fixture: Some("other".to_string()),
- })
- .expect("request json"),
- )
- .await;
- assert!(!response.ok);
- assert_eq!(
- response.error.as_deref(),
- Some("proof_smoke requires fixture order_acceptance_tiny_v1")
- );
- }
-
- #[tokio::test]
- async fn proof_smoke_rejects_full_proof_request_fields() {
- let response = handle_request_bytes(
- br#"{"protocol_version":"radroots.rhi.proof_smoke.v0","operation":"proof_smoke","backend":"validator_set_deterministic","fixture":"order_acceptance_tiny_v1","proof_mode":"core"}"#,
- )
- .await;
- assert!(!response.ok);
- assert!(
- response
- .error
- .as_deref()
- .is_some_and(|error| error.contains("unknown field"))
- );
- }
-
- #[cfg(not(feature = "sp1_proving"))]
- #[tokio::test]
- async fn local_execute_reports_unavailable_without_feature() {
- let response = handle_request_bytes(&request(
- RhiProofSmokeOperation::ProofSmoke,
- RhiProofSmokeBackend::LocalExecute,
- ))
- .await;
- assert!(!response.ok);
- assert_eq!(
- response.error.as_deref(),
- Some("local_execute backend is unavailable in this build")
- );
- }
-
- #[cfg(feature = "sp1_proving")]
- #[tokio::test]
- async fn local_execute_returns_sp1_public_values_without_proof_generation() {
- let deterministic = handle_request_bytes(&request(
- RhiProofSmokeOperation::ProofSmoke,
- RhiProofSmokeBackend::ValidatorSetDeterministic,
- ))
- .await;
- let response = handle_request_bytes(&request(
- RhiProofSmokeOperation::ProofSmoke,
- RhiProofSmokeBackend::LocalExecute,
- ))
- .await;
- if !radroots_trade_sp1_host::order_acceptance_sp1_guest_elf_available() {
- assert!(!response.ok);
- assert_eq!(
- response.error.as_deref(),
- Some("local_execute backend is unavailable in this build")
- );
- assert!(!response.capabilities.contains(&"local_execute".to_string()));
- return;
- }
- assert!(response.ok, "{:?}", response.error);
- assert_eq!(response.operation, RhiProofSmokeOperation::ProofSmoke);
- assert_eq!(response.backend, RhiProofSmokeBackend::LocalExecute);
- assert!(response.capabilities.contains(&"local_execute".to_string()));
- assert!(!response.proof_generated);
- assert_ne!(
- response.public_values_hash,
- deterministic.public_values_hash
- );
- assert!(response.sp1_program_hash.is_some());
- assert!(response.sp1_verifying_key_hash.is_some());
- for value in [
- response.sp1_program_hash.as_deref(),
- response.sp1_verifying_key_hash.as_deref(),
- ] {
- let value = value.expect("SP1 identity");
- assert_eq!(value.len(), 64);
- assert!(!value.starts_with("0x"));
- assert!(value.bytes().all(|byte| byte.is_ascii_hexdigit()));
- }
- assert_eq!(response.event_set_root, deterministic.event_set_root);
- assert_eq!(
- response.reducer_output_root,
- deterministic.reducer_output_root
- );
- assert!(response.error.is_none());
- }
-}
diff --git a/src/remote_prove.rs b/src/remote_prove.rs
@@ -1,476 +0,0 @@
-#![forbid(unsafe_code)]
-#![cfg_attr(coverage_nightly, coverage(off))]
-
-use crate::cli::Command;
-use radroots_trade_sp1_guest::{
- RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET, RADROOTS_SP1_TRADE_PROTOCOL_VERSION,
- RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH, RADROOTS_SP1_TRADE_WITNESS_VERSION,
-};
-use radroots_trade_sp1_host::{
- RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION, RADROOTS_SP1_TRADE_SP1_VERSION_LINE,
- RadrootsSp1TradeProofEngine, RadrootsSp1TradeProofMode, RadrootsSp1TradeRemoteProverRequest,
- RadrootsSp1TradeRemoteProverResponse, RadrootsSp1TradeRemoteProverStatus,
-};
-use std::path::Path;
-
-pub async fn run_cli_command(command: Command) -> anyhow::Result<()> {
- let Command::RemoteProve {
- input,
- output,
- proof_engine,
- } = command
- else {
- return Err(anyhow::anyhow!("remote-prove command expected"));
- };
- let engine = RadrootsSp1TradeProofEngine::from_label(proof_engine.as_str())
- .ok_or_else(|| anyhow::anyhow!("invalid proof engine"))?;
- let request_bytes = read_input(input.as_deref())?;
- let response = handle_request_bytes(&request_bytes, engine).await;
- let response_bytes = serde_json::to_vec_pretty(&response)?;
- write_output(output.as_deref(), &response_bytes)?;
- if response.status == RadrootsSp1TradeRemoteProverStatus::Completed {
- Ok(())
- } else {
- Err(anyhow::anyhow!(
- "{}",
- response
- .message
- .as_deref()
- .unwrap_or("remote proof request did not complete")
- ))
- }
-}
-
-pub async fn handle_request_bytes(
- bytes: &[u8],
- engine: RadrootsSp1TradeProofEngine,
-) -> RadrootsSp1TradeRemoteProverResponse {
- let request_id = request_id_from_bytes(bytes);
- let request = match serde_json::from_slice::<RadrootsSp1TradeRemoteProverRequest>(bytes) {
- Ok(request) => request,
- Err(error) => {
- return rejected_response(request_id, "invalid_json", error.to_string());
- }
- };
- match validate_request(&request) {
- Ok(()) => prove_request(request, engine).await,
- Err(rejection) => {
- rejected_response(request.request_id, rejection.reason, rejection.message)
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-struct RemoteProveRejection {
- reason: &'static str,
- message: &'static str,
-}
-
-fn validate_request(
- request: &RadrootsSp1TradeRemoteProverRequest,
-) -> Result<(), RemoteProveRejection> {
- if request.schema_version != RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION {
- return Err(rejection(
- "invalid_schema_version",
- "invalid schema_version",
- ));
- }
- if request.request_id.trim().is_empty() {
- return Err(rejection("invalid_request_id", "invalid request_id"));
- }
- if request.proof_target != RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET {
- return Err(rejection(
- "unsupported_proof_target",
- "unsupported proof_target",
- ));
- }
- if request.proof_mode != RadrootsSp1TradeProofMode::Core {
- return Err(rejection(
- "unsupported_proof_mode",
- "unsupported proof_mode",
- ));
- }
- if request.sp1_version_line != RADROOTS_SP1_TRADE_SP1_VERSION_LINE {
- return Err(rejection(
- "unsupported_sp1_version",
- "unsupported sp1 version",
- ));
- }
- if request.expected_reducer_program_hash != RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH {
- return Err(rejection(
- "reducer_program_hash_mismatch",
- "expected reducer program hash mismatch",
- ));
- }
- if request.expected_protocol_version != RADROOTS_SP1_TRADE_PROTOCOL_VERSION {
- return Err(rejection(
- "protocol_version_mismatch",
- "expected protocol version mismatch",
- ));
- }
- if request.expected_witness_version != RADROOTS_SP1_TRADE_WITNESS_VERSION {
- return Err(rejection(
- "witness_version_mismatch",
- "expected witness version mismatch",
- ));
- }
- if request.witness.witness_version != request.expected_witness_version {
- return Err(rejection(
- "witness_version_mismatch",
- "witness version mismatch",
- ));
- }
- if request.witness.proof_target != request.proof_target {
- return Err(rejection(
- "proof_target_mismatch",
- "witness proof target mismatch",
- ));
- }
- if request.witness.reducer_program_hash != request.expected_reducer_program_hash {
- return Err(rejection(
- "reducer_program_hash_mismatch",
- "witness reducer program hash mismatch",
- ));
- }
- if request.witness.radroots_protocol_version != request.expected_protocol_version {
- return Err(rejection(
- "protocol_version_mismatch",
- "witness protocol version mismatch",
- ));
- }
- if request.witness.sp1_program_hash.as_deref()
- != Some(request.expected_sp1_program_hash.as_str())
- {
- return Err(rejection(
- "sp1_program_hash_mismatch",
- "witness SP1 program hash mismatch",
- ));
- }
- if request.witness.sp1_verifying_key_hash.as_deref()
- != Some(request.expected_sp1_verifying_key_hash.as_str())
- {
- return Err(rejection(
- "sp1_verifying_key_hash_mismatch",
- "witness SP1 verifying key hash mismatch",
- ));
- }
- for value in [
- request.expected_sp1_program_hash.as_str(),
- request.expected_sp1_verifying_key_hash.as_str(),
- request.expected_public_values_hash.as_str(),
- ] {
- if !is_hash32(value) {
- return Err(rejection("invalid_hash", "expected hash field is invalid"));
- }
- }
- let execution = radroots_trade_sp1_host::execute_order_acceptance_public_values(
- &request.witness,
- )
- .map_err(|_| {
- rejection(
- "public_values_execution_failed",
- "public values execution failed",
- )
- })?;
- if execution.public_values_hash != request.expected_public_values_hash {
- return Err(rejection(
- "public_values_hash_mismatch",
- "expected public values hash mismatch",
- ));
- }
- Ok(())
-}
-
-fn rejection(reason: &'static str, message: &'static str) -> RemoteProveRejection {
- RemoteProveRejection { reason, message }
-}
-
-#[cfg(feature = "sp1_proving")]
-async fn prove_request(
- request: RadrootsSp1TradeRemoteProverRequest,
- engine: RadrootsSp1TradeProofEngine,
-) -> RadrootsSp1TradeRemoteProverResponse {
- match radroots_trade_sp1_host::generate_order_acceptance_sp1_proof_with_engine(
- &request.witness,
- request.proof_mode,
- engine,
- )
- .await
- {
- Ok(bundle) => completed_response(request, bundle),
- Err(error) => failed_response(
- request.request_id,
- "proof_generation_failed",
- error.to_string(),
- ),
- }
-}
-
-#[cfg(not(feature = "sp1_proving"))]
-async fn prove_request(
- request: RadrootsSp1TradeRemoteProverRequest,
- _engine: RadrootsSp1TradeProofEngine,
-) -> RadrootsSp1TradeRemoteProverResponse {
- failed_response(
- request.request_id,
- "proof_generation_unavailable",
- "remote-prove requires the sp1_proving feature".to_string(),
- )
-}
-
-#[cfg(feature = "sp1_proving")]
-fn completed_response(
- request: RadrootsSp1TradeRemoteProverRequest,
- bundle: radroots_trade_sp1_host::RadrootsSp1TradeProofBundle,
-) -> RadrootsSp1TradeRemoteProverResponse {
- if bundle.execution.public_values_hash != request.expected_public_values_hash {
- return failed_response(
- request.request_id,
- "public_values_hash_mismatch",
- "generated public values hash mismatch".to_string(),
- );
- }
- if bundle.proof.program_hash.as_deref() != Some(request.expected_sp1_program_hash.as_str()) {
- return failed_response(
- request.request_id,
- "sp1_program_hash_mismatch",
- "generated SP1 program hash mismatch".to_string(),
- );
- }
- if bundle.proof.verifying_key_hash.as_deref()
- != Some(request.expected_sp1_verifying_key_hash.as_str())
- {
- return failed_response(
- request.request_id,
- "sp1_verifying_key_hash_mismatch",
- "generated SP1 verifying key hash mismatch".to_string(),
- );
- }
- RadrootsSp1TradeRemoteProverResponse {
- schema_version: RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
- request_id: request.request_id,
- status: RadrootsSp1TradeRemoteProverStatus::Completed,
- status_url: None,
- status_path: None,
- proof_system: Some(request.proof_mode.proof_system()),
- proof_mode: Some(request.proof_mode),
- public_values_hash: Some(bundle.execution.public_values_hash),
- sp1_program_hash: bundle.proof.program_hash.clone(),
- sp1_verifying_key_hash: bundle.proof.verifying_key_hash.clone(),
- proof_artifact: Some(bundle.proof),
- resolved_proof_envelope_base64: None,
- reason_code: None,
- message: None,
- detail: None,
- }
-}
-
-fn rejected_response(
- request_id: String,
- reason: impl Into<String>,
- message: impl Into<String>,
-) -> RadrootsSp1TradeRemoteProverResponse {
- terminal_response(
- request_id,
- RadrootsSp1TradeRemoteProverStatus::Rejected,
- reason,
- message,
- )
-}
-
-fn failed_response(
- request_id: String,
- reason: impl Into<String>,
- message: impl Into<String>,
-) -> RadrootsSp1TradeRemoteProverResponse {
- terminal_response(
- request_id,
- RadrootsSp1TradeRemoteProverStatus::Failed,
- reason,
- message,
- )
-}
-
-fn terminal_response(
- request_id: String,
- status: RadrootsSp1TradeRemoteProverStatus,
- reason: impl Into<String>,
- message: impl Into<String>,
-) -> RadrootsSp1TradeRemoteProverResponse {
- RadrootsSp1TradeRemoteProverResponse {
- schema_version: RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
- request_id,
- status,
- status_url: None,
- status_path: None,
- proof_system: None,
- proof_mode: None,
- public_values_hash: None,
- sp1_program_hash: None,
- sp1_verifying_key_hash: None,
- proof_artifact: None,
- resolved_proof_envelope_base64: None,
- reason_code: Some(reason.into()),
- message: Some(message.into()),
- detail: None,
- }
-}
-
-fn request_id_from_bytes(bytes: &[u8]) -> String {
- serde_json::from_slice::<serde_json::Value>(bytes)
- .ok()
- .and_then(|value| {
- let request_id = value
- .get("request_id")
- .and_then(serde_json::Value::as_str)
- .map(str::trim)?;
- if request_id.is_empty() {
- None
- } else {
- Some(request_id.to_owned())
- }
- })
- .unwrap_or_else(|| "invalid-request".to_string())
-}
-
-fn is_hash32(value: &str) -> bool {
- value.len() == 66
- && value.starts_with("0x")
- && value[2..]
- .bytes()
- .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
-}
-
-fn read_input(input: Option<&Path>) -> anyhow::Result<Vec<u8>> {
- match input {
- Some(path) => Ok(std::fs::read(path)?),
- None => {
- use std::io::Read;
- let mut bytes = Vec::new();
- std::io::stdin().read_to_end(&mut bytes)?;
- Ok(bytes)
- }
- }
-}
-
-fn write_output(output: Option<&Path>, bytes: &[u8]) -> anyhow::Result<()> {
- match output {
- Some(path) => {
- std::fs::write(path, bytes)?;
- Ok(())
- }
- None => {
- println!("{}", String::from_utf8_lossy(bytes));
- Ok(())
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::handle_request_bytes;
- use radroots_trade_sp1_guest::{
- RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET, RADROOTS_SP1_TRADE_PROTOCOL_VERSION,
- RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH, RADROOTS_SP1_TRADE_WITNESS_VERSION,
- };
- use radroots_trade_sp1_host::{
- RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION, RADROOTS_SP1_TRADE_SP1_VERSION_LINE,
- RadrootsSp1TradeProofEngine, RadrootsSp1TradeProofMode,
- RadrootsSp1TradeRemoteProverRequest, RadrootsSp1TradeRemoteProverStatus,
- };
-
- fn hash32(ch: char) -> String {
- format!("0x{}", ch.to_string().repeat(64))
- }
-
- fn request() -> RadrootsSp1TradeRemoteProverRequest {
- let mut witness = crate::proof_smoke::order_acceptance_tiny_witness();
- witness.sp1_program_hash = Some(hash32('a'));
- witness.sp1_verifying_key_hash = Some(hash32('b'));
- let execution = radroots_trade_sp1_host::execute_order_acceptance_public_values(&witness)
- .expect("public values");
- RadrootsSp1TradeRemoteProverRequest {
- schema_version: RADROOTS_SP1_TRADE_REMOTE_PROVER_SCHEMA_VERSION,
- request_id: "request-1".to_string(),
- proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(),
- proof_mode: RadrootsSp1TradeProofMode::Core,
- sp1_version_line: RADROOTS_SP1_TRADE_SP1_VERSION_LINE.to_string(),
- witness,
- expected_sp1_program_hash: hash32('a'),
- expected_sp1_verifying_key_hash: hash32('b'),
- expected_public_values_hash: execution.public_values_hash,
- expected_reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(),
- expected_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(),
- expected_witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION,
- }
- }
-
- #[tokio::test]
- async fn remote_prove_rejects_unknown_provider_fields() {
- let response = handle_request_bytes(
- br#"{"schema_version":1,"request_id":"request-1","proof_target":"order_acceptance_v1","proof_mode":"core","sp1_version_line":"sp1-sdk-6.2.1","expected_sp1_program_hash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expected_sp1_verifying_key_hash":"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","expected_public_values_hash":"0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","expected_reducer_program_hash":"0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","expected_protocol_version":"radroots.sp1.trade.v1","expected_witness_version":1,"provider":"runpod"}"#,
- RadrootsSp1TradeProofEngine::Cpu,
- )
- .await;
- assert_eq!(response.request_id, "request-1");
- assert_eq!(
- response.status,
- RadrootsSp1TradeRemoteProverStatus::Rejected
- );
- assert_eq!(response.reason_code.as_deref(), Some("invalid_json"));
- }
-
- #[tokio::test]
- async fn remote_prove_rejects_expected_public_values_hash_mismatch() {
- let mut request = request();
- request.expected_public_values_hash = hash32('c');
- let response = handle_request_bytes(
- &serde_json::to_vec(&request).expect("request json"),
- RadrootsSp1TradeProofEngine::Cpu,
- )
- .await;
- assert_eq!(
- response.status,
- RadrootsSp1TradeRemoteProverStatus::Rejected
- );
- assert_eq!(
- response.reason_code.as_deref(),
- Some("public_values_hash_mismatch")
- );
- }
-
- #[tokio::test]
- async fn remote_prove_rejects_unsupported_modes_before_generation() {
- let mut request = request();
- request.proof_mode = RadrootsSp1TradeProofMode::Compressed;
- let response = handle_request_bytes(
- &serde_json::to_vec(&request).expect("request json"),
- RadrootsSp1TradeProofEngine::Cpu,
- )
- .await;
- assert_eq!(
- response.status,
- RadrootsSp1TradeRemoteProverStatus::Rejected
- );
- assert_eq!(
- response.reason_code.as_deref(),
- Some("unsupported_proof_mode")
- );
- }
-
- #[cfg(not(feature = "sp1_proving"))]
- #[tokio::test]
- async fn remote_prove_reports_generation_unavailable_without_proving_feature() {
- let request = request();
- let response = handle_request_bytes(
- &serde_json::to_vec(&request).expect("request json"),
- RadrootsSp1TradeProofEngine::Cpu,
- )
- .await;
- assert_eq!(response.status, RadrootsSp1TradeRemoteProverStatus::Failed);
- assert_eq!(
- response.reason_code.as_deref(),
- Some("proof_generation_unavailable")
- );
- }
-}
diff --git a/src/rhi.rs b/src/rhi.rs
@@ -1,12 +1,15 @@
#![cfg_attr(coverage_nightly, coverage(off))]
+use std::sync::Arc;
use std::time::{Duration, Instant};
use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrKeys};
use radroots_runtime::{Backoff, BackoffConfig};
+use tokio::sync::Mutex;
-use crate::features::trade_listing::state::TradeListingRuntime;
-use crate::features::trade_validation_receipt::TradeValidationReceiptProverPolicy;
+use crate::features::trade_agreement_attestation::{
+ TradeAgreementAttestationPolicy, TradeAgreementAttestationRuntime,
+};
#[cfg(not(test))]
fn connection_wait_timeout() -> Duration {
@@ -32,8 +35,8 @@ fn subscriber_result_hook()
async fn run_subscriber_once(
client: RadrootsNostrClient,
keys: RadrootsNostrKeys,
- runtime: TradeListingRuntime,
- proof_policy: TradeValidationReceiptProverPolicy,
+ runtime: TradeAgreementAttestationRuntime,
+ policy: TradeAgreementAttestationPolicy,
stop_rx: tokio::sync::watch::Receiver<bool>,
) -> Result<(), anyhow::Error> {
#[cfg(test)]
@@ -45,14 +48,8 @@ async fn run_subscriber_once(
return result;
}
- crate::features::trade_listing::subscriber::subscriber(
- client,
- keys,
- runtime,
- proof_policy,
- stop_rx,
- )
- .await
+ crate::features::trade_agreement_attestation::subscriber(client, keys, runtime, policy, stop_rx)
+ .await
}
async fn wait_for_connection_or_stop(
@@ -71,44 +68,41 @@ async fn wait_for_connection_or_stop(
pub struct Rhi {
pub(crate) _started: Instant,
pub client: RadrootsNostrClient,
- pub(crate) trade_listing_runtime: TradeListingRuntime,
- pub(crate) trade_validation_receipt_policy: TradeValidationReceiptProverPolicy,
+ pub(crate) agreement_attestation_runtime: TradeAgreementAttestationRuntime,
+ pub(crate) agreement_attestation_policy: TradeAgreementAttestationPolicy,
}
impl Rhi {
pub fn new(keys: RadrootsNostrKeys) -> Self {
- Self::with_trade_listing_runtime(keys, TradeListingRuntime::new())
+ Self::with_agreement_attestation_runtime(keys, TradeAgreementAttestationRuntime::new())
}
- pub fn with_trade_listing_runtime(
+ pub fn with_agreement_attestation_runtime(
keys: RadrootsNostrKeys,
- trade_listing_runtime: TradeListingRuntime,
+ agreement_attestation_runtime: TradeAgreementAttestationRuntime,
) -> Self {
- Self::with_trade_listing_runtime_and_policy(
+ Self::with_agreement_attestation_runtime_and_policy(
keys,
- trade_listing_runtime,
- TradeValidationReceiptProverPolicy::default(),
+ agreement_attestation_runtime,
+ TradeAgreementAttestationPolicy::default(),
)
}
- pub fn with_trade_listing_runtime_and_policy(
+ pub fn with_agreement_attestation_runtime_and_policy(
keys: RadrootsNostrKeys,
- trade_listing_runtime: TradeListingRuntime,
- trade_validation_receipt_policy: TradeValidationReceiptProverPolicy,
+ agreement_attestation_runtime: TradeAgreementAttestationRuntime,
+ agreement_attestation_policy: TradeAgreementAttestationPolicy,
) -> Self {
let client = RadrootsNostrClient::new(keys);
Self {
_started: Instant::now(),
client,
- trade_listing_runtime,
- trade_validation_receipt_policy,
+ agreement_attestation_runtime,
+ agreement_attestation_policy,
}
}
}
-use std::sync::Arc;
-use tokio::sync::Mutex;
-
pub struct RhiHandle {
stop_tx: Arc<Mutex<Option<tokio::sync::watch::Sender<bool>>>>,
join: Option<tokio::task::JoinHandle<()>>,
@@ -118,7 +112,7 @@ impl Clone for RhiHandle {
fn clone(&self) -> Self {
Self {
stop_tx: Arc::clone(&self.stop_tx),
- join: None, // don’t clone the JoinHandle!
+ join: None,
}
}
}
@@ -140,14 +134,14 @@ impl RhiHandle {
pub async fn start_subscriber(
client: RadrootsNostrClient,
keys: RadrootsNostrKeys,
- runtime: TradeListingRuntime,
+ runtime: TradeAgreementAttestationRuntime,
backoff_cfg: BackoffConfig,
) -> RhiHandle {
start_subscriber_with_policy(
client,
keys,
runtime,
- TradeValidationReceiptProverPolicy::default(),
+ TradeAgreementAttestationPolicy::default(),
backoff_cfg,
)
.await
@@ -156,8 +150,8 @@ pub async fn start_subscriber(
pub async fn start_subscriber_with_policy(
client: RadrootsNostrClient,
keys: RadrootsNostrKeys,
- runtime: TradeListingRuntime,
- proof_policy: TradeValidationReceiptProverPolicy,
+ runtime: TradeAgreementAttestationRuntime,
+ policy: TradeAgreementAttestationPolicy,
backoff_cfg: BackoffConfig,
) -> RhiHandle {
let (stop_tx, mut stop_rx) = tokio::sync::watch::channel(false);
@@ -178,7 +172,7 @@ pub async fn start_subscriber_with_policy(
client.clone(),
keys.clone(),
runtime.clone(),
- proof_policy.clone(),
+ policy.clone(),
stop_rx.clone(),
)
.await;
@@ -186,7 +180,7 @@ pub async fn start_subscriber_with_policy(
let failed = res.is_err();
if let Err(e) = res {
- tracing::error!("Error on job request subscription: {e}");
+ tracing::error!("Error on agreement attestation subscription: {e}");
} else {
backoff.reset();
}
@@ -217,7 +211,7 @@ mod tests {
use super::{
Rhi, RhiHandle, start_subscriber, subscriber_result_hook, wait_for_connection_or_stop,
};
- use crate::features::trade_listing::state::TradeListingRuntime;
+ use crate::features::trade_agreement_attestation::TradeAgreementAttestationRuntime;
use anyhow::anyhow;
use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrKeys};
use radroots_runtime::BackoffConfig;
@@ -229,18 +223,7 @@ mod tests {
let keys = RadrootsNostrKeys::generate();
let rhi = Rhi::new(keys);
let _ = rhi.client.clone();
- let state = rhi.trade_listing_runtime.state();
- state
- .lock()
- .await
- .mark_listing_validated("addr", "evt-listing-1");
- assert!(
- rhi.trade_listing_runtime
- .state()
- .lock()
- .await
- .is_listing_validated("addr")
- );
+ assert!(rhi.agreement_attestation_runtime.reports().await.is_empty());
}
#[tokio::test]
@@ -271,7 +254,7 @@ mod tests {
let handle_err = start_subscriber(
client_err,
keys.clone(),
- TradeListingRuntime::new(),
+ TradeAgreementAttestationRuntime::new(),
cfg.clone(),
)
.await;
@@ -285,7 +268,13 @@ mod tests {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push_back(Ok(()));
- let handle_ok = start_subscriber(client_ok, keys, TradeListingRuntime::new(), cfg).await;
+ let handle_ok = start_subscriber(
+ client_ok,
+ keys,
+ TradeAgreementAttestationRuntime::new(),
+ cfg,
+ )
+ .await;
tokio::time::sleep(std::time::Duration::from_millis(30)).await;
handle_ok.stop();
handle_ok.stopped().await;
@@ -298,7 +287,7 @@ mod tests {
let handle = start_subscriber(
client,
keys,
- TradeListingRuntime::new(),
+ TradeAgreementAttestationRuntime::new(),
BackoffConfig {
base_ms: 25,
max_ms: 50,
@@ -324,7 +313,7 @@ mod tests {
let handle = start_subscriber(
client,
keys,
- TradeListingRuntime::new(),
+ TradeAgreementAttestationRuntime::new(),
BackoffConfig {
base_ms: 200,
max_ms: 200,
diff --git a/tests/source_guards.rs b/tests/source_guards.rs
@@ -2,172 +2,132 @@ use std::fs;
use std::path::Path;
#[test]
-fn rhi_manifest_has_no_sdk_dependency() {
+fn rhi_manifest_has_no_sdk_or_legacy_proof_dependency() {
let manifest = read_repo_file("Cargo.toml");
- assert!(
- !manifest.contains("radroots_sdk"),
- "RHI must not depend on radroots_sdk"
- );
-}
-
-#[test]
-fn rhi_validation_receipt_paths_use_validator_set_contract() {
- let listing_events = read_repo_file("src/features/trade_listing/handlers/events.rs");
- let receipt_worker = read_repo_file("src/features/trade_validation_receipt.rs");
-
- assert!(
- listing_events.contains("publish_validation_receipt("),
- "trade listing event handler must publish V1 validation receipts from accepted orders"
- );
- assert!(
- !listing_events.contains("radroots_trade::dvm"),
- "trade listing handler must not import the removed DVM contract"
- );
-
- for required in [
- "validator_set_addr",
- "validator_set_event_id",
- "validator_set_address_from_str",
- "validation_receipt_event_build",
- "verify_validation_receipt_event",
- "MissingValidatorSetBinding",
- "mark_receipt_completed",
+ for forbidden in [
+ "radroots_sdk",
+ "radroots_trade_sp1_guest",
+ "radroots_trade_sp1_host",
+ "sp1_verify",
+ "sp1_proving",
+ "sp1_cuda_proving",
+ "reqwest",
+ "sqlx",
+ "libsqlite3-sys",
] {
assert!(
- receipt_worker.contains(required),
- "trade validation receipt worker must retain validator-set receipt contract `{required}`"
+ !manifest.contains(forbidden),
+ "RHI manifest must not retain retired dependency `{forbidden}`"
);
}
+}
- for forbidden in [
- "radroots_trade::dvm",
- "KIND_TRADE_TRANSITION_PROOF_REQUEST",
- "KIND_TRADE_TRANSITION_PROOF_RESULT",
- "RadrootsTradeTransitionProofRequest",
- "build_transition_proof_result_tags",
- "deterministic_none",
- "DeterministicNone",
+#[test]
+fn rhi_release_product_surface_has_no_order_or_receipt_modules() {
+ for forbidden_path in [
+ "src/features/trade_listing/mod.rs",
+ "src/features/trade_validation_receipt.rs",
+ "src/proof_smoke.rs",
+ "src/remote_prove.rs",
] {
assert!(
- !receipt_worker.contains(forbidden),
- "trade validation receipt worker must not retain retired contract `{forbidden}`"
+ !Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join(forbidden_path)
+ .exists(),
+ "RHI must not retain retired source path `{forbidden_path}`"
);
}
-}
-#[test]
-fn rhi_sources_do_not_import_removed_sdk_or_protocol_bypasses() {
for (path, source) in rust_sources_under("src") {
for forbidden in [
- "radroots_sdk",
- "radroots_sdk::protocol::order",
- "SdkDvmInventoryBinWitness",
- "TradeProtocolClient",
- "KIND_TRADE_LISTING_VALIDATE_REQ",
- "KIND_TRADE_LISTING_VALIDATION_REQUEST",
- "KIND_WORKER_TRADE_TRANSITION_PROOF_REQ",
- "KIND_TRADE_TRANSITION_PROOF_REQUEST",
- "KIND_TRADE_TRANSITION_PROOF_RESULT",
- "radroots_trade::dvm",
- "deterministic_none",
- "DeterministicNone",
+ "trade_listing",
+ "trade_validation_receipt",
+ "proof_smoke",
+ "remote_prove",
+ "KIND_ORDER",
+ "RadrootsOrder",
+ "radroots_trade::order",
+ "radroots_event_codec::order",
+ "AgreedPendingValidation",
+ "ValidationExpired",
+ "order_acceptance",
+ "KIND_TRADE_VALIDATION_RECEIPT",
+ "validation_receipt_event_build",
+ "verify_validation_receipt_event",
+ "radroots_trade_sp1",
+ "proof_mode",
+ "LocalExecute",
+ "local_execute",
] {
assert!(
!source.contains(forbidden),
- "{path} contains forbidden SDK adoption bypass `{forbidden}`"
+ "{path} retains retired order or proof surface `{forbidden}`"
);
}
}
}
#[test]
-fn rhi_processed_job_state_is_durable_workflow_authority() {
- let state = read_repo_file("src/features/trade_listing/state.rs");
- let processed_jobs = read_repo_file("src/features/trade_listing/processed_jobs.rs");
- let receipt_worker = read_repo_file("src/features/trade_validation_receipt.rs");
-
- assert!(
- !state.contains("rhi_processed_jobs: HashMap"),
- "RHI JSON subscriber state must not be the processed-job authority"
- );
- assert!(
- state.contains("processed_jobs: Arc<RhiProcessedJobStore>"),
- "TradeListingRuntime must own the processed-job SQLite store"
- );
+fn rhi_agreement_attestation_is_release_product_optional_infrastructure() {
+ let worker = read_repo_file("src/features/trade_agreement_attestation.rs");
+ let lib = read_repo_file("src/lib.rs");
+ let cli = read_repo_file("src/cli.rs");
+ let config = read_repo_file("src/config.rs");
for required in [
- "CREATE TABLE IF NOT EXISTS rhi_processed_jobs",
- "request_id TEXT PRIMARY KEY",
- "CREATE UNIQUE INDEX IF NOT EXISTS rhi_processed_jobs_receipt_event_idx",
- "CREATE UNIQUE INDEX IF NOT EXISTS rhi_processed_jobs_result_event_idx",
- "pub async fn claim_job(",
- "pub async fn mark_receipt_publishing(",
- "pub async fn mark_receipt_published(",
- "pub async fn mark_result_publishing(",
- "pub async fn mark_completed(",
- "pub async fn mark_receipt_completed(",
- "RhiProcessedJobClaim::InProgress",
- "RhiProcessedJobClaim::RecoverReceipt",
- "RhiProcessedJobStatus::ReceiptPublishing",
- "RhiProcessedJobStatus::ResultPublishing",
- "RhiProcessedJobStatus::Completed",
- "receipt_event_json",
- "result_event_json",
- "proof_metadata_json",
- "DuplicateConflictingResult",
+ "RHI_AGREEMENT_ATTESTATION_PROTOCOL_ID",
+ "TradeAgreementAttestationPolicy",
+ "LocalStatementHash",
+ "TradeAgreementAttestationRuntime",
+ "handle_trade_mutation_event",
+ "attest_projection_claim",
+ "claim_mutation_id",
+ "projection_digest",
+ "RadrootsTradeAttestationResultV1::Valid",
+ "RadrootsTradeAttestationResultV1::Invalid",
+ "TRADE_MUTATION_EVENT_KINDS",
+ "is_trade_mutation_event_kind",
+ "no_agreement_authority",
+ "expected_statement_contract_hash",
] {
assert!(
- processed_jobs.contains(required),
- "RHI processed-job store must retain SQLite workflow authority `{required}`"
+ worker.contains(required),
+ "agreement attestation worker must retain release-product requirement `{required}`"
);
}
- for required in [
- "fn processed_job_for_receipt(",
- "async fn publish_receipt_with_processed_job(",
- "publish_signed_event_io(",
- "mark_receipt_completed(",
- "RhiProcessedJobClaim::Completed",
- ] {
- assert!(
- receipt_worker.contains(required),
- "RHI receipt worker must retain processed-job workflow guard `{required}`"
- );
- }
+ assert!(
+ lib.contains("trade_mutation_subscription_kinds()")
+ && lib.contains("&TRADE_MUTATION_EVENT_KINDS"),
+ "RHI service presence must advertise canonical release-product trade mutation kinds"
+ );
+ assert!(
+ cli.contains("attestation-smoke")
+ && !cli.contains("proof-smoke")
+ && !cli.contains("remote-prove"),
+ "RHI CLI must expose only release-product agreement attestation smoke command"
+ );
+ assert!(
+ config.contains("settings.config.trade_agreement_attestation.validate()?"),
+ "RHI config loading must validate the canonical attestation policy"
+ );
}
#[test]
-fn rhi_validation_receipt_policy_is_enabled_and_validator_set_bound() {
+fn rhi_state_paths_are_named_for_agreement_attestation() {
+ let paths = read_repo_file("src/paths.rs");
let config = read_repo_file("src/config.rs");
- let receipt_worker = read_repo_file("src/features/trade_validation_receipt.rs");
+ let main = read_repo_file("src/main.rs");
- for required in [
- "TradeValidationReceiptProverBackend::LocalExecute",
- "validator_binding(",
- "MissingValidatorSetBinding",
- "validator_set_addr",
- "validator_set_event_id",
- "ProverBackendRequiresNone",
- ] {
+ for source in [paths.as_str(), config.as_str(), main.as_str()] {
assert!(
- receipt_worker.contains(required),
- "trade validation receipt policy must retain enabled validator-set governance `{required}`"
+ source.contains("trade-agreement-attestation"),
+ "RHI runtime state paths must use agreement-attestation naming"
);
- }
-
- assert!(
- !receipt_worker.contains("Disabled") && !receipt_worker.contains("DeterministicNone"),
- "RHI must not expose disabled or deterministic-none validation receipt backends"
- );
-
- for required in [
- "settings.config.trade_validation_receipt.validate()?",
- "TradeValidationReceiptProverBackend::LocalExecute",
- ] {
assert!(
- config.contains(required),
- "RHI config loading must retain enabled validator-set policy validation `{required}`"
+ !source.contains("trade-listing"),
+ "RHI runtime state paths must not retain trade-listing naming"
);
}
}