diff --git a/.gitignore b/.gitignore index d7bffe131..a9cf22ed3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,12 @@ build/ .failed-tests **/proptest-regressions/** /result +bench-results/ + +# Python / Jupyter +__pycache__/ +*.pyc +.ipynb_checkpoints/ + +# Notebook logs (captured from k8s) +live-tests/sync-bench/k8s/logs/ diff --git a/Cargo.lock b/Cargo.lock index 6df8aaeed..7abd25204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2904,6 +2904,83 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "opentelemetry" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest 0.13.1", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.13.1", + "thiserror 2.0.18", + "tokio", + "tonic", + "tonic-types", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "portable-atomic", + "rand 0.9.4", + "thiserror 2.0.18", + "tokio", + "tokio-stream", +] + [[package]] name = "optfield" version = "0.4.0" @@ -3858,7 +3935,9 @@ dependencies = [ "bytes", "cookie", "cookie_store", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "http-body-util", @@ -3873,6 +3952,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-rustls", @@ -4622,6 +4703,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync-bench" +version = "0.1.0" +dependencies = [ + "futures", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", + "strum", + "tokio", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "zaino-backend-lmdb", + "zaino-indexes", + "zaino-persistence", + "zaino-primitives", + "zaino-rpc", + "zaino-source", + "zaino-source-zebra-readstate", + "zaino-source-zebra-rpc", + "zaino-sync", + "zebra-chain 10.1.0", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4965,6 +5071,17 @@ dependencies = [ "tonic-prost", ] +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.4.13" @@ -5021,7 +5138,7 @@ dependencies = [ [[package]] name = "tower-batch-control" version = "1.0.1" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "futures", "futures-core", @@ -5049,7 +5166,7 @@ dependencies = [ [[package]] name = "tower-fallback" version = "0.2.41" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "futures-core", "pin-project", @@ -5151,6 +5268,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-serde" version = "0.2.0" @@ -5960,6 +6093,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zaino-backend-lmdb" +version = "0.1.0" +dependencies = [ + "lmdb", + "tempfile", + "zaino-persistence", +] + [[package]] name = "zaino-common" version = "0.2.0" @@ -5976,6 +6118,22 @@ dependencies = [ "zingo_common_components", ] +[[package]] +name = "zaino-convert-zebra" +version = "0.1.0" +dependencies = [ + "thiserror 2.0.18", + "zaino-primitives", + "zebra-chain 10.1.0", +] + +[[package]] +name = "zaino-core" +version = "0.1.0" +dependencies = [ + "zaino-primitives", +] + [[package]] name = "zaino-fetch" version = "0.2.1" @@ -6005,6 +6163,45 @@ dependencies = [ "zebra-rpc 10.0.1", ] +[[package]] +name = "zaino-fs" +version = "0.1.0" +dependencies = [ + "futures", + "zaino-core", +] + +[[package]] +name = "zaino-indexes" +version = "0.1.0" +dependencies = [ + "zaino-primitives", + "zaino-sync", +] + +[[package]] +name = "zaino-nfs" +version = "0.1.0" +dependencies = [ + "futures", + "zaino-core", +] + +[[package]] +name = "zaino-persistence" +version = "0.1.0" +dependencies = [ + "thiserror 2.0.18", + "zaino-primitives", +] + +[[package]] +name = "zaino-primitives" +version = "0.1.0" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "zaino-proto" version = "0.1.3" @@ -6018,6 +6215,30 @@ dependencies = [ "zebra-state 9.0.1", ] +[[package]] +name = "zaino-rpc" +version = "0.1.0" +dependencies = [ + "reqwest 0.13.1", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "zaino-source", +] + +[[package]] +name = "zaino-runtime" +version = "0.1.0" +dependencies = [ + "futures", + "tokio", + "zaino-core", + "zaino-fs", + "zaino-nfs", + "zaino-service", +] + [[package]] name = "zaino-serve" version = "0.3.1" @@ -6038,6 +6259,55 @@ dependencies = [ "zebra-rpc 10.0.1", ] +[[package]] +name = "zaino-service" +version = "0.1.0" +dependencies = [ + "futures", + "tokio", + "zaino-core", +] + +[[package]] +name = "zaino-source" +version = "0.1.0" +dependencies = [ + "thiserror 2.0.18", + "tokio", + "zaino-primitives", +] + +[[package]] +name = "zaino-source-zebra-readstate" +version = "0.1.0" +dependencies = [ + "tokio", + "tower 0.4.13", + "tracing", + "zaino-convert-zebra", + "zaino-primitives", + "zaino-source", + "zebra-chain 10.1.0", + "zebra-state 9.0.1", +] + +[[package]] +name = "zaino-source-zebra-rpc" +version = "0.1.0" +dependencies = [ + "hex", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "zaino-convert-zebra", + "zaino-primitives", + "zaino-rpc", + "zaino-source", + "zebra-chain 10.1.0", +] + [[package]] name = "zaino-state" version = "0.3.1" @@ -6092,6 +6362,21 @@ dependencies = [ "zebra-state 9.0.1", ] +[[package]] +name = "zaino-sync" +version = "0.1.0" +dependencies = [ + "rayon", + "strum", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", + "zaino-persistence", + "zaino-primitives", + "zaino-source", +] + [[package]] name = "zaino-testutils" version = "0.2.0" @@ -6115,6 +6400,14 @@ dependencies = [ "zebra-state 9.0.1", ] +[[package]] +name = "zaino-wallet" +version = "0.1.0" +dependencies = [ + "futures", + "zaino-service", +] + [[package]] name = "zainod" version = "0.4.2" @@ -6141,6 +6434,15 @@ dependencies = [ "zebra-state 9.0.1", ] +[[package]] +name = "zallet-fit" +version = "0.1.0" +dependencies = [ + "futures", + "zaino-service", + "zaino-wallet", +] + [[package]] name = "zcash_address" version = "0.12.0" @@ -6425,7 +6727,7 @@ dependencies = [ [[package]] name = "zebra-chain" version = "10.1.0" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "bech32", "bitflags 2.13.0", @@ -6536,7 +6838,7 @@ dependencies = [ [[package]] name = "zebra-consensus" version = "9.0.1" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "bellman", "blake2b_simd", @@ -6560,8 +6862,8 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tower 0.4.13", - "tower-batch-control 1.0.1 (git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800)", - "tower-fallback 0.2.41 (git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800)", + "tower-batch-control 1.0.1 (git+https://github.com/nachog00/zebra.git?rev=d3f426b35)", + "tower-fallback 0.2.41 (git+https://github.com/nachog00/zebra.git?rev=d3f426b35)", "tracing", "tracing-futures", "zcash_primitives", @@ -6616,7 +6918,7 @@ dependencies = [ [[package]] name = "zebra-network" version = "9.0.0" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "bitflags 2.13.0", "byteorder", @@ -6669,7 +6971,7 @@ dependencies = [ [[package]] name = "zebra-node-services" version = "8.0.0" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "color-eyre", "jsonrpsee-types", @@ -6744,7 +7046,7 @@ dependencies = [ [[package]] name = "zebra-rpc" version = "10.0.1" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "base64", "chrono", @@ -6817,7 +7119,7 @@ dependencies = [ [[package]] name = "zebra-script" version = "9.0.0" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "libzcash_script", "rand 0.8.6", @@ -6868,7 +7170,7 @@ dependencies = [ [[package]] name = "zebra-state" version = "9.0.1" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "bincode", "chrono", @@ -6905,7 +7207,7 @@ dependencies = [ [[package]] name = "zebra-test" version = "3.0.0" -source = "git+https://github.com/ZcashFoundation/zebra.git?rev=9a27f886a5bfb143f65d1712e912cef252426800#9a27f886a5bfb143f65d1712e912cef252426800" +source = "git+https://github.com/nachog00/zebra.git?rev=d3f426b35#d3f426b3566da64cc7bdd5f1c2e5d64ac6838fff" dependencies = [ "color-eyre", "futures", diff --git a/Cargo.toml b/Cargo.toml index 8f50df514..7749a3c12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,53 @@ # Workspace Members are in dependency order, do not change this without prior consideration. [workspace] members = [ + "packages/zaino-primitives", + "packages/zaino-source", + "packages/zaino-persistence", + "packages/zaino-backend-lmdb", + "packages/zaino-rpc", + "packages/zaino-source-zebra-rpc", "packages/zaino-common", "packages/zainod", "packages/zaino-serve", "packages/zaino-state", "packages/zaino-fetch", "packages/zaino-proto", + "packages/zaino-sync", + "packages/zaino-indexes", + "packages/zaino-convert-zebra", + "packages/zaino-source-zebra-readstate", + # Driving-port scaffold: pure vocabulary, inner service trait algebra, + # published wallet client. Not yet in default-members (unwired). + "packages/zaino-core", + "packages/zaino-service", + "packages/zaino-fs", + "packages/zaino-nfs", + "packages/zaino-runtime", + "packages/zaino-wallet", # Live-test suite (docs/adr/0002, docs/adr/0003, docs/adr/0004). # zaino-testutils first: the e2e/clientless partitions depend on it. "live-tests/zaino-testutils", "live-tests/clientless", "live-tests/e2e", + "live-tests/sync-bench", + "live-tests/zallet-fit", ] # `container-test` runs a bare `cargo nextest run`, which operates on # default-members — so the heavy live-test crates are built and tested only # when selected explicitly (`-p e2e` / `-p clientless`). See docs/adr/0002. default-members = [ + "packages/zaino-primitives", + "packages/zaino-source", + "packages/zaino-rpc", + "packages/zaino-source-zebra-rpc", "packages/zaino-common", "packages/zainod", "packages/zaino-serve", "packages/zaino-state", "packages/zaino-fetch", "packages/zaino-proto", + "packages/zaino-sync", ] # Use the edition 2021 dependency resolver in the workspace, to match the crates @@ -73,6 +98,10 @@ tracing-subscriber = { version = "0.3.20", features = [ "json", ] } tracing-futures = "0.2" +tracing-opentelemetry = "0.33" +opentelemetry = "0.32" +opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"] } +opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"] } tracing-tree = "0.4" nu-ansi-term = "0.50" time = { version = "0.3", features = ["macros", "formatting"] } @@ -82,6 +111,7 @@ http = "1.1" url = "2.5" reqwest = { version = "0.13", default-features = false, features = [ "cookies", + "json", "rustls", "webpki-roots", ] } @@ -103,6 +133,9 @@ dashmap = "6.1" lmdb = "0.8" lmdb-sys = "0.8" +# Parallelism +rayon = "1.10" + # Async async-stream = "0.3" futures = "0.3.30" @@ -111,6 +144,9 @@ futures = "0.3.30" metrics = "0.24" metrics-exporter-prometheus = { version = "0.16", default-features = false, features = ["http-listener"] } +# Derive +strum = { version = "0.27", features = ["derive"] } + # Utility thiserror = "2.0" lazy-regex = "3.3" @@ -149,6 +185,18 @@ zaino-state = { path = "packages/zaino-state", version = "0.3.1", default-featur zaino-serve = { path = "packages/zaino-serve", version = "0.3.1", default-features = false } zaino-testutils = { path = "live-tests/zaino-testutils" } +# New sync-engine crates (no published versions yet). +zaino-primitives = { path = "packages/zaino-primitives" } +zaino-source = { path = "packages/zaino-source" } +zaino-persistence = { path = "packages/zaino-persistence" } +zaino-backend-lmdb = { path = "packages/zaino-backend-lmdb" } +zaino-rpc = { path = "packages/zaino-rpc" } +zaino-source-zebra-rpc = { path = "packages/zaino-source-zebra-rpc" } +zaino-sync = { path = "packages/zaino-sync" } +zaino-indexes = { path = "packages/zaino-indexes" } +zaino-convert-zebra = { path = "packages/zaino-convert-zebra" } +zaino-source-zebra-readstate = { path = "packages/zaino-source-zebra-readstate" } + # Zingo-infra (validator launcher driving the live tests; not a prod dep). # TEMPORARY (zingolabs/infrastructure#269): pinned to an exact commit on the # `add_client_support` branch so every machine resolves the same code; restore @@ -191,6 +239,8 @@ debug = true # inherits it through the single lock; Cargo honours `[patch.crates-io]` only # from the workspace root. See docs/updating_zebra_crates.md. # Tracking: zaino#. -zebra-chain = { git = "https://github.com/ZcashFoundation/zebra.git", rev = "9a27f886a5bfb143f65d1712e912cef252426800" } -zebra-rpc = { git = "https://github.com/ZcashFoundation/zebra.git", rev = "9a27f886a5bfb143f65d1712e912cef252426800" } -zebra-state = { git = "https://github.com/ZcashFoundation/zebra.git", rev = "9a27f886a5bfb143f65d1712e912cef252426800" } +# TEMPORARY: pointing at nachog00/zebra fork for ReadRequest::CompactBlock prototype. +# Revert to ZcashFoundation/zebra once upstream merges the feature. +zebra-chain = { git = "https://github.com/nachog00/zebra.git", rev = "d3f426b35" } +zebra-rpc = { git = "https://github.com/nachog00/zebra.git", rev = "d3f426b35" } +zebra-state = { git = "https://github.com/nachog00/zebra.git", rev = "d3f426b35" } diff --git a/docs/runtime/composition.md b/docs/runtime/composition.md new file mode 100644 index 000000000..074f70646 --- /dev/null +++ b/docs/runtime/composition.md @@ -0,0 +1,200 @@ +# Runtime Composition — FS Index Engine ⊕ NFS Reorg Window + +**Status:** design direction. Q1, Q2, Q4, Q5 resolved; Q3 open. This supersedes +the earlier "block-store spine ⊕ typed-index layer" framing — see **The key +insight**. + +## Problem + +The runtime (subdomain **C** — the orchestrator implementing +`zaino-service::IndexerService`) must handle the chain across **two operational +states**: + +- **Catch-up (bulk, on boot):** fetch the whole finalized history and build the + store + indexes. Throughput-bound, one-time, not fully operational + (passthrough covers the gap). +- **Tip-follow (steady):** ingest one newly-mined block at a time, reorg-aware. + Latency-bound, forever. + +Plus a **snapshot** for coherent cross-request reads spanning both. Legacy +`NodeBackedChainIndex` does all this unsoundly (three validator pollers, a +fragile single-writer, reorg-unsafe `height→hash` lookups, an undetected +chain-shortening bug). Two efforts each cover part: + +- **`zaino-sync`** — a parallel batch/DAG **index engine**. Great at bulk + indexing; models no NFS/reorg. +- **Hahn's `zaino-store`** (PR #1378) — a two-tier block store with a + **Lean-verified reorg algorithm** (`find_trim_index`); block-only, no typed + indexes. + +## The key insight + +**The finalized block store is not a separate thing — it's one index in the +sync engine.** A compact block is just `height → CompactBlock`; stored as an +index it gives 1-read block serving inside the same backend as the aux indexes. +The current 8-way body decomposition (headers/txids/sapling/orchard/transparent +as separate indexes) is *coincidental* — nothing queries the pieces +individually (verified against the serving code) — so collapse it into one +compact-block index. + +That dissolves the "block store vs index engine" split for the finalized state: +**the sync engine *is* the FS store.** And it collapses Hahn's role: with no +separate finalized block store needed, **Hahn contributes only the reorg-prone +window** — `Chain` + `find_trim_index` + snapshot. No `Freezer`, no forward-fill, +no `sync_step`. + +## Decision + +Two engines, split by **operational state** and by **finalized vs reorg-prone**, +meeting at the freeze horizon. + +``` + incoming reads (zaino-service::IndexerService) + │ + ┌─────────────────────────────────────┼─────────────────────────────────────┐ + │ Snapshot = { pinned NFS Chain (+ side branches), FS watermark } │ + │ recent (> watermark) → pinned NFS Chain (reorg-bound, actively pinned) │ + │ finalized (≤ watermark)→ FS index reads (append-only, passively coherent) │ + └─────────────────────────────────────┼─────────────────────────────────────┘ + │ + FS — sync engine (one backend) │ NFS — reorg window (adopt Hahn) + ────────────────────────────── │ ──────────────────────────────── + compact_block : height → CompactBlock │ Chain: in-mem ~101 blocks (im::Vector) + (= the block store; pre_index + │ find_trim_index: reorg (Lean-verified) + tree_sizes fold, reassembled) │ snapshot: pins the Chain + hash_to_height, txid_location, │ side-branch set (Q2) + transparent_spends, address-history │ aux queries here re-derive from Chain + built: BULK parallel on boot, │ driven: TIP-FOLLOW loop (1 block/iter) + per-block at freeze │ + │ + catch-up (bulk) ─────────── freeze horizon ─────────── tip-follow (steady) +``` + +### FS — the sync engine owns the finalized state (blocks + indexes) + +One backend, uniform indexes: +- **`compact_block`** — `height → CompactBlock`; *this is the finalized block + store*. 1-read serving. (Realized as `pre_index_compact_block` (L-scope) + a + `tree_sizes` (S-scope) fold, reassembled at serve — see Q4.) +- **aux reverse indexes** — `hash_to_height`, `txid_location`, + `transparent_spends`, address-history: the lookups a block store can't answer. +- The 8-way body decomposition is **dropped**. + +Built by the sync engine's **parallel bulk pipeline on boot** (start-height → +`tip−D`), extended **per-block at freeze** in steady state. Only ever indexes +immutable (finalized) blocks — so it needs **no reorg machinery**. + +### NFS — adopt Hahn's reorg window, and only that + +The in-memory reorg-prone window: +- **`Chain`** (`im::Vector`, ~101 blocks) + a **side-branch set** (Q2) for + fork-serving. +- **`find_trim_index`** — the Lean-verified reorg/fork-find (adopted + near-verbatim). +- **snapshot** — pin an `Arc` (Q1). +- Driven by a **tip-follow loop** (light: one block at a time). +- NFS aux queries **re-derive** from the `Chain` (cheap over ~101 blocks) — no + persistent NFS index. + +### The boundary + +The **freeze horizon** (`tip − D`). At boot the bulk build fills FS to `tip−D`, +then the NFS `Chain` slow-syncs the last `D`. In steady state a block crossing +the horizon **freezes**: the tip-follower hands the already-fetched block to the +sync engine's per-block extract (block + aux → FS). **No redundant fetch** — +bulk covers `[start, tip−D]`, the `Chain` covers `[tip−D, tip]` (disjoint); +freeze reuses the fetched block. + +## Ingestion & data flow + +- **Boot / catch-up:** the sync engine's parallel bulk pipeline builds FS + (compact-block index + aux) from the start height to `tip−D`. Passthrough + serves un-built ranges; **serviceability advertises the synced height** (partial + service during catch-up). Then the NFS `Chain` slow-syncs `tip−D → tip`. +- **Tip-follow (steady):** the tip-follow loop extends/reorgs the `Chain` (via + `find_trim_index`) one block at a time. On freeze, the graduated block is + extracted into FS. Reorgs only ever touch the `Chain`. +- **Single ingestor per range:** the bulk and tip paths are disjoint; freeze + reuses the fetched block. No two components fetch the same range. + +## The Snapshot + +`Snapshot = { pinned Arc, pinned Arc, FS watermark }`. +Recent reads (> watermark) hit the pinned `Chain`; finalized reads (≤ watermark) +hit the FS indexes (append-only, immutable — passively coherent, no versioning). +**FS-passive / NFS-pinned**, matching ADR-0003. First-class, client-held, held +until dropped (Q1). + +## Serving + +- **compact block by height** → FS `compact_block` index (finalized) or the NFS + `Chain` (recent). +- **aux lookups** (hash→height, txid→loc, spend-status, address) → FS aux + indexes (finalized) or re-derive from the `Chain` (recent). +- **fork queries** (getchaintips, orphaned-fork tx-status, fork-point) → the NFS + side-branch set (Q2). +- **full `Block` / raw-tx / proofs** → **validator passthrough**, not stored + (legacy `get_fullblock_bytes_from_node`; Q4). A full-block cache is a future + optimization only if whole-chain full-block streaming (zallet scan) is hot. + +## What we adopt vs keep + +- **Adopt from Hahn (`zaino-store`) — narrow:** `Chain` (`im::Vector`), + `find_trim_index` (Lean-verified reorg), the snapshot pin. **Not** the + `Freezer`, forward-fill, or `sync_step` — superseded by the sync engine's bulk + build + FS index backend. +- **Keep the sync engine (`zaino-sync`/`zaino-indexes`) whole**, change the index + set: **add** `compact_block` (pre-index + `tree_sizes`), **keep** the aux + reverse indexes, **drop** the 8-way body split. +- **Placement — a thin runtime + three delegated component crates**, each hiding + its infra behind domain semantics: + - **`zaino-fs`** — finalised state: elevates `zaino-sync`/`zaino-indexes` into + finalised-state semantics (serve compact blocks + aux, bulk-build, freeze); + hides the engine internally. + - **`zaino-nfs`** — reorg window: `Chain` + `find_trim_index` + snapshot + + side-branch set + tip-follow loop (`im`; **no LMDB**). + - **`zaino-mempool`** — tip-tagged mempool (deferred). + - **`zaino-runtime`** orchestrates the three and implements `IndexerService`; + **`zaino-core` stays pure**. + +## Design decisions + +Resolved (verified against `zaino-store` code + legacy serving code): + +- **Q1 — First-class snapshot.** Add a client-held `snapshot()` over the pinned + `Arc`; `im::Vector` supports it trivially (contra Hahn's rationale §3, + per reviewers/ADR-0003). +- **Q2 — Side branches.** Retain a companion side-branch set on the NFS window + for fork-serving. +- **Q4 — Finalized store = the sync engine's compact-block index** + (`pre_index_compact_block` L-scope + `tree_sizes` S-scope, reassembled at + serve; 2 reads) **+ aux reverse indexes**. Body decomposition dropped, **no + separate Freezer**. (Full-`CompactBlock` 1-read serving needs X-scope deps, + not yet built — take the 2-read for now.) +- **Q5 — Adopt only Hahn's NFS window** (`Chain` + `find_trim_index` + snapshot); + the FS/bulk path is the sync engine. `sync_step`/`Freezer`/forward-fill not + adopted. + +Open: + +- **Q3 — FS integrity + versioning.** The sync engine's FS has more structure + than Hahn's raw blocks (typed indexes), but still needs the integrity/tamper + model + migration story (candidate: PR #1347 primary/shadow routing). + +## Non-goals (not decided here) + +- Mempool — a separate component consuming the tip signal. +- Wire/serving projections (compact/proto/verbose) — outer adapters over the port. +- The `zaino-nfs` ↔ `zaino-sync` ↔ `zaino-runtime` seam traits — the next step + (capabilities algebra). + +## Sources + +- Hahn `zaino-store` (PR #1378): `DESIGN.md`, `block-store-rationale.md`, + `BlockStore.lean` — adopted narrowly (Chain + find_trim_index + snapshot). +- PR #1378 review (Nuttycombe, idky137): snapshot coherence, side-branch + retention, FS integrity. +- Legacy NFS survey + block-type survey (this thread): intent + the + CompactBlock/IndexedBlock/index-decomposition map. +- ADR-0003 (PR #1414): unconditional cross-request snapshot pinning. +- `docs/sync-engine/*`: the index engine. diff --git a/docs/runtime/user-stories.md b/docs/runtime/user-stories.md new file mode 100644 index 000000000..08542defc --- /dev/null +++ b/docs/runtime/user-stories.md @@ -0,0 +1,123 @@ +# Driving-Port User Stories + +Living document — updated as development uncovers modifications or new stories. +Layered by actor. Each story states what the consumer wants, and is traced to the +**capability** (a `zaino-service` trait) and the **component** that serves it. A +story is the *why* behind a capability; no capability lands without one. + +Status key: ✅ served (impl + test) · 🟡 scaffolded (trait exists) · ⬜ not started. + +--- + +## Actors + +- **Full-wallet library** — zallet and future in-process embedders. Links Zaino, + owns its own parsing/wallet-state. +- **Lightwallet-serving clients** — mobile/light wallets via zainod's gRPC + `CompactTxStreamer`. +- **Full-node-RPC / explorer** — via zainod's JSON-RPC. +- **Operator** — deploys and monitors. + +--- + +## Level 0 — Cross-cutting (every consumer) + +> **US-0.1 — Snapshot coherence.** As any consumer, I want to pin a view of the +> chain and have every read through it stay consistent across reorgs for the +> life of the handle, so a multi-read request never sees torn/mixed-chain data. +> — capability: `TakeSnapshot`/`Snapshot`; component: `RuntimeSnapshot` (pinned +> NFS `Arc` + FS watermark). ADR-0003. 🟡 + +> **US-0.2 — Serviceability during catch-up.** As any consumer, I want the port +> to tell me which capabilities are answerable *now*, and to serve what it can +> while still syncing (finalised reads before the recent window exists), so I get +> partial service instead of an all-or-nothing boot. +> — capability: `Serviceable`; component: `NfsView` readiness + serviceability +> manifest. Recent reads while `Syncing` → `NotServiceable`, never a false miss. ✅ (readiness path tested) + +> **US-0.3 — Reorg safety.** As any consumer, reads must never return data for a +> chain the snapshot isn't on; a reorg during my request must not corrupt my view. +> — component: NFS `Chain` + `find_trim_index` (Lean-verified) + snapshot pin. 🟡 + +--- + +## Level 1 — Full-wallet library (zallet) + +> **US-1.1 — Coherent block stream.** As a full-wallet lib, I want to stream +> blocks over a height range, coherent to one snapshot, so a scan isn't torn by a +> mid-scan reorg. — `CompactBlockRead::stream_compact` / passthrough. 🟡 + +> **US-1.2 — Tip-change subscription.** As a full-wallet lib, I want an explicit +> tip-change signal, so I learn of new blocks without scraping a mempool-close +> side effect. — `TipSubscribe`. 🟡 + +> **US-1.3 — Coherent address unspent set.** As a full-wallet lib, I want an +> address's unspent outpoints as of a snapshot, correct **across the +> finalised/recent boundary** — finalised UTXOs minus those spent in the recent +> window, plus UTXOs created in the recent window still unspent — so transparent +> balance reconciles without a seam. — `AddressRead::unspent_outpoints`; +> component: **a merge of FS index ∪ NFS re-derivation**. ⬜ *(next)* + +> **US-1.4 — Spend status.** As a full-wallet lib, I want the spend status of an +> outpoint as of a snapshot. — `SpendRead::spend_status`. 🟡 + +> **US-1.5 — Treestate / witnesses.** As a full-wallet lib, I want treestate and +> subtree roots at an in-view height, to build note witnesses. — `TreestateRead`. 🟡 + +> **US-1.6 — Broadcast.** As a full-wallet lib, I want to broadcast a tx and get +> a txid or a typed rejection. — `Broadcast` (validator passthrough). 🟡 + +> **US-1.7 — Full blocks / raw transactions.** As a full-wallet lib, I want full +> `Block`/raw-tx bytes to parse into my own types. — served by **validator +> passthrough, by hash within the snapshot** (Q4). ⬜ + +> **US-1.8 — Fork point.** As a full-wallet lib, I want to locate the fork point +> between my view and the best chain via a locator, to rewind exactly on reorg. +> — `ForkReconcile::fork_point`. 🟡 + +--- + +## Level 2 — Lightwallet serving (via zainod gRPC) + +> **US-2.1** compact-block stream over a range. — `CompactBlockRead`. 🟡 +> **US-2.2** treestate + subtree roots. — `TreestateRead`. 🟡 +> **US-2.3** address txids / balance / utxos. — `AddressRead`. ⬜ +> **US-2.4** tip / mempool / broadcast. — `TipSubscribe`/`MempoolSubscribe`/`Broadcast`. 🟡 + +--- + +## Level 3 — Full-node-RPC / explorer (via zainod JSON-RPC) + +> **US-3.1** verbose block/tx by height or hash. — passthrough / verbose projection. ⬜ +> **US-3.2** address balance / deltas / utxos / txids over ranges. — `AddressRead`. ⬜ +> **US-3.3** chain info / stats. — (RPC surface). ⬜ +> **US-3.4** mempool contents + broadcast. — `MempoolSubscribe`/`Broadcast`. 🟡 + +--- + +## Level 4 — Operator + +> **US-4.1 — Clean resume.** As an operator, I want to kill and restart and have +> sync resume from the last committed height with no manual step. — FS watermark +> + freeze commit; NFS re-established on boot. ⬜ + +> **US-4.2 — Observable, private sync progress.** As an operator, I want sync +> progress observable behind a **non-default** feature flag (per the privacy +> policy). — feature-gated tracing/metrics. ⬜ + +--- + +## Traceability + +The composition (`composition.md`) exists to serve these stories: `RuntimeSnapshot` +routing serves US-0.1/1.1/2.1; `NfsView` readiness serves US-0.2; the NFS reorg +window serves US-0.3/1.8; the FS index engine serves the finalised half of every +read; validator passthrough serves US-1.7/3.1. The address **merge** (US-1.3/2.3/ +3.2) is the next capability, and it drives adding address re-derivation to +`NfsSnapshot`. + +## Changelog + +| Date | Change | +|------|--------| +| 2026-07-22 | Initial draft. Actors + Levels 0–4, traced to capabilities. | diff --git a/docs/sync-engine/crate-architecture.md b/docs/sync-engine/crate-architecture.md new file mode 100644 index 000000000..92c0d8d17 --- /dev/null +++ b/docs/sync-engine/crate-architecture.md @@ -0,0 +1,119 @@ +## Crate Architecture + +The sync engine follows a hexagonal (ports-and-adapters) architecture. +Domain core crates define business logic with no external dependencies. +Port crates declare trait interfaces. Adapter crates implement those +traits against concrete infrastructure. Orchestration wires indexes +together. Shared infrastructure provides cross-cutting utilities. + +```mermaid +graph TB + subgraph domain_core ["Domain Core"] + primitives["zaino-primitives
Domain types"] + sync["zaino-sync
DAG sync engine"] + end + + subgraph ports ["Ports (Trait Crates)"] + source["zaino-source
Driven port: validator queries"] + persistence["zaino-persistence
Driven port: storage"] + end + + subgraph adapters ["Adapters"] + zebra_rpc["zaino-source-zebra-rpc
Zebra JSON-RPC adapter"] + zebra_rs["zaino-source-zebra-readstate
Zebra ReadState adapter"] + lmdb["zaino-backend-lmdb
LMDB storage adapter"] + end + + subgraph orchestration ["Orchestration"] + indexes["zaino-indexes
Zcash index definitions + sets"] + end + + subgraph infra ["Shared Infrastructure"] + rpc["zaino-rpc
JSON-RPC 2.0 client"] + convert["zaino-convert-zebra
zebra-chain to domain conversion"] + end + + %% Port dependencies on domain core + source -->|"depends on"| primitives + persistence -->|"depends on"| primitives + + %% Engine depends on ports + domain + sync -->|"depends on"| persistence + sync -->|"depends on"| primitives + + %% Orchestration depends on engine + domain + indexes -->|"depends on"| sync + indexes -->|"depends on"| primitives + + %% Adapter: zaino-source-zebra-rpc + zebra_rpc -.->|"implements
GetBlock, GetChainTip,
GetTreestate"| source + zebra_rpc -->|"depends on"| primitives + zebra_rpc -->|"depends on"| rpc + zebra_rpc -->|"depends on"| convert + + %% Adapter: zaino-source-zebra-readstate + zebra_rs -.->|"implements
GetBlock, GetChainTip"| source + zebra_rs -->|"depends on"| primitives + zebra_rs -->|"depends on"| convert + + %% Adapter: zaino-backend-lmdb + lmdb -.->|"implements
Backend, BackendReader,
BackendWriter"| persistence + + %% Infra dependencies + rpc -->|"depends on"| source + convert -->|"depends on"| primitives + + %% External dependencies (outside the workspace) + zebra_rpc -.->|"uses"| zebra_chain["zebra-chain"] + zebra_rs -.->|"uses"| zebra_chain + zebra_rs -.->|"uses"| zebra_state["zebra-state"] + convert -.->|"uses"| zebra_chain + lmdb -.->|"uses"| lmdb_ext["lmdb (C lib)"] + + %% Styling + classDef port fill:#e8f4f8,stroke:#2196F3,stroke-width:2px + classDef adapter fill:#fff3e0,stroke:#FF9800,stroke-width:2px + classDef core fill:#e8f5e9,stroke:#4CAF50,stroke-width:2px + classDef orch fill:#f3e5f5,stroke:#9C27B0,stroke-width:2px + classDef shared fill:#fce4ec,stroke:#E91E63,stroke-width:1px + classDef external fill:#f5f5f5,stroke:#9E9E9E,stroke-width:1px,stroke-dasharray: 5 5 + + class source,persistence port + class zebra_rpc,zebra_rs,lmdb adapter + class primitives,sync core + class indexes orch + class rpc,convert shared + class zebra_chain,zebra_state,lmdb_ext external +``` + +### Crate Summary + +| Crate | Layer | Role | Implements | +|-------|-------|------|------------| +| `zaino-primitives` | Domain Core | Zcash domain types: `Block`, `BlockHeader`, `Transaction`, `Height`, `BlockHash`, `TransactionHash`, `Zatoshis`, etc. Zero external dependencies beyond `thiserror`. | -- | +| `zaino-sync` | Domain Core | DAG-driven parallel sync engine. Defines the `IndexDef` trait hierarchy (scope axis: `BlockLocal` / `SelfCumulative` / `CrossIndex`; composition axis: `Append` / `Monoidal` / `Fold`), the `Schema` trait for persistence encoding, scheduler, block buffer, and pipeline. Generic -- contains no blockchain knowledge. | -- | +| `zaino-source` | Port (Driven) | One trait per validator query: `GetBlock`, `GetChainTip`, `GetBestBlockHeight`, `GetTransaction`, `GetBlockByHash`, `GetBlockVerbose`, `GetTreestate`, `GetAddressBalance`, `GetAddressDeltas`, `GetAddressTxids`, `GetAddressUtxos`, `GetMempoolTxids`, `GetSubtreeRoots`, `GetCommitmentTreeRoots`. Also provides `Resilient` wrapper and `RetryPolicy`. | -- | +| `zaino-persistence` | Port (Driven) | Storage abstraction: `Backend` (open reader/writer/flush), `BackendReader` (get/scan by namespace), `BackendWriter` (atomic commit of `WriteOp` batches). Namespace-based keyspace organization. | -- | +| `zaino-source-zebra-rpc` | Adapter (Source) | Bridges `zaino-source` traits to Zebra's JSON-RPC endpoint. Uses `zaino-rpc` for transport and `zaino-convert-zebra` for type mapping. | `GetBlock`, `GetChainTip`, `GetTreestate` | +| `zaino-source-zebra-readstate` | Adapter (Source) | Bridges `zaino-source` traits to Zebra's finalized state DB via `zebra-state` `ReadStateService` (Tower). Direct DB reads -- no HTTP, no JSON. Orders of magnitude faster for bulk sync. | `GetBlock`, `GetChainTip` | +| `zaino-backend-lmdb` | Adapter (Persistence) | LMDB-backed storage. One named database per `Namespace`. Zero-copy reads via memory-mapped files. Atomic batch commits. `NO_SYNC` mode with explicit flush at batch boundaries. | `Backend`, `BackendReader`, `BackendWriter` | +| `zaino-indexes` | Orchestration | Zcash-specific index definitions (headers, txids, txid-location, hash-to-height, transparent-spends, transparent-data, sapling, orchard) and pre-composed index sets (headers-only, headers-and-spends, current-zaino). Each index implements `IndexDef` + extract + merge + `Schema`. Sets define `ProvideContext` projections and builder functions. | `IndexDef`, `Extract*`, `Merge*`, `Schema` | +| `zaino-rpc` | Shared Infra | JSON-RPC 2.0 client: HTTP transport, request/response envelope, retry on work-queue exhaustion, authentication. Returns raw `serde_json::Value` -- response parsing is the adapter's responsibility. | -- | +| `zaino-convert-zebra` | Shared Infra | Pure conversion functions: `zebra-chain` types to `zaino-primitives` domain types. `block_from_zebra`, `header_from_zebra`, `header_from_parts` entry points composing per-type converters. | -- | + +### Dependency Flow + +The dependency graph enforces the hexagonal architecture invariant: + +- **Inward only**: adapters depend on ports, ports depend on domain core. Never the reverse. +- **No adapter-to-adapter**: source adapters and the persistence adapter are fully independent. +- **Engine is generic**: `zaino-sync` depends on `zaino-persistence` (the port), never on `zaino-backend-lmdb` (the adapter). It depends on `zaino-primitives` for `IndexId` only. +- **Orchestration is the composition root**: `zaino-indexes` depends on `zaino-sync` and `zaino-primitives` to define concrete Zcash indexes. The application entry point selects adapters and wires them together. +- **Conversion is infra, not domain**: `zaino-convert-zebra` depends on `zaino-primitives` (domain) and `zebra-chain` (external). It is used by source adapters, not by the engine. + +--- + +- [Formal Model](formal-model.md) +- [Design Spec](design.md) +- [Implementation Spec](implementation.md) +- [User Stories](user-stories.md) diff --git a/docs/sync-engine/design.md b/docs/sync-engine/design.md new file mode 100644 index 000000000..8f76578d4 --- /dev/null +++ b/docs/sync-engine/design.md @@ -0,0 +1,709 @@ +# Sync Engine Implementation Design + +Concrete architecture for a DAG-driven parallel sync engine, based on the formal +model in [index-sync-model.md](index-sync-model.md). + +This document covers the implementation design — crate structure, trait hierarchy, +data flow, and runtime behaviour. The formal model covers the theory — axes, +invariants, cost model. + +User stories derived from this design live in +[sync-engine-user-stories.md](sync-engine-user-stories.md). + +--- + +## 1. Architecture Overview + +Four layers, each independently testable and swappable: + +``` +┌─────────────────────────────────────────────┐ +│ Layer 1: Index Definitions │ +│ Descriptor + extract + merge + write_ops │ +│ (blockchain-specific) │ +└──────────────────┬──────────────────────────┘ + │ registered into +┌──────────────────▼──────────────────────────┐ +│ Layer 2: Sync Engine │ +│ DAG, scheduling, parallel dispatch, │ +│ merge, commit, flush │ +│ (generic — no blockchain knowledge) │ +└───┬──────────────────────────────┬──────────┘ + │ │ +┌───▼──────────────┐ ┌───────────▼───────────┐ +│ Layer 3: │ │ Layer 4: │ +│ Provisioner │ │ Backend │ +│ │ │ │ +│ source → context │ │ commit + flush + read │ +│ (blockchain- │ │ (storage-specific) │ +│ specific) │ │ │ +└──────────────────┘ └───────────────────────┘ +``` + +--- + +## 2. Core Invariant + +Every extraction receives inputs determined by its **scope**: + +| Scope | Extract signature | Inputs | +|-------|-------------------|--------| +| $\mathsf{L}$ (BlockLocal) | `extract(ctx) -> Delta` | BlockContext only | +| $\mathsf{S}$ (SelfCumulative) | `extract(ctx, prior) -> Delta` | BlockContext + own prior accumulated state | +| $\mathsf{X}$ (CrossIndex) | `extract(ctx, deps) -> Delta` | BlockContext + DepsReader over committed deps | + +The scope axis is enforced at the type level: each scope has its own extract +trait (`ExtractLocal`, `ExtractCumulative`, `ExtractCross`), so an L-scope +index literally cannot ask for prior state or dependency reads — the +signature doesn't have the parameter. + +**Invariants preserved:** +- No extractor calls the source directly. +- No extractor reads uncommitted state. +- No extractor accesses data about blocks other than $h$ through the source. +- Non-local data about earlier blocks comes from committed indexes via DepsReader + (X-scope) or from engine-threaded accumulated state (S-scope). + +An explicit escape hatch (`SourceAccess::NonLocal`) exists for cases where adding +an intermediate index is disproportionate to the need. When declared, the engine +provides a source handle and adjusts scheduling for I/O latency. *(placeholder +only — `SourceHandle` / `NonLocalSource` not yet implemented)* + +> **Note:** The original invariant described a uniform two-input signature +> `f(BlockContext, DepsReader)` for all scopes. The implementation replaces +> this with scope-specific trait signatures that make invalid access +> unrepresentable at compile time. The S-scope's `PriorState` is a third +> input channel the original formulation did not name — it is threaded by +> the engine (loaded from backend on resume, carried across blocks/batches +> by the bridge), not provided by the provisioner or the DepsReader. + +--- + +## 3. Crate Structure + +``` +zaino-sync/ ← generic, reusable, no blockchain knowledge + src/ + lib.rs ← public API surface + descriptor.rs ← InputScope, CompositionType, SourceAccess, Descriptor + traits.rs ← IndexDef, ExtractLocal/Cumulative/Cross, + MergeAppend/Monoidal/Fold, Schema, + ProvideContext, WriteOp + encode.rs ← Encode / Decode traits + built-in impls + dag.rs ← DependencyDag, FiringRule, acyclicity, phase assignment + scheduler.rs ← Scheduler, Task, ExtractJob, BatchHandle + engine.rs ← SyncEngine: supply/demand loop, entry points + block_buffer.rs ← BlockBuffer: sliding window, batch eviction + pipeline.rs ← IndexPipeline (trait-object-safe), IntoIndexPipeline + bridge.rs ← LocalBridge, CumulativeBridge, MergeStrategy, + BridgeDispatch (sealed) + backend.rs ← Backend, BackendReader, BackendWriter, WriterTopology + provisioner.rs ← Provisioner trait (generic) + progress.rs ← SyncProgress, watermark tracking, crash recovery + primitives.rs ← BlockHeight, IndexId, BatchIndex, PhaseIndex, BlockOffset + testing.rs ← InMemoryBackend, SlowBackend, TestBlockContext, + MockProvisioner + testing/ + toy_indexes.rs ← ValueIndex (L,A), CountIndex (L,M), + RunningSumIndex (L,F), CumulativeSumIndex (S,M) + +zaino-state/ ← existing crate + src/ + chain_index/ ← existing read-side code, UNCHANGED + + indexes/ ← NEW: per-index implementations (not yet started) + headers.rs ← (L,A) impl ExtractLocal + MergeAppend + block_heights.rs ← (L,A) + txid_location.rs ← (L,A) + transparent.rs ← (L,A) + sapling.rs ← (L,A) + orchard.rs ← (L,A) + commitment_tree.rs ← (L,A) or possibly (S,M) + spent.rs ← (L,A) + addr_history.rs ← (L,M) impl ExtractLocal + MergeMonoidal + txoutset_accum.rs ← (L,M) impl ExtractLocal + MergeMonoidal + + sync/ ← NEW: wiring (not yet started) + provisioner.rs ← impl Provisioner for Zcash (source → ZcashBlockContext) + backend_lmdb.rs ← impl Backend for LMDB + strategy.rs ← SyncStrategy trait + MonolithicSync + DagSync impls +``` + +--- + +## 4. Layer 1: Index Definitions + +### 4.1 Descriptor + +Static, declarative properties. No logic. The type-level markers (`BlockLocal`, +`SelfCumulative`, `CrossIndex`, `Append`, `Monoidal`, `Fold`) are sealed traits +with runtime mirrors for dynamic dispatch. + +```rust +struct Descriptor { + name: IndexId, + scope: InputScope, + composition: CompositionType, + dependencies: &'static [IndexId], + source_access: SourceAccess, +} + +// Type-level markers (sealed) +struct BlockLocal; // implements Scope +struct SelfCumulative; // implements Scope +struct CrossIndex; // implements Scope +struct Append; // implements Composition +struct Monoidal; // implements Composition +struct Fold; // implements Composition + +// Runtime mirrors +enum InputScope { BlockLocal, SelfCumulative, CrossIndex } +enum CompositionType { Append, Monoidal, Fold } +enum SourceAccess { None, NonLocal } +``` + +### 4.2 Context Projection (replaces SourceRequirements) + +~~The original design used `SourceRequirements` bitflags to configure the +provisioner at runtime.~~ The implementation uses **compile-time context +projection** via the `ProvideContext` trait instead: + +```rust +/// Project set-wide Ctx → per-index BlockContext. +trait ProvideContext { + fn context(&self) -> T; +} + +// Identity blanket: if the index wants the whole context, it's free. +impl ProvideContext for T { + fn context(&self) -> T { self.clone() } +} +``` + +The set-wide `Ctx` type parameter on `SyncEngine` is the provisioner's +output. Each index declares its own `IndexDef::BlockContext`, and the engine +requires `Ctx: ProvideContext` for every registered index. + +This means the provisioner always fetches the full context (the union is +implicit in the `Ctx` type), which is simpler but loses the runtime "fetch +only what's needed" optimisation. If that becomes a bottleneck, context +projection can be extended with a `SourceRequirements` bitflag approach at +the provisioner level without changing index code. + +### 4.3 Index Definition and Extraction Traits + +The root trait `IndexDef` declares scope and composition at the type level. +Extraction is split into scope-specific traits; merge is split into +composition-specific traits. + +```rust +/// Root: declares position on the Scope × Composition grid. +trait IndexDef: Send + Sync + 'static { + type Scope: Scope; // type-level: BlockLocal | SelfCumulative | CrossIndex + type Composition: Composition; // type-level: Append | Monoidal | Fold + type Delta: Send + Sync; + type BlockContext: Send + Sync + 'static; + + const NAME: IndexId; + const DEPENDENCIES: &'static [IndexId] = &[]; + const SOURCE_ACCESS: SourceAccess = SourceAccess::None; + + fn descriptor() -> Descriptor { /* derived from above */ } +} + +/// L-scope: pure block-level extraction. Full parallelism. +trait ExtractLocal: IndexDef { + fn extract(ctx: &Self::BlockContext) -> Result; +} + +/// S-scope: extraction depends on own prior accumulated state. +trait ExtractCumulative: IndexDef { + type PriorState: Send + Sync; + fn extract(ctx: &Self::BlockContext, prior: &Self::PriorState) + -> Result; +} + +/// X-scope: extraction depends on committed state of declared dependencies. +trait ExtractCross: IndexDef { + fn extract(ctx: &Self::BlockContext, deps: &DepsReader) + -> Result; +} +``` + +### 4.4 Merge Traits + +```rust +/// Append: disjoint keys. Marker trait — no merge logic needed. +trait MergeAppend: IndexDef {} + +/// Monoidal: associative + commutative combine. +trait MergeMonoidal: IndexDef { + type Accumulator: Send + Sync; + fn identity() -> Self::Accumulator; + fn lift(delta: Self::Delta) -> Self::Accumulator; + fn combine(a: Self::Accumulator, b: Self::Accumulator) -> Self::Accumulator; +} + +/// Fold: order-dependent sequential application. +trait MergeFold: IndexDef { + type FoldState: Send + Sync; + fn initial_state() -> Self::FoldState; + fn fold(state: &mut Self::FoldState, delta: Self::Delta); +} +``` + +### 4.5 Schema and Persistence Traits (replaces to_write_ops) + +~~Each merge trait originally had a `to_write_ops` method coupling merge +output directly to WriteOps.~~ The implementation separates this into two +layers: + +```rust +/// Typed persistence: merge result → typed entries. +/// Generic over M (the merge result type): +/// Vec for Append, Accumulator for Monoidal, FoldState for Fold. +trait Schema: IndexDef { + type Key: Encode + Decode + Send + Sync; + type Value: Encode + Decode + Send + Sync; + fn into_entries(merged: M) -> Vec<(Self::Key, Self::Value)>; + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> M; +} + +/// Byte-level serialisation boundary. +trait Encode { fn encode(&self) -> Vec; } +trait Decode: Sized { fn decode(bytes: &[u8]) -> Result; } +``` + +This separation enables versioned schema types (different `Key`/`Value` per +DB version) without changing extraction or merge logic. + +### 4.6 Bridges and MergeStrategy + +The engine operates on trait-object-safe `IndexPipeline` values. Bridges +adapt the statically-typed index traits to this dynamic interface: + +```rust +/// Trait-object-safe interface with interior mutability (Mutex). +trait IndexPipeline: Send + Sync { + fn descriptor(&self) -> &Descriptor; + fn extract_one(&self, ctx: &Ctx) -> Result<(), PipelineError>; + fn merge(&self) -> Result<(), PipelineError>; + fn persist(&self) -> Result, PipelineError>; + fn load_state(&self, reader: &dyn BackendReader) -> Result<(), PipelineError>; +} +``` + +Two bridge types exist: + +- **`LocalBridge`** — handles all BlockLocal compositions via + `MergeStrategy` dispatch (AppendStrategy, MonoidalStrategy, FoldStrategy). +- **`CumulativeBridge`** — handles SelfCumulative compositions. Maintains + `running_state` across blocks; loads prior state from backend on resume. + +`MergeStrategy` is a sealed trait that unifies the three composition +algebras into a single `accumulate_one` / `merge_deltas` interface, so the +bridge code doesn't branch on composition type. + +CrossIndex bridges are **not yet implemented** (no real Zcash indexes need +X-scope currently). + +--- + +## 5. Layer 2: Sync Engine + +### 5.1 Registration and DAG Construction + +At startup, the engine: + +1. Accepts index registrations via `IndexSet` builder pattern + (`.with::()` calls; type-checked at registration). +2. Builds the dependency DAG from declared dependencies (Kahn's topo sort). +3. Validates acyclicity, uniqueness, dep existence (rejects at `.build()` if + ill-formed). +4. Computes per-edge `FiringRule` (Pipelined or Barrier) and phase assignment. +5. Constructs bridges (LocalBridge / CumulativeBridge) and wraps them as + `Box>`. + +### 5.1.1 Scheduler and BatchHandle + +The scheduler (`Scheduler`) is the brain of the engine. It tracks per-index +progress and emits only work that is safe to run: + +```rust +enum Task { + Extract(ExtractJob), + CompleteBatch(BatchHandle), +} +``` + +State transitions are phantom-typed to prevent out-of-order reporting: + +```rust +struct FullyExtracted(()); // private unit — can't be constructed externally +struct Merged(()); + +struct BatchHandle { + index: IndexId, + batch: BatchIndex, + _state: PhantomData, +} +``` + +Lifecycle: `extraction_done()` -> `BatchHandle` -> +`merge_done()` -> `BatchHandle` -> `batch_committed()`. Each +transition is a method that consumes the handle and returns the next state. + +### 5.2 Runtime Pipeline + +The implementation uses a supply/demand loop rather than the originally +envisioned streaming-channel architecture. The engine is `SyncEngine` +with three entry points: + +```rust +// Pre-loaded blocks (tests, small ranges) +fn sync_range(&mut self, blocks: Vec) -> Result<(), SyncError>; + +// Lazy iterator (streaming without async) +fn sync_streaming>(&mut self, source: I) + -> Result<(), SyncError>; + +// Async channel (production: provisioner on separate task) +async fn sync_channel(&mut self, rx: tokio::sync::mpsc::Receiver) + -> Result<(), SyncError>; +``` + +The core loop (`sync_streaming`) is a three-phase dispatch: + +1. **Supply**: pull up to `batch_size` blocks from source into `BlockBuffer`. +2. **Demand**: call `scheduler.ready_work()` → dispatch tasks (extract via + rayon `par_iter`, then merge/persist/commit). +3. **Evict**: after all indexes commit batch N, evict buffer entries through N. + +``` +┌──────────────┐ ┌──────────────────┐ ┌────────────────┐ +│ Source │────→│ BlockBuffer │────→│ Engine loop │ +│ (iter/channel)│ │ (sliding window) │ │ supply/demand │ +└──────────────┘ └──────────────────┘ └───────┬────────┘ + │ batch ready + ┌────────▼────────┐ + │ Scheduler │ + │ ready_work() │ + └────────┬────────┘ + │ Task + ┌────────▼────────┐ + │ Extract (rayon) │ + │ Merge + Persist │ + │ Commit + Evict │ + └─────────────────┘ +``` + +**BlockBuffer** is a sliding-window `BTreeMap>` with batch-level +eviction. Backpressure is implicit: when the buffer is full, the supply phase +stalls. One block context serves all indexes (projected via `ProvideContext`). + +The scheduler does **not** know about fetching — it only checks if blocks are +available and if dependency firing rules are satisfied. + +### 5.3 Batch Size + +$B$ controls: +- Flush amortisation (fewer flushes = less I/O overhead). +- Memory pressure (larger $B$ = more deltas in flight). +- Phase gate latency (downstream phases wait for batch-sized chunks). +- $\mathsf{M}$-type collision containment scope. + +$B$ does NOT control provisioning or extraction cadence — those stream continuously. + +### 5.4 Phase Execution + +For multi-phase DAGs, the engine runs phases with staggered batch pipelining +where dependency read patterns allow it: + +``` +Phase 0: [extract β₀][extract β₁][extract β₂] ... + ↓ commit +Phase 1: [extract β₀][extract β₁] ... + ↓ commit +Phase 2: [extract β₀] ... +``` + +Each phase trails the one above by one batch boundary. In steady state, all +phases are active simultaneously. The bottleneck phase determines throughput. + +For dependencies that require global/final state (`SourceAccess::NonLocal` or +specific read patterns), the engine waits for the dependency to complete the +entire chain before starting the downstream phase. + +### 5.5 Progress and Crash Recovery + +**Implemented.** The engine stores a watermark (`METADATA_INDEX` / `WATERMARK_KEY`) +atomically with each batch commit in `try_commit()`. On startup, +`SyncEngine::committed_height(&backend)` reads the watermark to determine +resume height. The `SyncProgress` struct tracks the watermark in memory. + +Partially committed batches are discarded (the backend's atomic commit +guarantees no partial state). For S-scope indexes, the `CumulativeBridge` +loads its prior accumulated state from the backend via +`IndexPipeline::load_state(reader)` on resume. + +### 5.6 Tracing + +**Implemented** behind a non-default `tracing` feature flag (per privacy +policy — observability must be opt-in). Instrumented spans cover: +`sync_range`, `sync_streaming`, `sync_channel`, `dispatch_tasks`, +`run_extractions_parallel`, `merge_persist`, `try_commit`. + +--- + +## 6. Layer 3: Provisioner + +### 6.1 Trait + +Current implementation is a synchronous MVP: + +```rust +trait Provisioner: Send + Sync { + type BlockContext: Send + Sync; + + /// Fetch a contiguous range of block contexts. + fn provision_range( + &self, + from: BlockHeight, + to: BlockHeight, + ) -> Result, ProvisionError>; +} +``` + +The engine uses this via `sync_range` / `sync_streaming` (synchronous), or +spawns the provisioner on a separate tokio task feeding an `mpsc` channel +consumed by `sync_channel` (async production path). + +> **True north**: the provisioner should be a streaming async source with +> configurable concurrency and backpressure from the engine's `BlockBuffer`. +> The `sync_channel` entry point is the stepping stone. The `provision_range` +> → `Vec` shape is retained for tests and small ranges. + +### 6.2 Zcash Implementation (not yet started) + +```rust +struct ZcashProvisioner { + source: Arc, + network: Network, + concurrency: usize, +} + +struct ZcashBlockContext { + height: Height, + hash: BlockHash, + header: BlockHeader, + transactions: Vec, + tree_roots: Option<(SaplingRoot, OrchardRoot)>, + tree_sizes: Option<(u32, u32)>, + parent_chainwork: Option, +} +``` + +Context projection (`ProvideContext`) replaces the `SourceRequirements` +bitflag approach — the provisioner always fetches the full context; each +index receives its projection. See § 4.2. + +--- + +## 7. Layer 4: Backend + +### 7.1 Trait + +```rust +trait Backend: Send + Sync { + type Reader: BackendReader; + type Writer: BackendWriter; + + fn reader(&self) -> Result; + fn writer(&self) -> Result; + fn flush(&self) -> Result<(), BackendError>; + fn topology(&self) -> WriterTopology; +} + +enum WriterTopology { + /// All indexes share a single writer. Writes are serialised. + SharedWriter, + /// Each index has its own writer. Writes parallelise. + PerIndexWriter, +} + +trait BackendWriter: Send { + /// Commit a batch of write operations atomically. + fn commit(&mut self, ops: Vec) -> Result<(), BackendError>; +} + +trait BackendReader: Send { + fn get(&self, index: IndexId, key: &[u8]) -> Result>, BackendError>; + fn scan(&self, index: IndexId) -> Result, Vec)>, BackendError>; +} +``` + +> **DepsReader**: the design specified a visibility-restricted wrapper around +> `BackendReader`. In the implementation, `DepsReader` is a **placeholder +> struct** (no methods). It is passed to `ExtractCross` but cannot yet +> provide data. This is acceptable because no real Zcash indexes use X-scope. +> When CrossIndex bridges are built, `DepsReader` will wrap `BackendReader` +> with a `visible: HashSet` filter as originally designed. + +> **BackendReader::scan** replaces `cursor()`. Used by `CumulativeBridge` to +> load prior state on resume via `IndexPipeline::load_state(reader)`. + +### 7.2 WriteOp + +```rust +enum WriteOp { + Put { + index: IndexId, + key: Vec, + value: Vec, + }, + Delete { + index: IndexId, + key: Vec, + }, +} +``` + +Note: `IndexId` (a newtype) replaces `&'static str` for index names. + +--- + +## 8. Coexistence with Existing Code + +Both old and new write paths implement a shared strategy trait: + +```rust +trait SyncStrategy: Send + Sync { + async fn sync_to_height( + &self, + target: Height, + source: &dyn BlockchainSource, + ) -> Result<()>; +} +``` + +- `MonolithicSync`: wraps the existing `write_block_batch_blocking` path. Zero + changes to existing code. +- `DagSync`: wires the sync engine with registered indexes, provisioner, and + backend. + +Selection is config-driven. Both produce the same LMDB tables (assuming the same +index set), so the read-side traits are unaffected. + +--- + +## 9. The Implementor's Experience + +Adding a new (L,A) index: + +```rust +struct TransparentSpenderIndex; + +impl IndexDef for TransparentSpenderIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = Vec<(Outpoint, SpenderRef)>; + type BlockContext = ZcashBlockContext; + + const NAME: IndexId = IndexId::new("transparent_spender"); +} + +impl ExtractLocal for TransparentSpenderIndex { + fn extract(ctx: &ZcashBlockContext) -> Result { + let mut entries = Vec::new(); + for tx in &ctx.transactions { + let txid = tx.txid(); + for input in tx.transparent_inputs() { + if let Some(outpoint) = input.outpoint() { + entries.push((outpoint, SpenderRef { txid, .. })); + } + } + } + Ok(entries) + } +} + +impl MergeAppend for TransparentSpenderIndex {} // marker only + +impl Schema> for TransparentSpenderIndex { + type Key = Outpoint; + type Value = SpenderRef; + + fn into_entries(deltas: Vec) -> Vec<(Outpoint, SpenderRef)> { + deltas.into_iter().flatten().collect() + } + fn from_entries(entries: Vec<(Outpoint, SpenderRef)>) -> Vec { + vec![entries] + } +} +``` + +The implementor: +- Declares scope and composition **in the type system** (not runtime enums). +- Implements extraction with a scope-appropriate signature (no unused params). +- Implements `Schema` for typed persistence (serialisation is separate via `Encode`). + +The implementor does NOT: +- Think about scheduling, parallelism, or phase ordering. +- Manage transactions, commits, or flushes. +- Know about other indexes or the provisioner's internals. +- Write any concurrency code. +- Produce `WriteOp`s directly — the bridge handles that via `Schema` + `Encode`. + +--- + +## 10. Open Questions + +- **Validation**: should block validation (parent-hash continuity, merkle root) + be a responsibility of the engine, the provisioner, or a dedicated "validation + index" that produces no write ops but can fail the batch? + +- **Reorg handling**: how does the engine handle chain reorganisations during + tip-following? The model covers initial sync; steady-state tip-following with + rollbacks has different characteristics. + +- **Dynamic index sets**: can indexes be added or removed at runtime (e.g., + feature-gated indexes enabled by config), or is the index set fixed at startup? + +- **Incremental provisioner**: during tip-following (post initial sync), the + provisioner streams one block at a time rather than batch ranges. Should the + engine have a separate tip-following mode, or does the same pipeline work with + $B = 1$? + +## 11. Implementation Status + +Summary of what exists in `zaino-sync` (`feature/sync-engine-draft` branch) +as of 2026-07-09: + +### Done + +- Full trait hierarchy: `IndexDef`, scope-specific extract traits, composition- + specific merge traits, `Schema`, `Encode`/`Decode` +- Type-level descriptors with sealed Scope/Composition markers + runtime mirrors +- `DependencyDag` with Kahn's topo sort, `FiringRule` (Pipelined/Barrier) +- `Scheduler` with phantom-typed `BatchHandle` lifecycle +- `BlockBuffer` (sliding window, batch eviction) +- `LocalBridge` (all L×{A,M,F} compositions) via `MergeStrategy` dispatch +- `CumulativeBridge` (S×{M,F}) with `running_state` threading +- `IndexSet` builder (`.with::()` + `.build()`) +- `SyncEngine` with three entry points (`sync_range`, `sync_streaming`, + `sync_channel`) +- Watermark persistence and crash resume +- Feature-gated tracing +- `InMemoryBackend`, `SlowBackend`, `MockProvisioner`, 4 toy indexes, 29 tests + +### Not Done + +- **CrossIndex bridges**: `DepsReader` is a placeholder; `ExtractCross` compiles + but cannot provide data; Barrier firing rule always blocks (scheduler TODO) +- **SourceHandle / NonLocalSource**: placeholder structs only +- **Streaming provisioner**: `provision_range -> Vec` is sync; true streaming + awaits real provisioner implementation +- **Real Zcash indexes**: ~10 indexes, not started +- **Real provisioner**: Zebra ReadState / JSON-RPC adapter, not started +- **Real backend**: LMDB adapter, not started +- **zainod wiring**: IndexSet registration at startup, not started diff --git a/docs/sync-engine/formal-model.md b/docs/sync-engine/formal-model.md new file mode 100644 index 000000000..0ce958b3f --- /dev/null +++ b/docs/sync-engine/formal-model.md @@ -0,0 +1,657 @@ +# Index Sync Model + +A formal model for reasoning about parallel index construction in blockchain indexers. + +## 1. Motivation + +A blockchain indexer maintains a set of **indexes** — persistent data structures that +answer queries about the chain. During initial sync the indexer must construct all +indexes from genesis to tip. The order and degree of parallelism with which indexes +can be built is not arbitrary: it is constrained by data dependencies between indexes, +by the algebraic structure of their update operations, and by the cost model of the +persistence layer. + +This document formalises those constraints into a model that is: + +1. **Declarative** — each index is described by a small tuple of properties. +2. **Compositional** — the initial sync schedule for an arbitrary set of indexes is + derived mechanically from their individual descriptors. +3. **Implementation-agnostic** — the model separates *what* constraints exist from + *how* a particular engine satisfies them, enabling the invariants to be enforced + by a framework rather than by developer discipline. + +--- + +## 2. Definitions + +### 2.1 Chain and Blocks + +A **chain** $\mathcal{C}$ is an ordered sequence of **blocks** +$\langle b_1, b_2, \ldots, b_N \rangle$ where each $b_i$ is a self-contained +data payload at **height** $i$. Blocks are the atomic unit of consensus; they +arrive from a **source** and cannot be subdivided at the fetch boundary. + +Within a block, a sequence of **transactions** $\langle t_1, \ldots, t_k \rangle$ +may provide a finer-grained unit of independent work for some operations. + +### 2.2 Index + +An **index** $I$ is a persistent map $K_I \to V_I$ that is derived deterministically +from the chain. Given the same chain, the same index contents must result. + +An index **entry** is a single $(k, v)$ pair written to the index during construction. + +### 2.3 Index Set + +An **index set** $\mathcal{I} = \{I_1, I_2, \ldots, I_n\}$ is the complete +collection of indexes that the indexer maintains. The sync problem is: given a chain +$\mathcal{C}$ and an index set $\mathcal{I}$, construct all indexes correctly and as +fast as possible. + +### 2.4 Terminology + +Several terms in this domain are overloaded. This document uses the following +conventions consistently: + +| Term | Meaning | +|------|---------| +| **Initial sync** | The whole process of constructing all indexes from genesis to chain tip. The problem this model addresses. | +| **Coordination** | Worker-to-worker synchronisation: barriers, ordering constraints, and readiness conditions that ensure indexes are built in a valid order. | +| **Flush** | Forcing durably-committed data to stable storage (i.e., `fsync`). Provides the guarantee that persisted data survives process or power failure. | +| **Commit** | Making a batch of writes visible to readers within the persistence layer. After commit, the data is queryable; after flush, it is durable. A commit without a subsequent flush is visible but not crash-safe. | +| **Merge** | Combining per-block contributions into a batch-level or index-level result, according to the index's composition type ($\mathsf{A}$, $\mathsf{M}$, or $\mathsf{F}$). | +| **Extraction** | Computing the per-block contribution $\delta_I(b)$ for a given index $I$ and block $b$. The CPU-bound work of parsing, encoding, and checksumming. | +| **Pipelining** | Overlapping the extraction of block $b_{h+1}$ with the merge or fold of block $b_h$. Pipelining does not violate sequential dependencies — it exploits the fact that extraction and merging are different operations that may run concurrently when one does not depend on the other's result. | + +--- + +## 3. The Two Axes + +Every index has two orthogonal properties that determine its initial sync behaviour. +Together they form a classification grid that governs phase placement, parallelism, +and merge strategy. + +### 3.1 Axis 1: Input Scope + +**Input scope** answers: *what data must be available before computing index $I$'s +entries for block $b_h$?* + +| Symbol | Name | Definition | +|--------|------|------------| +| $\mathsf{L}$ | **Block-local** | Only the raw bytes of $b_h$ are needed. No dependency on any prior block or other index. | +| $\mathsf{S}$ | **Self-cumulative** | The index's own accumulated state through height $h{-}1$ is needed. The computation at height $h$ depends on the result at height $h{-}1$. | +| $\mathsf{X}(D)$ | **Cross-index** | The output of one or more other indexes $D \subseteq \mathcal{I} \setminus \{I\}$ is needed. | + +These can combine: an index may be both $\mathsf{S}$ and $\mathsf{X}(D)$, meaning it +depends on its own prior state *and* on other indexes. + +**Implications for ordering:** + +- $\mathsf{L}$: No ordering constraints from input scope. Can begin immediately. +- $\mathsf{S}$: Creates an intra-index ordering. Block $h$ cannot be processed + before block $h{-}1$ for this index (though this constraint may be relaxable via + axis 2; see below). +- $\mathsf{X}(D)$: Creates an inter-index ordering. Index $I$ cannot begin (or at + least cannot complete) until every $J \in D$ is available for reading. + +### 3.2 Axis 2: Composition Type + +**Composition type** answers: *given per-block (or per-batch) contributions computed +independently, how are they combined into the final index state?* + +| Symbol | Name | Definition | Parallelisation strategy | +|--------|------|------------|--------------------------| +| $\mathsf{A}$ | **Append** | Each block writes to disjoint keys. Contributions from different blocks never collide. | Embarrassingly parallel. Merge = concatenation. | +| $\mathsf{M}$ | **Monoidal** | Keys may overlap, but the merge operation forms a commutative monoid $(V_I, \oplus, e)$ — i.e., $\oplus$ is associative and commutative with identity $e$. | Parallel map-reduce. Batch results can be merged in any order. | +| $\mathsf{F}$ | **Fold** | The merge is order-dependent. The contribution of block $h$ to the final state depends on the *sequence* of prior contributions, not just their aggregate. | Sequential merge only. Can be pipelined (see below) but not parallelised across blocks. | + +**Pipelining in $\mathsf{F}$-type indexes:** + +For an $(\mathsf{S}, \mathsf{F})$ index, processing a single block has two stages: + +1. **Extract**: parse the raw block bytes into a contribution $\delta_I(b_h)$. This + only needs block data, not prior index state. +2. **Fold**: apply $f(s_{h-1}, \delta_I(b_h)) \to s_h$. This needs the previous + state and therefore cannot run until the prior fold completes. + +The fold is sequential, but extraction is not. Pipelining overlaps the extraction of +future blocks with the fold of the current block: + +``` +Extract: [b₁][b₂][b₃][b₄][b₅]... ← can run ahead +Fold: [b₁][b₂][b₃][b₄]... ← strictly sequential, stalls until prior fold completes +``` + +Without pipelining, each block waits for both extraction and fold to complete before +the next block's extraction begins. Pipelining hides extraction latency behind fold +latency (or vice versa), improving throughput by the ratio of the two. + +The key distinction from $(\mathsf{S}, \mathsf{M})$ is: a monoidal accumulation can +use parallel prefix to compute **all** cumulative states simultaneously. A +non-decomposable fold genuinely must apply $f$ in sequence — pipelining is the +strongest available strategy. + +**Interaction with self-cumulative scope ($\mathsf{S}$):** + +An index that is $(\mathsf{S}, \mathsf{M})$ has a self-referential dependency, but +the accumulation operation is monoidal. This means the sequential dependency is +*algebraically decomposable*: parallel prefix algorithms can compute all $N$ +cumulative values in $O(N / P + \log P)$ time on $P$ processors. The canonical +example is a running sum: $s_h = s_{h-1} + \delta_h$ is sequential, but addition is +associative, so prefix sums parallelise. + +### 3.3 Classification Grid + +The two axes form a grid. Each cell has a characteristic strategy: + +| | $\mathsf{A}$ (append) | $\mathsf{M}$ (monoidal) | $\mathsf{F}$ (fold) | +|---|---|---|---| +| $\mathsf{L}$ (block-local) | Embarrassingly parallel. No coordination needed. | Parallel map, reduce at merge. | Parallel extraction, sequential merge. | +| $\mathsf{S}$ (self-cumulative) | Pipeline, or parallel prefix if the accumulation is monoidal. | Parallel prefix. | Strictly sequential merge. Pipelining only. | +| $\mathsf{X}(D)$ (cross-index) | Dependency gate, then embarrassingly parallel. | Dependency gate, then map-reduce. | Dependency gate, then sequential merge. Worst case. | + +### 3.4 Relationship to the "All-Isolated" Model + +A simpler model sometimes proposed is to require that every index be buildable in +complete isolation — no index may depend on any other index or on its own prior +state. This model is a **special case** of the one presented here: it restricts +every index to $\sigma_I = \mathsf{L}$ and $D_I = \emptyset$. + +Under that restriction: +- All indexes are in phase 0 (no DAG edges). +- The classification grid collapses to a single row. +- The only variation is along the composition axis. + +This model subsumes the all-isolated model by additionally supporting indexes with +$\sigma = \mathsf{S}$ or $\sigma = \mathsf{X}(D)$, and by deriving the exact +constraints those dependencies introduce rather than prohibiting them outright. The +all-isolated model is sufficient for parallelism but not necessary; this model +captures the weaker, necessary conditions. + +--- + +## 4. Index Descriptor + +Each index is fully characterised for initial sync purposes by a **descriptor**: + +$$ +I = (\sigma_I,\; \gamma_I,\; c_I,\; w_I,\; D_I) +$$ + +| Component | Type | Meaning | +|-----------|------|---------| +| $\sigma_I$ | $\in \{\mathsf{L},\; \mathsf{S},\; \mathsf{X}\}$ | Input scope | +| $\gamma_I$ | $\in \{\mathsf{A},\; \mathsf{M},\; \mathsf{F}\}$ | Composition type | +| $c_I : \mathbb{B} \to \mathbb{R}^+$ | function | Compute cost per block (may depend on block content: tx count, action count, etc.) | +| $w_I : \mathbb{B} \to \mathbb{N}$ | function | Write amplification: number of entries produced per block | +| $D_I$ | $\subseteq \mathcal{I} \setminus \{I\}$ | Dependency set (empty if $\sigma_I = \mathsf{L}$; must be non-empty if $\sigma_I = \mathsf{X}$) | + +For $\mathsf{M}$-type indexes, the descriptor also implicitly specifies the monoid +$(V_I, \oplus_I, e_I)$. For $\mathsf{S}$-type indexes, it specifies the accumulation +function $\text{acc}_I : S_I \times \Delta_I \to S_I$. + +--- + +## 5. Dependency DAG + +Given an index set $\mathcal{I}$ with descriptors, the **dependency graph** +$G = (\mathcal{I}, E)$ has edges: + +$$ +E = \{(J, I) \mid J \in D_I\} +$$ + +That is, an edge from $J$ to $I$ means "$I$ depends on $J$." + +**Constraint**: $G$ must be a DAG. Cycles would mean deadlock; the index set is +ill-formed if any cycle exists. + +### 5.1 Phase Assignment (Conservative Scheduling) + +The **phase assignment** $\phi : \mathcal{I} \to \mathbb{N}$ is the topological +layer function: + +$$ +\phi(I) = \begin{cases} +0 & \text{if } D_I = \emptyset \\ +1 + \max_{J \in D_I} \phi(J) & \text{otherwise} +\end{cases} +$$ + +All indexes in the same phase are mutually independent and can be built concurrently. +Under conservative scheduling, phase $p$ cannot begin until all indexes in phases +$0, \ldots, p{-}1$ have completed their merge for the current batch and their state +is readable. + +The **depth** of the DAG — $\max_I \phi(I)$ — is the minimum number of sequential +phase boundaries under this conservative strategy. + +### 5.2 Per-Edge Scheduling (Optimal) + +Phase assignment groups indexes into uniform layers and places a barrier between each +layer. This over-coordinates when indexes within a phase have different costs or +serve different downstream consumers. + +The optimal scheduling strategy operates at the granularity of individual DAG edges +rather than layers. Each index $I$ has a **firing rule**: $I$ can begin processing +batch $\beta_j$ as soon as every dependency in $D_I$ has individually completed the +work required for $\beta_j$. + +$$ +\text{ready}(I, \beta_j) = \bigwedge_{J \in D_I} \text{available}(J, \beta_j) +$$ + +Under per-edge scheduling: +- If index $A$ finishes batch $\beta_j$ before unrelated index $B$ (both in the + same phase), then index $C$ (which depends only on $A$) can start $\beta_j$ + immediately — it need not wait for $B$. +- No artificial grouping into layers is needed; the DAG edges *are* the schedule. + +Phase assignment remains useful as a mental model and as a simpler (if conservative) +implementation strategy. Per-edge scheduling is the theoretical optimum. + +### 5.3 Dependency Composition Type and Downstream Scheduling + +Whether a downstream index can begin consuming a dependency's output before the +dependency has completed its merge for a batch depends on the dependency's +**composition type**: + +| Dependency's $\gamma$ | When downstream can read | Why | +|---|---|---| +| $\mathsf{A}$ | Per-entry, as each entry is written | Every entry is final on write. No merge step produces intermediate state. | +| $\mathsf{M}$ | After the batch merge completes | Keys are in flux until the monoidal reduce finishes; reading mid-merge yields an intermediate value that does not correspond to any valid chain state. | +| $\mathsf{F}$ | After the batch fold completes | Fold state is meaningless until applied in full sequence. | + +For $\mathsf{A}$-type dependencies, a downstream index could in principle stream +entries from its dependency as they are produced, overlapping extraction and +downstream computation within the same batch. This is a second-order optimisation +that the model notes as sound but does not require. + +--- + +## 6. Batch Processing + +### 6.1 Batches + +A **batch** $\beta = \langle b_j, b_{j+1}, \ldots, b_{j+B-1} \rangle$ is a +contiguous subsequence of $B$ blocks. The chain is partitioned into +$\lceil N / B \rceil$ batches, where $B$ is the **batch size** — a tuning parameter. + +Within a batch, per-block contributions are computed (potentially in parallel) and +then merged and persisted together. + +Batches serve a dual role: + +1. **Performance**: amortise flush cost over $B$ blocks rather than paying it per + block. +2. **Correctness**: contain $\mathsf{M}$-type key collisions to a controlled, + single-writer merge scope. Without batching, concurrent writes to overlapping + keys would require per-key locking or atomic read-modify-write support in the + persistence layer. + +### 6.2 Processing a Batch + +For a single phase $p$ and batch $\beta$: + +**Step 1: Extract.** For each index $I$ with $\phi(I) = p$ and each block +$b \in \beta$, compute the per-block contribution $\delta_I(b)$. + +- If $\sigma_I = \mathsf{L}$: each $\delta_I(b)$ can be computed independently. +- If $\sigma_I = \mathsf{S}$: contributions within the batch may require + intra-batch sequencing (or parallel prefix if $\gamma_I = \mathsf{M}$). +- If $\sigma_I = \mathsf{X}$: contributions may require reads from indexes + whose dependency gate has been satisfied in earlier phases. + +**Step 2: Merge.** Combine per-block contributions into a batch-level result. + +- If $\gamma_I = \mathsf{A}$: the batch result is the union of all entries + (disjoint keys; no conflict resolution needed). +- If $\gamma_I = \mathsf{M}$: the batch result is + $\bigoplus_{b \in \beta} \delta_I(b)$, computed via the monoidal operation. + Commutativity and associativity guarantee this can be done in any order. +- If $\gamma_I = \mathsf{F}$: the batch result must be computed by applying + contributions in chain order. + +**Step 3: Commit.** Write the batch-level results to the persistence layer and make +them visible to readers. After commit, subsequent phases (or the next batch of the +current phase) can read the committed entries. + +**Step 4: Flush.** Force the committed data to stable storage (`fsync`). After +flush, the data is durable — it will survive process or power failure. Flush is the +expensive I/O operation; its cost is amortised over all $B$ blocks in the batch. + +Commit and flush are distinct operations with different costs and guarantees: + +| Operation | After completion | Cost | +|-----------|-----------------|------| +| **Commit** | Data is visible to readers within the persistence layer | Fast (in-memory page table update) | +| **Flush** | Data is durable on stable storage | Slow (device I/O, typically 1-10ms on SSD) | + +A batch may be committed (enabling downstream phases to proceed) before it is +flushed, if the system tolerates replaying uncommitted batches on crash recovery. +Alternatively, commit and flush may be combined as a single atomic step for +simplicity. + +### 6.3 Cross-Batch Dependencies + +For $(\mathsf{S}, \mathsf{M})$ indexes, cross-batch state threading can use +**parallel prefix**: + +1. Each batch computes its local aggregate $a_\beta = \bigoplus_{b \in \beta} \delta_I(b)$. +2. A prefix scan over the batch aggregates produces cumulative state at each batch boundary. +3. Each batch can then derive per-block cumulative values from its local data and the prefix result. + +This reduces the sequential dependency from $N$ blocks to $\lceil N/B \rceil$ batch-level prefix operations. + +For $(\mathsf{S}, \mathsf{F})$ indexes, no such decomposition exists. The batch must +receive the prior batch's final state before its fold step can proceed (though its +extraction step can run ahead; see Section 3.2 on pipelining). This makes +$(\mathsf{S}, \mathsf{F})$ the critical path bottleneck for any initial sync +pipeline. + +### 6.4 Cross-Phase Pipelining + +Phases do not need to wait for the previous phase to complete across the entire +chain before starting. A downstream phase can begin processing batch $\beta_j$ as +soon as the dependency gate for $\beta_j$ is satisfied (per Section 5.2). + +For dependencies with read pattern $\mathsf{R_{\leq}}$ (the downstream index at +height $h$ reads from its dependency at heights $\leq h$), pipelining applies: +phase $p$ can process batch $\beta_j$ while phase $p-1$ processes $\beta_{j+1}$. +In steady state, all phases are active simultaneously, staggered by one batch. + +For dependencies that require the dependency's global/final state +($\mathsf{R_{*}}$), pipelining is not possible: the downstream phase must wait for +the dependency to complete the entire chain. + +The read pattern is a property of the specific dependency relationship, not of the +index in isolation. Each entry in $D_I$ should be understood as carrying an implicit +read pattern. + +--- + +## 7. Cost Model + +### 7.1 Per-Batch Cost + +For a single batch $\beta$ in phase $p$: + +$$ +T_{\text{batch}}(p, \beta) = T_{\text{compute}}(p, \beta) + T_{\text{write}}(p, \beta) + T_{\text{flush}} +$$ + +**Compute:** + +$$ +T_{\text{compute}}(p, \beta) = \max_{I : \phi(I) = p} \left[ + \frac{1}{P_I} \sum_{b \in \beta} c_I(b) + + R_I(\beta) + + M_I(\beta) +\right] +$$ + +- $P_I$: number of parallel workers available for index $I$'s extraction +- $R_I(\beta)$: cost of reading from dependency indexes for this batch +- $M_I(\beta)$: cost of merging per-block contributions within this batch + +The $\max$ arises because indexes within a phase are concurrent; the slowest +determines the phase's compute time. + +**Write:** + +Under a **shared-writer** persistence model (e.g., single LMDB environment): + +$$ +T_{\text{write}}(p, \beta) = \sum_{I : \phi(I) = p} w_I(\beta) \cdot \omega +$$ + +where $\omega$ is the amortised cost per `put` operation and $w_I(\beta) = \sum_{b \in \beta} w_I(b)$. + +Under a **per-index-writer** model (separate environments): + +$$ +T_{\text{write}}(p, \beta) = \max_{I : \phi(I) = p}\; w_I(\beta) \cdot \omega +$$ + +**Flush:** + +$$ +T_{\text{flush}} = F +$$ + +A largely fixed cost per batch. Typically 1-10 ms on SSD, ~50 ms on spinning disk. + +### 7.2 Total Initial Sync Time + +$$ +T_{\text{total}} = \sum_{p=0}^{\text{depth}} \sum_{j=0}^{\lceil N/B \rceil - 1} +T_{\text{batch}}(p, \beta_j) +$$ + +Note that the sum over phases is sequential (phase boundaries require coordination), +while within each phase, batches may overlap via pipelining if sufficient parallelism +exists. With cross-phase pipelining (Section 6.4), the phases overlap as well, +reducing total time toward: + +$$ +T_{\text{pipelined}} \approx \text{depth} \times T_{\text{batch,max}} + \frac{N}{B} \times \max_{p}\; T_{\text{batch}}(p) +$$ + +where the first term is pipeline fill latency and the second is steady-state +throughput governed by the bottleneck phase. + +### 7.3 Marginal Cost of a New Index + +Given an existing index set $\mathcal{I}$ and a proposed new index $I_{\text{new}}$ +with descriptor $(\sigma, \gamma, c, w, D)$: + +**Phase placement**: $\phi(I_{\text{new}})$ is determined by $D$ as per Section 5. + +**Case 1** — $I_{\text{new}}$ joins an existing phase $p$: + +$$ +\Delta T_{\text{compute}} = \sum_\beta \max\!\Big(0,\; + T_{I_{\text{new}}}(\beta) - \max_{I : \phi(I) = p} T_I(\beta) +\Big) +$$ + +If $I_{\text{new}}$ is faster than the current bottleneck in phase $p$, its compute +cost is fully hidden: $\Delta T_{\text{compute}} = 0$. + +$$ +\Delta T_{\text{write}} = \begin{cases} +\sum_\beta w_{I_{\text{new}}}(\beta) \cdot \omega & \text{(shared writer)} \\ +\sum_\beta \max(0,\; w_{I_{\text{new}}}(\beta) \cdot \omega - W_{\max}(p, \beta)) & \text{(per-index writer)} +\end{cases} +$$ + +**Case 2** — $I_{\text{new}}$ creates a new phase $p' = \text{depth} + 1$: + +$$ +\Delta T = \sum_\beta \Big[ T_{I_{\text{new}}}(\beta) + w_{I_{\text{new}}}(\beta) \cdot \omega + F \Big] +$$ + +The full cost is additive — nothing hides behind it. + +--- + +## 8. Invariants + +The following properties must hold for any correct initial sync execution. These are +the rules that a framework can enforce mechanically. + +### 8.1 Dependency Precedence + +> **Invariant 1.** For every index $I$ and every $J \in D_I$: all entries of $J$ +> through height $h$ must be readable before computing $I$'s entries at height $h$. + +This is enforced by the firing rules described in Section 5.2: either via +conservative phase barriers or via per-edge readiness tracking. + +### 8.2 Merge Determinism + +> **Invariant 2.** For $\mathsf{A}$-type indexes: keys produced by different blocks +> must be disjoint. For $\mathsf{M}$-type indexes: the merge operation must be +> associative and commutative. For $\mathsf{F}$-type indexes: contributions must be +> applied in strict chain order. + +This is enforceable at the type level: the framework can require $\mathsf{M}$-type +indexes to provide a monoid implementation that the merge step uses, and can enforce +ordering for $\mathsf{F}$-type merges. + +### 8.3 Batch Atomicity + +> **Invariant 3.** All entries produced by a batch are committed atomically. Either +> all entries for all indexes in the batch are committed, or none are. + +This ensures crash recovery is clean: the indexer can resume from the last +fully-committed batch boundary without partial state. + +### 8.4 DAG Well-Formedness + +> **Invariant 4.** The dependency graph $G$ induced by the descriptors $\{D_I\}$ +> must be acyclic. This is a static property of the index set and can be verified +> at registration time. + +--- + +## 9. Separation of Concerns + +The central question: can the model's invariants be enforced by a generic framework, +so that *implementing a new index* does not require understanding the full initial +sync theory? + +The answer is yes, via a clean separation into three layers. + +### 9.1 Layer 1: Index Definition (provided by the implementor) + +For each index, the implementor provides: + +1. **Descriptor declaration**: the tuple $(\sigma, \gamma, D)$ — input scope, + composition type, and dependency set. These are static, declarative properties. + +2. **Extract function**: given a block (and, if $\sigma = \mathsf{X}$, a read handle + to dependency indexes), produce the per-block contribution $\delta_I(b)$. + +3. **Merge specification** (required only if $\gamma \neq \mathsf{A}$): + - For $\mathsf{M}$: the monoid $(V_I, \oplus, e)$. + - For $\mathsf{F}$: the fold function $f : S \times \delta \to S$. + +4. **Write function**: given the merged batch result, produce the persistence + operations (key-value puts and deletes). + +The implementor does **not** specify: +- When to run relative to other indexes (derived from the DAG). +- How many blocks to process per batch (a tuning parameter). +- When to flush (a framework decision). +- How to parallelise extraction (the framework maps over blocks/txs). + +### 9.2 Layer 2: Sync Engine (the framework) + +The framework is parameterised by an index set and handles: + +1. **DAG construction and validation.** At registration time, read all descriptors, + build $G$, verify acyclicity (Invariant 4), and compute the phase assignment + $\phi$. + +2. **Batch scheduling.** Partition the chain into batches of size $B$. For each + phase, dispatch extraction work to a thread pool. + +3. **Dependency wiring.** For phase $p > 0$, provide indexes in phase $p$ with + read handles to the (now-committed) indexes from earlier phases. The framework + guarantees Invariant 1 by construction — the read handles are not available + until the dependency's firing rule is satisfied. + +4. **Merge dispatch.** After extraction, invoke the appropriate merge strategy + based on $\gamma$: + - $\mathsf{A}$: no-op merge (just collect). + - $\mathsf{M}$: parallel reduce using the declared monoid. + - $\mathsf{F}$: sequential apply in chain order. + This enforces Invariant 2. + +5. **Persistence and flush.** Collect write operations from all indexes in the + batch, commit them in a single transaction, then flush. This enforces + Invariant 3. + +6. **Progress tracking.** Maintain a persistent watermark (the last fully-flushed + batch boundary) for crash recovery. + +### 9.3 Layer 3: Persistence Backend (pluggable) + +The persistence layer is a separate concern from both the index logic and the sync +engine: + +1. **Writer interface**: accepts batched key-value operations (puts, deletes) and + commits them atomically. +2. **Reader interface**: provides point lookups and cursor scans over committed data. +3. **Flush interface**: exposes durability flush with configurable guarantees. +4. **Topology**: may offer a single shared writer or per-index writers, which the + sync engine can query to choose its write cost model (sum vs max). + +### 9.4 The Implementor's Experience + +Under this separation, adding a new index to the system involves: + +1. Declare the descriptor: "I am $(\mathsf{X}, \mathsf{M})$ and I depend on + index $J$." +2. Implement `extract(block, deps) -> entries`. +3. Implement the monoid `merge(a, b) -> c` (if $\mathsf{M}$) or fold (if + $\mathsf{F}$). +4. Implement `write(result) -> [put/delete ops]`. + +The framework handles everything else. Crucially: + +- **Phase placement is automatic.** The implementor declares dependencies; the + framework computes the phase. +- **Parallelism is automatic.** The framework parallelises extraction across + blocks (and optionally across transactions) without the implementor writing + any concurrency code. +- **Merge correctness is enforced.** The framework calls the monoid/fold in the + right pattern; the implementor cannot accidentally apply contributions out of + order for an $\mathsf{F}$-type index. +- **Persistence atomicity is guaranteed.** The implementor never calls flush or + manages transactions; the framework batches and commits. + +The invariants from Section 8 are structural properties of the framework, not +conventions that implementors must remember. + +### 9.5 What This Does Not Capture + +The model and framework enforce *ordering and concurrency* invariants. They do not +enforce *semantic* correctness of individual indexes: if an implementor writes a +buggy `extract` function that produces wrong entries, the framework will +faithfully persist them. Semantic correctness remains the implementor's +responsibility, testable through standard unit and integration testing against +known chain data. + +The model also does not capture **reorg handling** (rolling back the tip region +when the chain reorganises). Reorgs have different cost characteristics from +initial sync — batch sizes are small (1-2 blocks), and the cost of *undoing* +entries depends on the composition type: +- $\mathsf{A}$: delete the keys written for the reverted block. +- $\mathsf{M}$: apply the inverse operation $\ominus$ if the monoid has + inverses, or recompute from a checkpoint. +- $\mathsf{F}$: recompute from the last checkpoint. + +A full model of reorg cost is left for future work. + +--- + +## 10. Summary + +| Concept | What it captures | +|---------|-----------------| +| **Input scope** ($\sigma$) | When an index can start: immediately, after itself, or after dependencies | +| **Composition type** ($\gamma$) | How per-block work combines: trivially, via reduce, or sequentially | +| **Descriptor tuple** | Complete specification of an index's initial sync properties | +| **Dependency DAG** | The minimum sequential structure, derived mechanically from descriptors | +| **Batch size** ($B$) | Amortisation knob for flush cost and collision containment for $\mathsf{M}$-type indexes | +| **Cost model** | Quantitative framework for estimating initial sync time and marginal impact of changes | +| **Three-layer separation** | Ensures the framework enforces invariants; implementors only provide index logic | +| **All-isolated containment** | The all-indexes-independently-buildable model is a special case ($\sigma = \mathsf{L}$ for all $I$) | + +The key insight is that the initial sync schedule for any index set is *not a design +decision* — it is a *derivable consequence* of the declared properties of each +index. A framework that computes the schedule from descriptors removes an entire +class of bugs (ordering violations, missed dependencies, incorrect parallelism) +from the implementor's concern. diff --git a/docs/sync-engine/implementation.md b/docs/sync-engine/implementation.md new file mode 100644 index 000000000..840504eee --- /dev/null +++ b/docs/sync-engine/implementation.md @@ -0,0 +1,325 @@ +# Sync Engine Implementation + +This document describes the concrete implementation of the sync engine as it +exists in the `review-sync` branch. It is written for a developer who has read +the [design document](../sync-engine-design.md) and the +[formal model](../index-sync-model.md) but has not yet explored the code. + +--- + +## Context + +Zaino's original `zaino-state` crate grew into a monolith. The `BlockchainSource` +trait in `zaino-state/src/chain_index/source.rs` accepted a `HashOrHeight`, returned +an `Arc`, and mixed transport concerns (JSON-RPC envelope +parsing, Zebra `ReadStateService` tower calls, mempool queries, treestate retrieval, +subtree-root pagination) into a single god-trait with over a dozen methods. Every +consumer imported `BlockchainSource` and transitively pulled in Zebra types, the +`zaino-fetch` HTTP client, protocol-buffer definitions, and the LMDB storage layer. +Adding a new data source or swapping the persistence engine required touching code +throughout the tree. + +The sync engine redesign replaces this with a hexagonal architecture. Domain logic +lives at the centre, behind trait boundaries that separate the three external +concerns -- where blocks come from (source adapters), how extracted data is stored +(persistence adapters), and what computation turns raw blocks into indexed entries +(index definitions). The formal model provides the theoretical framework: every index +declares its input scope and composition type, and the engine derives the schedule, +parallelism strategy, and merge semantics from those declarations alone. + +--- + +## Crate Architecture + +The implementation is split across ten crates organised into four dependency layers +that mirror the design document's conceptual layers, plus conversion and transport +crates that sit outside the core dependency chain. + +At the foundation sits `zaino-primitives`, a zero-dependency crate that defines the +vocabulary types every other crate needs: `Height`, `BlockHash`, `TransactionHash`, +`Block`, `BlockHeader`, `Transaction`, and the shielded and transparent sub-structures. +Nothing in the system depends on Zebra types or storage types for its core domain +model; everything passes through `zaino-primitives` first. + +The two driven-port crates define the trait boundaries. `zaino-source` declares +one trait per question a consumer can ask about the chain -- `GetBlock`, `GetChainTip`, +`GetTransaction`, `GetAddressBalance`, and so on -- each with its own per-trait error +type. Consumers compose capabilities via trait bounds (`fn sync`), +and adapters implement whichever subset they support. `zaino-persistence` declares the +storage interface: `Backend`, `BackendReader`, `BackendWriter`, and `WriteOp`. Both +crates include mock implementations behind a `testing` feature flag. + +The sync engine itself lives in `zaino-sync`. This is the generic, blockchain-agnostic +orchestrator. It contains the trait hierarchy (`IndexDef`, `ExtractLocal`, +`ExtractCumulative`, `MergeAppend`, `MergeMonoidal`, `MergeFold`, `Schema`), the +dependency DAG builder, the scheduler with phantom-typed batch handles, the +`BlockBuffer` sliding window, the bridge layer that erases index types for dynamic +dispatch, and the `SyncEngine` orchestrator with its three entry points. Nothing in +this crate knows about Zcash, LMDB, or JSON-RPC. + +Blockchain-specific index definitions live in `zaino-indexes`. Each index module +declares a narrow context type, implements `IndexDef` with compile-time scope and +composition markers, provides an extraction function, and defines a `Schema` for +persistence encoding. Index set modules compose indexes into named configurations, +define the set-wide context type, and provide `ProvideContext` projections. Currently +two sets exist: `headers_only` (a single `HeadersIndex`) and `headers_and_spends` +(`HeadersIndex` plus `TransparentSpendsIndex`). + +Adapter crates implement the driven-port traits against concrete technologies. On the +source side, `zaino-source-zebra-rpc` bridges `zaino-source` query traits to a Zebra +validator over JSON-RPC using the shared `zaino-rpc` HTTP client, while +`zaino-source-zebra-readstate` opens Zebra's finalized-state RocksDB directly for +zero-copy reads. On the persistence side, `zaino-backend-lmdb` implements `Backend` +against LMDB with one named database per namespace. The conversion crate +`zaino-convert-zebra` maps `zebra_chain` types into `zaino-primitives` domain types, +providing both full-block conversion (`block_from_zebra`) and a fast header-only path +(`header_from_parts`) that avoids deserialising transactions entirely. + +The dependency graph flows strictly inward: adapter crates depend on port-trait crates +and `zaino-primitives`; `zaino-indexes` depends on `zaino-sync` and `zaino-primitives`; +`zaino-sync` depends on `zaino-persistence` (for the `Backend` trait it orchestrates +against). No adapter depends on another adapter. No domain crate depends on an adapter. + +--- + +## How New Crates Refactor Existing Code + +The old `BlockchainSource` trait in `zaino-state` combined block fetching, transaction +lookup, mempool access, treestate queries, subtree-root pagination, address balance +lookups, and chain-tip polling into a single interface. Both the `FetchService` +(JSON-RPC client) and `StateService` (Zebra `ReadStateService` wrapper) had to +implement every method, and callers that only needed block data still pulled in the +full dependency tree. + +`zaino-source` decomposes this into fine-grained traits. `GetBlock` returns a domain +`Block` for a height. `GetChainTip` returns the tip height and hash. `GetTransaction` +fetches by txid. Each trait has a dedicated error type, and each adapter implements +only what it supports. The resilience wrapper (`Resilient`) decorates any adapter +with retry and backoff logic without baking retry into each implementation. + +The old `zaino-fetch` crate contained both the JSON-RPC HTTP client and the +response-parsing logic for each RPC method. The new `zaino-rpc` crate retains only +the transport layer -- HTTP requests, JSON-RPC envelope, authentication, and +work-queue-exhaustion retry -- and returns raw `serde_json::Value`. Response parsing +moves into the adapter crate (`zaino-source-zebra-rpc`), where it belongs. + +The old LMDB code in `zaino-state` was entangled with the indexing logic. +`zaino-persistence` extracts the storage contract into `Backend`, `BackendReader`, and +`BackendWriter` traits that know nothing about block structure or index semantics. +`zaino-backend-lmdb` implements these traits with LMDB-specific configuration +(`NO_SYNC` for batch-boundary flushing, `NO_TLS` for cross-thread read transactions) +while the in-memory backend in `zaino-persistence` itself serves testing. + +The old block-parsing code spread across `zaino-state/src/chain_index/` used Zebra +types directly throughout the indexing pipeline. `zaino-convert-zebra` centralises +all Zebra-to-domain conversions in one place, producing `zaino-primitives` types that +the rest of the system consumes. The header-only conversion path +(`header_from_parts`) accepts pre-parsed header components from Zebra's +`ReadRequest::BlockHeader` response, avoiding full block deserialisation entirely -- +a critical optimisation for the 174k blocks/s throughput observed in header-only sync. + +--- + +## The Sync Pipeline + +A block's journey from source to persisted index entry passes through five stages, +each owned by a different component. + +The provisioner (currently `MockProvisioner` for tests, with real adapters pending) +fetches blocks from the source and produces a set-wide context value -- for instance +a `HeadersAndSpendsContext` containing the header fields and the list of transparent +spends extracted from the block's transactions. This context is the union of everything +any index in the set might need. + +The engine feeds contexts into the `BlockBuffer`, a sliding-window `BTreeMap>` +that holds blocks for the current and potentially next batch. Backpressure is implicit: +when the buffer reaches its capacity, the supply phase stalls until batch eviction +frees space. + +The `Scheduler` tracks per-index extraction progress and emits `Task` values. For each +block in the current batch, it produces `Task::Extract(ExtractJob)` entries specifying +which index should process which block offset. The engine dispatches these extractions +in parallel via Rayon's `par_iter`. Each extraction call goes through the +`IndexPipeline` trait-object interface: the engine calls `pipeline.extract_one(&ctx)`, +and the bridge inside projects the set-wide context down to the index's narrow context +type via `ProvideContext`, then calls the index's statically-typed `ExtractLocal::extract` +(or `ExtractCumulative::extract` for S-scope indexes). The delta is stored inside the +bridge's interior-mutable buffer. + +When all extractions for a batch complete, the scheduler emits +`Task::CompleteBatch(BatchHandle)`. The engine calls `pipeline.merge()` +on each index, which invokes the appropriate `MergeStrategy`: for Append indexes, the +deltas are simply collected; for Monoidal indexes, they are reduced via the declared +monoid; for Fold indexes, they are applied sequentially. The bridge then calls +`pipeline.persist()`, which invokes the index's `Schema` implementation to convert the +merged result into typed key-value entries, then encodes those entries into raw bytes +via `Schema::encode_key` and `Schema::encode_value`, producing a `Vec`. + +The engine collects `WriteOp` vectors from all indexes in the batch, appends a +watermark entry recording the committed height, and passes the combined batch to +`BackendWriter::commit()` in a single atomic transaction. After commit, it calls +`Backend::flush()` to force durability. The scheduler advances its watermark, the +`BlockBuffer` evicts consumed entries, and the loop continues with the next batch. + +For `SelfCumulative` indexes, the `CumulativeBridge` threads accumulated state across +blocks within a batch (via its internal `running_state` field) and across batches +(via `load_state` from the backend on resume). This ensures that an S-scope index +interrupted mid-sync can pick up exactly where it left off. + +--- + +## Index Definition Pattern + +Defining a new index requires four trait implementations and no concurrency, +scheduling, or persistence code. The `HeadersIndex` in `zaino-indexes` illustrates +the pattern. + +First, the index module declares a narrow context type containing only the data this +index needs. For `HeadersIndex`, that is `HeaderCtx` with five fields: height, hash, +prev_hash, time, and bits. This context is distinct from the set-wide context; the +set module provides the projection via `ProvideContext`. + +Second, the `IndexDef` implementation pins the index on the scope-composition grid. +`HeadersIndex` sets `type Scope = BlockLocal` and `type Composition = Append`, meaning +its extraction needs only the current block and its entries have disjoint keys. The +`Delta` associated type is `HeaderEntry` -- a struct carrying the height as key and a +`HeaderValue` (hash, prev_hash, time, bits) as value. The `const NAME: IndexId` +identifies the index for scheduling, persistence namespace mapping, and diagnostics. + +Third, the scope-specific extraction trait is implemented. Because `HeadersIndex` is +`BlockLocal`, it implements `ExtractLocal`, whose signature is +`fn extract(ctx: &HeaderCtx) -> Result`. The implementation +simply copies the context fields into a `HeaderEntry`. A `SelfCumulative` index would +instead implement `ExtractCumulative`, receiving its prior accumulated state as a +second parameter. A `CrossIndex` would implement `ExtractCross`, receiving a +`DepsReader` handle to query committed dependency state. The compiler prevents an +L-scope index from accessing prior state or dependency data -- the parameters are +simply not in the signature. + +Fourth, the `Schema` implementation defines the persistence encoding. For Append +indexes, the generic parameter is `Vec` (the collected deltas from the batch). +`Schema::into_entries` maps the batch into typed `(Key, Value)` pairs, and +`encode_key`/`encode_value` serialise them to bytes. The reverse path -- +`decode_key`, `decode_value`, and `from_entries` -- supports state loading for +cumulative indexes and future query serving. For `HeadersIndex`, the key is a +`BlockHeight` encoded as 8 little-endian bytes, and the value is a 72-byte fixed-width +encoding of the four header fields. + +The merge trait implementation is trivial for Append indexes: `impl MergeAppend for HeadersIndex {}` +is a marker with no methods. Monoidal indexes would implement `MergeMonoidal` with +`identity`, `lift`, and `combine`; Fold indexes would implement `MergeFold` with +`initial_state` and `fold`. + +Composing indexes into a set is a separate concern, handled in the `sets` module. +A set module defines the set-wide context type (the union of all per-index contexts), +implements `ProvideContext` for each index's narrow type, and exposes a builder +function: `IndexSet::new().with::().with::()`. +The same index definition can appear in multiple sets with different set-wide contexts; +only the `ProvideContext` projection differs. + +--- + +## Error Model + +Errors are stratified into three layers, each with a distinct audience and retry +semantic. + +At the boundary with external systems, `zaino-source` defines a three-tier error +hierarchy. `FetchError` represents a single transport-level failure (connection +refused, timeout, HTTP status, RPC error code, parse failure, auth rejection), carrying +a machine-readable `FailureMode` enum that the resilience wrapper matches on to decide +retryability. `QueryError` wraps either a domain rejection from the server (the +`Domain` variant, parameterised by the per-trait error type) or a `FetchError` -- +this is what adapter implementations return. `SourceError` is the consumer-facing +type from the resilience wrapper, adding an `Unavailable` variant for retries +exhausted. Domain errors are never retried; transport errors may be; unavailability +is terminal for the current operation. + +`zaino-persistence` takes a simpler, per-operation approach. `OpenError` covers +handle acquisition (backend closed, corrupted). `CommitError` covers write failures +(namespace not found, IO error, transaction conflict). `ReadError` covers read +failures. `FlushError` covers durability failures. Each is a small `thiserror` enum +with string payloads -- the backend adapter fills in implementation-specific detail. + +The sync engine composes both layers into `SyncError`, which wraps `DagError` (invalid +dependency graph), `PipelineError` (extraction or merge failure within an index +pipeline), and the four persistence error types via `#[from]` conversions. `PipelineError` +itself wraps `ExtractError` (from index extraction code) and adds `Merge` and `Persist` +variants for failures during those pipeline phases. This layering means that a +transport timeout in the provisioner, a parse error in an index's extraction function, +a namespace-not-found in the LMDB backend, and a cycle in the dependency DAG all +surface as distinct, pattern-matchable variants of `SyncError` without losing their +original context. + +--- + +## Benchmark Results + +Three benchmark configurations have been run against Zcash mainnet data, each +exercising a different combination of source adapter and index set. + +The headers-only configuration using `ZebraReadStateAdapter` achieved 174,000 +blocks per second. This configuration uses `header_from_parts` in +`zaino-convert-zebra`, which accepts Zebra's pre-parsed `Header` struct and height +without deserialising the block body at all. The index set contains only +`HeadersIndex` (BlockLocal, Append), so extraction is embarrassingly parallel with +no merge overhead. This number represents the ceiling for the sync engine's +scheduling and persistence machinery when extraction cost is near zero. + +The headers-plus-transparent-spends configuration using `ZebraReadStateAdapter` +achieved 539 blocks per second. Adding `TransparentSpendsIndex` forces full block +deserialisation through `block_from_zebra`, which parses every transaction's +transparent inputs, sapling nullifiers, and orchard actions. The three-orders-of-magnitude +drop from the headers-only case is almost entirely attributable to block +deserialisation cost (particularly the `sandblast` region of the chain where +individual blocks contain thousands of shielded outputs) rather than sync engine +overhead. + +The RPC-based configuration using `ZebraRpcAdapter` with the headers-and-spends +index set achieved 323 blocks per second on the sandblast region. The additional +overhead versus the ReadState path comes from HTTP round-trips, hex encoding of +block bytes in JSON-RPC responses, and JSON parsing. For the pre-sandblast region +where blocks are small, RPC throughput is substantially higher, but the sandblast +blocks (which dominate total sync time) are the meaningful benchmark. + +--- + +## What's Not Yet Implemented + +Several components described in the design document remain as stubs or placeholders. + +CrossIndex bridges are structurally present but non-functional. The `DepsReader` +type exists but has no methods; `ExtractCross` compiles but cannot provide dependency +data to an extractor. The scheduler's `Barrier` firing rule is declared but always +blocks. No real Zcash index currently requires X-scope, so this is deferred until +one does. + +The `SourceHandle` and `NonLocalSource` escape hatches are placeholder structs. These +would allow an index to declare `SourceAccess::NonLocal` and receive a handle for +fetching data about blocks other than the one being extracted -- useful for cases where +adding an intermediate dependency index is disproportionate. The engine would adjust +scheduling for I/O latency when this is declared. + +The streaming provisioner interface remains synchronous. The `Provisioner` trait's +`provision_range` method returns `Vec`, suitable for tests and small +ranges. The `sync_channel` entry point on the engine accepts a `tokio::mpsc::Receiver` +and is the intended production path (provisioner on a separate task, feeding blocks +through a channel with backpressure), but no real provisioner implementation wires +this up yet. + +The real Zcash index set is incomplete. Of the roughly ten indexes needed for full +Lightwalletd-compatible serving (headers, block heights, txid location, transparent +inputs/outputs, sapling spends/outputs, orchard actions, commitment trees, spent +markers, address history), only `HeadersIndex` and `TransparentSpendsIndex` have +implementations. The remaining indexes follow the same pattern and are +straightforward to add. + +Reorg handling is not addressed. The engine covers initial sync (genesis to tip); +steady-state tip-following with rollbacks has different characteristics (batch size +of 1, undo logic that depends on composition type). The formal model notes this as +future work. + +The `zainod` binary has not been wired to use the new sync engine. The integration +point would be an `IndexSet` registration at startup, a provisioner backed by the +configured source adapter, and an `LmdbBackend` backed by the configured database +path, all composed into a `SyncEngine` that replaces the current monolithic sync loop. diff --git a/docs/sync-engine/user-stories.md b/docs/sync-engine/user-stories.md new file mode 100644 index 000000000..ad7576f51 --- /dev/null +++ b/docs/sync-engine/user-stories.md @@ -0,0 +1,130 @@ +# Sync Engine User Stories + +Living document. Updated as development uncovers modifications or new stories. + +Layered top-down by actor. Each layer is a consumer-provider boundary: the +story states what the consumer wants, and what the provider must never have +to know. + +Last updated: 2026-07-09 + +--- + +## Level 0 — Application Builder (composes index sets) + +The person building an application on top of Zaino. Picks which indexes to +run, which source to connect, and which storage to use. + +> **US-0.1** As an app builder, I want to assemble an index set by picking +> indexes (`IndexSet::new().with::().with::()...`), +> so that a light-wallet server, a block explorer, and an analytics job each +> run only the indexes they need — paying storage and sync cost only for those. + +> **US-0.2** As an app builder, I want to plug in my own source (provisioner) +> and my own storage (backend) behind traits, so that the same index set runs +> against Zebra ReadState, JSON-RPC, or a test fixture, and persists to LMDB +> or in-memory, without touching index code. + +> **US-0.3** As an app builder, I want registration to fail fast (cycle in +> deps, missing dependency, duplicate name) at build time, so misconfigured +> sets never start syncing. + +--- + +## Level 1 — Index-Set Implementor (e.g., the Zaino Zcash set) + +The person who owns *the* concrete index set for a blockchain. Wants to +define domain indexes and offload all orchestration to the engine. + +> **US-1.1** As an index-set implementor, I want to declare *what* each index +> computes (scope, composition, deps, extract, schema) and get scheduling, +> parallelism, merging, atomic persistence, and crash-resume *for free*, so +> that I never write concurrency or transaction code. + +> **US-1.2** As an index-set implementor, I want the set to commit as one unit +> per batch — all indexes' writes plus the watermark atomically — so that on +> restart the whole set resumes from one height with no per-index skew visible +> to readers. + +> **US-1.3** As an index-set implementor, I want to define one set-wide block +> context and have each index receive its own projection of it +> (`ProvideContext`), so that adding an index that needs extra source data +> widens the context in one place, not in every index. + +--- + +## Level 2 — Index Implementor (writes one index) + +The person adding a single index to an existing set. Should never think +about scheduling, parallelism, transactions, or other indexes. + +> **US-2.1** As an index implementor, I want to state my index's position on +> the Scope x Composition grid in the type system, so the compiler rejects an +> extract signature that reads data my scope doesn't grant (an L-scope index +> simply cannot ask for prior state or dep reads). + +> **US-2.2** As an L-scope implementor, I want to write a pure per-block +> function `extract(ctx) -> Delta` and a merge algebra (or none, for Append), +> so the engine can parallelize across blocks without me knowing. + +> **US-2.3** As an S-scope implementor, I want my prior accumulated state +> handed to me — loaded from the backend on resume, threaded across blocks and +> batches by the engine — so I never manage checkpointing myself. + +> **US-2.4** As an X-scope implementor, I want a read handle restricted to +> *committed* state of my *declared* dependencies, so I can't accidentally +> read uncommitted or undeclared data. *(designed, not yet implemented)* + +> **US-2.5** As an index implementor, I want to describe persistence as typed +> `(Key, Value)` entries (`Schema` + `Encode`), so serialization and schema +> versioning are separate from extraction logic. + +--- + +## Level 3 — Intermediate Provider-Consumer Contracts + +The boundaries between engine internals. Each story names both sides of the +contract. + +> **US-3.1 (Engine <- Provisioner)** As the engine, I want an ordered stream +> of block contexts with backpressure from my bounded buffer, so a slow disk +> stalls fetching instead of ballooning memory; as the provisioner, I want to +> know nothing about indexes — I fill a buffer. + +> **US-3.2 (Scheduler <- Engine)** As the engine loop, I want the scheduler +> to hand me only work that is safe to run right now (block available AND +> firing rules satisfied), so workers can execute blindly; as the scheduler, +> I want state transitions type-checked (`BatchHandle` -> +> ``), so out-of-order reporting can't compile. + +> **US-3.3 (Engine <- Backend)** As the engine, I want a single atomic +> `commit(Vec)` per batch plus a flush/durability boundary, and to +> know the writer topology (shared vs per-index), so I can order writes +> correctly for the store's concurrency model. + +> **US-3.4 (Downstream index <- Upstream index)** As a downstream index, I +> want the upstream's commit of batch N to be my firing signal for batch N +> (Pipelined) or its full completion (Barrier), so cross-index reads are +> always over consistent committed state. *(Barrier stubbed)* + +--- + +## Level 4 — Operator + +The person deploying and monitoring Zaino. + +> **US-4.1** As an operator, I want a persisted watermark and clean resume — +> kill the process at any point, restart, and it continues from the last +> committed batch with no manual intervention. + +> **US-4.2** As an operator, I want sync progress observable (feature-gated +> tracing/metrics, non-default per privacy policy), so I can tell "syncing" +> from "stalled" from "done". + +--- + +## Changelog + +| Date | Change | +|------|--------| +| 2026-07-09 | Initial draft. 14 stories across 5 levels. | diff --git a/integration-tests/Cargo.lock b/integration-tests/Cargo.lock new file mode 100644 index 000000000..7ea9b9fb3 --- /dev/null +++ b/integration-tests/Cargo.lock @@ -0,0 +1,6225 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[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-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "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", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bellman" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afceed28bac7f9f5a508bca8aeeff51cdfa4770c0b967ac55c621e2ddfd6171" +dependencies = [ + "bitvec", + "blake2s_simd", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core 0.6.4", + "rayon", + "subtle", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.117", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex", + "syn 2.0.117", +] + +[[package]] +name = "bip0039" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568b6890865156d9043af490d4c4081c385dd68ea10acd6ca15733d511e6b51c" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "rand 0.8.6", + "sha2 0.10.9", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bip32" +version = "0.6.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143f5327f23168716be068f8e1014ba2ea16a6c91e8777bc8927da7b51e1df1f" +dependencies = [ + "bs58", + "hmac 0.13.0-pre.4", + "rand_core 0.6.4", + "ripemd 0.2.0-pre.4", + "secp256k1", + "sha2 0.11.0-pre.4", + "subtle", + "zeroize", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitflags-serde-legacy" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b64e60c28b6d25ad92e8b367801ff9aa12b41d05fc8798055d296bace4a60cc" +dependencies = [ + "bitflags 2.11.1", + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bounded-vec" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dc0086e469182132244e9b8d313a0742e1132da43a08c24b9dd3c18e0faf3a" +dependencies = [ + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2 0.10.9", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "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 = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "config" +version = "0.15.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68cfe19cd7d23ffde002c24ffa5cda73931913ef394d5eaaa32037dc940c0c" +dependencies = [ + "pathdiff", + "serde_core", + "toml 1.1.2+spec-1.1.0", + "winnow", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "corez" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df6f98652d30167eaeea34d77b730e07c8caba6df17bd4551842b9b8da01deb" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.0-pre.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" +dependencies = [ + "block-buffer 0.11.0-rc.3", + "crypto-common 0.2.0-rc.1", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "documented" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6b3e31251e87acd1b74911aed84071c8364fc9087972748ade2f1094ccce34" +dependencies = [ + "documented-macros", + "phf", + "thiserror 2.0.18", +] + +[[package]] +name = "documented-macros" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1149cf7462e5e79e17a3c05fd5b1f9055092bbfa95e04c319395c3beacc9370f" +dependencies = [ + "convert_case", + "itertools 0.14.0", + "optfield", + "proc-macro2", + "quote", + "strum", + "syn 2.0.117", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775765289f7c6336c18d3d66127527820dd45ffd9eb3b6b8ee4708590e6c20f5" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.16.1", + "pkcs8", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equihash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306286e8dcc39ab3dfceb74c792ce8baffdab90591321d3ffaae64829734c37f" +dependencies = [ + "blake2b_simd", + "corez", + "document-features", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "f4jumble" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d42773cb15447644d170be20231a3268600e0c4cea8987d013b93ac973d3cf7" +dependencies = [ + "blake2b_simd", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.6", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fpe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" +dependencies = [ + "cbc", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +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 = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[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 0.11.1+wasi-snapshot-preview1", + "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 = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "memuse", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "halo2_gadgets" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2a697cad929f706b7987fe804ad57d43622cd37463ba7e4d662a926fdcfea3" +dependencies = [ + "arrayvec", + "bitvec", + "ff", + "group", + "halo2_poseidon", + "halo2_proofs", + "lazy_static", + "pasta_curves", + "rand 0.8.6", + "sinsemilla", + "subtle", + "uint 0.9.5", +] + +[[package]] +name = "halo2_legacy_pdqsort" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47716fe1ae67969c5e0b2ef826f32db8c3be72be325e1aa3c1951d06b5575ec5" + +[[package]] +name = "halo2_poseidon" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa3da60b81f02f9b33ebc6252d766f843291fb4d2247a07ae73d20b791fc56f" +dependencies = [ + "bitvec", + "ff", + "group", + "pasta_curves", +] + +[[package]] +name = "halo2_proofs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2_legacy_pdqsort", + "indexmap 1.9.3", + "maybe-rayon", + "pasta_curves", + "rand_core 0.6.4", + "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" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "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 = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[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" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58" +dependencies = [ + "digest 0.11.0-pre.9", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "human_bytes" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hybrid-array" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[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", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "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", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[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-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "incrementalmerkletree" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30821f91f0fa8660edca547918dc59812893b497d07c1144f326f07fdd94aba9" +dependencies = [ + "either", +] + +[[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", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +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.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +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 = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "jsonrpsee" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e281ae70cc3b98dac15fced3366a880949e65fc66e345ce857a5682d152f3e62" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348ee569eaed52926b5e740aae20863762b16596476e943c9e415a6479021622" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "jsonrpsee-types", + "parking_lot", + "rand 0.8.6", + "rustc-hash 2.1.2", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7398cddf5013cca4702862a2692b66c48a3bd6cf6ec681a47453c93d63cf8de5" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21429bcdda37dcf2d43b68621b994adede0e28061f816b038b0f18c70c143d51" +dependencies = [ + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f05e0028e55b15dbd2107163b3c744cd3bb4474f193f95d9708acbf5677e44" +dependencies = [ + "http", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "known-folders" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[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.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags 2.11.1", + "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[package]] +name = "librocksdb-sys" +version = "0.16.0+8.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" +dependencies = [ + "bindgen 0.69.5", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libzcash_script" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8ce05b56f3cbc65ec7d0908adb308ed91281e022f61c8c3a0c9388b5380b17" +dependencies = [ + "bindgen 0.72.1", + "cc", + "thiserror 2.0.18", + "tracing", + "zcash_script", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lmdb" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0908efb5d6496aa977d96f91413da2635a902e5e31dbef0bfb88986c248539" +dependencies = [ + "bitflags 1.3.2", + "libc", + "lmdb-sys", +] + +[[package]] +name = "lmdb-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b392838cfe8858e86fac37cf97a0e8c55cc60ba0a18365cadc33092f128ce9" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[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-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "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.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[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", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "mset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4d16a3d2b0e89ec6e7d509cf791545fcb48cbc8fc2fb2e96a492defda9140" + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[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 = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" + +[[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-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-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-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 = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[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 = "openrpsee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faeb689cfe5fad5e7285f87b00c903366b307d97f41de53e894ec608968ca3a1" +dependencies = [ + "documented", + "jsonrpsee", + "quote", + "schemars 1.2.1", + "serde", + "serde_json", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "optfield" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orchard" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54f8d29bfb1e76a9d4e868a1a08cce2e57dd2bdc66232982822ad3114b91ab3" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "corez", + "ff", + "fpe", + "getset", + "group", + "halo2_gadgets", + "halo2_poseidon", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.6", + "rand_core 0.6.4", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "ordered-map" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac8f4a4a06c811aa24b151dbb3fe19f687cb52e0d5cca0493671ed88f973970" +dependencies = [ + "quickcheck", + "quickcheck_macros", +] + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +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 0.5.18", + "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 = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.6", + "static_assertions", + "subtle", +] + +[[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 0.10.7", + "password-hash", +] + +[[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 = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713258393a82f091ead52047ca779d37e5766226d009de21696c4e667044368" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +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 = "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 = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "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 = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand 0.8.6", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +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 = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec 0.6.0", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.1", + "uint 0.10.0", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[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.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.117", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +dependencies = [ + "bitflags 2.11.1", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quickcheck" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +dependencies = [ + "env_logger", + "log", + "rand 0.7.3", + "rand_core 0.5.1", +] + +[[package]] +name = "quickcheck_macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 2.1.2", + "rustls", + "socket2", + "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.4", + "ring", + "rustc-hash 2.1.2", + "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", + "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 = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[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 = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[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 = "reddsa" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a5191930e84973293aa5f532b513404460cd2216c1cfb76d08748c15b40b02" +dependencies = [ + "blake2b_simd", + "byteorder", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core 0.6.4", + "serde", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "redjubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b0ac1bc6bb3696d2c6f52cff8fba57238b81da8c0214ee6cd146eb8fde364e" +dependencies = [ + "rand_core 0.6.4", + "reddsa", + "serde", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +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", + "bytes", + "cookie", + "cookie_store", + "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 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[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 = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ripemd" +version = "0.2.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48cf93482ea998ad1302c42739bc73ab3adc574890c373ec89710e219357579" +dependencies = [ + "digest 0.11.0-pre.9", +] + +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + +[[package]] +name = "rocksdb" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + +[[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 = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "sapling-crypto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d70756ede56b5e4dd417979777bd87ddb83dfcbd0815dbf8175a9920537f8a0" +dependencies = [ + "aes", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381", + "corez", + "document-features", + "ff", + "fpe", + "getset", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "rand 0.8.6", + "rand_core 0.6.4", + "redjubjub", + "subtle", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-pre.9", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simple-mermaid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589144a964b4b30fe3a83b4bb1a09e2475aac194ec832a046a23e75bddf9eb29" + +[[package]] +name = "sinsemilla" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d268ae0ea06faafe1662e9967cd4f9022014f5eeb798e0c302c876df8b7af9c" +dependencies = [ + "group", + "pasta_curves", + "subtle", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "soketto" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +dependencies = [ + "base64", + "bytes", + "futures", + "http", + "httparse", + "log", + "rand 0.8.6", + "sha1", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +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" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +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 = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[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.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[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", +] + +[[package]] +name = "tonic" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "rustls-native-certs", + "socket2", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tonic-prost" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.117", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tonic-reflection" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf0685a51e6d02b502ba0764002e766b7f3042aed13d9234925b6ffbfa3fca7" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "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" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-batch-control" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6cf52578f98b4da47335c26c4f883f7993b1a9b9d2f5420eb8dbfd5dd19a28" +dependencies = [ + "futures", + "futures-core", + "pin-project", + "rayon", + "tokio", + "tokio-util", + "tower 0.4.13", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower-fallback" +version = "0.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434e19ee996ee5c6aa42f11463a355138452592e5c5b5b73b6f0f19534556af" +dependencies = [ + "futures-core", + "pin-project", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-tree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac87aa03b6a4d5a7e4810d1a80c19601dbe0f8a837e9177f23af721c7ba7beec" +dependencies = [ + "nu-ansi-term", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wagyu-zcash-parameters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb" +dependencies = [ + "wagyu-zcash-parameters-1", + "wagyu-zcash-parameters-2", + "wagyu-zcash-parameters-3", + "wagyu-zcash-parameters-4", + "wagyu-zcash-parameters-5", + "wagyu-zcash-parameters-6", +] + +[[package]] +name = "wagyu-zcash-parameters-1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8" + +[[package]] +name = "wagyu-zcash-parameters-2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c" + +[[package]] +name = "wagyu-zcash-parameters-3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147" + +[[package]] +name = "wagyu-zcash-parameters-4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6" + +[[package]] +name = "wagyu-zcash-parameters-5" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d" + +[[package]] +name = "wagyu-zcash-parameters-6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" + +[[package]] +name = "walletless-tests" +version = "0.2.0" +dependencies = [ + "anyhow", + "corez", + "futures", + "hex", + "serde_json", + "tempfile", + "tokio", + "tower 0.4.13", + "tracing", + "wire_serialized_transaction_test_data", + "zaino-common", + "zaino-fetch", + "zaino-proto", + "zaino-state", + "zaino-testutils", + "zainod", + "zcash_local_net", + "zebra-chain", + "zebra-rpc", + "zebra-state", + "zip32", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "which" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" +dependencies = [ + "libc", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wire_serialized_transaction_test_data" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebb4f5d78d0832cc4a1760a20288470c6fafa02f18e555a7ece133cd8cef5ca" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "xdg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zaino-common" +version = "0.2.0" +dependencies = [ + "hex", + "nu-ansi-term", + "serde", + "thiserror 1.0.69", + "time", + "tracing", + "tracing-subscriber", + "tracing-tree", + "zebra-chain", + "zingo_common_components", +] + +[[package]] +name = "zaino-fetch" +version = "0.2.0" +dependencies = [ + "base64", + "byteorder", + "derive_more", + "hex", + "http", + "indexmap 2.14.0", + "jsonrpsee-types", + "prost", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 1.0.69", + "tokio", + "tonic", + "tracing", + "url", + "zaino-common", + "zaino-proto", + "zebra-chain", + "zebra-rpc", +] + +[[package]] +name = "zaino-proto" +version = "0.1.3" +dependencies = [ + "prost", + "tonic", + "tonic-prost", + "tonic-prost-build", + "which 4.4.2", + "zebra-chain", + "zebra-state", +] + +[[package]] +name = "zaino-serve" +version = "0.3.0" +dependencies = [ + "futures", + "jsonrpsee", + "serde", + "thiserror 1.0.69", + "tokio", + "tonic", + "tower 0.4.13", + "tracing", + "zaino-fetch", + "zaino-proto", + "zaino-state", + "zebra-chain", + "zebra-rpc", +] + +[[package]] +name = "zaino-state" +version = "0.3.0" +dependencies = [ + "arc-swap", + "async-trait", + "bitflags 2.11.1", + "blake2", + "bs58", + "chrono", + "corez", + "dashmap", + "derive_more", + "futures", + "hex", + "incrementalmerkletree", + "indexmap 2.14.0", + "lmdb", + "lmdb-sys", + "nonempty", + "primitive-types 0.13.1", + "prost", + "reqwest", + "sapling-crypto", + "serde", + "serde_json", + "sha2 0.10.9", + "simple-mermaid", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tower 0.4.13", + "tracing", + "whoami", + "zaino-common", + "zaino-fetch", + "zaino-proto", + "zcash_address", + "zcash_keys", + "zcash_primitives", + "zcash_protocol", + "zcash_transparent", + "zebra-chain", + "zebra-rpc", + "zebra-state", +] + +[[package]] +name = "zaino-testutils" +version = "0.2.0" +dependencies = [ + "futures", + "http", + "once_cell", + "portpicker", + "tokio", + "tonic", + "tracing", + "zaino-common", + "zaino-fetch", + "zaino-proto", + "zaino-serve", + "zaino-state", + "zainod", + "zcash_local_net", + "zcash_protocol", + "zebra-chain", + "zebra-rpc", + "zebra-state", +] + +[[package]] +name = "zainod" +version = "0.4.1" +dependencies = [ + "clap", + "config", + "http", + "serde", + "tempfile", + "thiserror 1.0.69", + "tokio", + "toml 0.5.11", + "tracing", + "tracing-subscriber", + "zaino-common", + "zaino-fetch", + "zaino-serve", + "zaino-state", + "zebra-chain", + "zebra-state", +] + +[[package]] +name = "zcash_address" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58342d0aaa8e2fa98849636f52800ac4bf020574c944c974742fc933db58cac2" +dependencies = [ + "bech32", + "bs58", + "corez", + "f4jumble", + "zcash_encoding", + "zcash_protocol", +] + +[[package]] +name = "zcash_encoding" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1440921903cdb86133fb9e2fe800be488015db2939a30bedb413078a1acb0306" +dependencies = [ + "corez", + "hex", + "nonempty", +] + +[[package]] +name = "zcash_history" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fde17bf53792f9c756b313730da14880257d7661b5bfc69d0571c3a7c11a76d" +dependencies = [ + "blake2b_simd", + "byteorder", + "primitive-types 0.12.2", +] + +[[package]] +name = "zcash_keys" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbcdfbb5c8edb247439d72a397abaae9b7dd14a1c070e7e4fc3536924f9065f" +dependencies = [ + "bech32", + "blake2b_simd", + "bls12_381", + "bs58", + "corez", + "document-features", + "group", + "memuse", + "nonempty", + "orchard", + "rand_core 0.6.4", + "sapling-crypto", + "secrecy", + "subtle", + "tracing", + "zcash_address", + "zcash_encoding", + "zcash_protocol", + "zcash_transparent", + "zip32", +] + +[[package]] +name = "zcash_local_net" +version = "0.6.0" +source = "git+https://github.com/zingolabs/infrastructure.git?rev=0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083#0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083" +dependencies = [ + "getset", + "hex", + "json", + "reqwest", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", + "zcash_protocol", + "zebra-chain", + "zebra-node-services", + "zebra-rpc", + "zingo_common_components", + "zingo_test_vectors", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77efec759c3798b6e4d829fcc762070d9b229b0f13338c40bf993b7b609c2272" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c69e07f5eb3f682a6467b4b08ee4956f1acd1e886d70b21c4766953b3a1beba2" +dependencies = [ + "blake2b_simd", + "block-buffer 0.11.0-rc.3", + "corez", + "crypto-common 0.2.0-rc.1", + "document-features", + "equihash", + "ff", + "hex", + "incrementalmerkletree", + "jubjub", + "memuse", + "nonempty", + "orchard", + "rand_core 0.6.4", + "redjubjub", + "sapling-crypto", + "secp256k1", + "sha2 0.10.9", + "zcash_encoding", + "zcash_note_encryption", + "zcash_protocol", + "zcash_script", + "zcash_transparent", +] + +[[package]] +name = "zcash_proofs" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de6b0ca82e08a9d38b1121f87c5b180b5feac19fecba074cb582882210d2371" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381", + "document-features", + "group", + "home", + "jubjub", + "known-folders", + "rand_core 0.6.4", + "redjubjub", + "sapling-crypto", + "tracing", + "wagyu-zcash-parameters", + "xdg", + "zcash_primitives", +] + +[[package]] +name = "zcash_protocol" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bec496a0bd62dae98c4b26f51c5dab112d0c5350bbc2ccfdfd05bb3454f714d" +dependencies = [ + "corez", + "document-features", + "hex", + "memuse", + "zcash_encoding", +] + +[[package]] +name = "zcash_script" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f872800287d118be71bdf6fe8c869c6a6ff6fb0a5762f68fb2af54c97edf0f2" +dependencies = [ + "bip32", + "bitflags 2.11.1", + "bounded-vec", + "hex", + "ripemd 0.1.3", + "secp256k1", + "sha1", + "sha2 0.10.9", + "thiserror 2.0.18", +] + +[[package]] +name = "zcash_spec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" +dependencies = [ + "blake2b_simd", +] + +[[package]] +name = "zcash_transparent" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15df1908b428d4edeb7c7caae5692e05e2e92e5c38007a40b20ac098efdffd96" +dependencies = [ + "bip32", + "bs58", + "corez", + "document-features", + "getset", + "hex", + "nonempty", + "ripemd 0.1.3", + "secp256k1", + "sha2 0.10.9", + "subtle", + "zcash_address", + "zcash_encoding", + "zcash_protocol", + "zcash_script", + "zcash_spec", + "zip32", +] + +[[package]] +name = "zebra-chain" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed3a4e00e8f0b2197f3d8fd6cad02351a25ddbffcf0a6dc1fe463d7ea4ccfa" +dependencies = [ + "bech32", + "bitflags 2.11.1", + "bitflags-serde-legacy", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bounded-vec", + "bs58", + "byteorder", + "chrono", + "derive-getters", + "dirs", + "ed25519-zebra", + "equihash", + "futures", + "group", + "halo2_proofs", + "hex", + "humantime", + "incrementalmerkletree", + "itertools 0.14.0", + "jubjub", + "lazy_static", + "num-integer", + "orchard", + "primitive-types 0.12.2", + "rand_core 0.6.4", + "rayon", + "reddsa", + "redjubjub", + "ripemd 0.1.3", + "sapling-crypto", + "schemars 1.2.1", + "secp256k1", + "serde", + "serde-big-array", + "serde_json", + "serde_with", + "sha2 0.10.9", + "sinsemilla", + "static_assertions", + "strum", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", + "uint 0.10.0", + "x25519-dalek", + "zcash_address", + "zcash_encoding", + "zcash_history", + "zcash_note_encryption", + "zcash_primitives", + "zcash_protocol", + "zcash_script", + "zcash_transparent", +] + +[[package]] +name = "zebra-consensus" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15475adf8c271d03de99d18d622a8ae4c512c401e3e29da1f27a0ba62b22057c" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381", + "chrono", + "derive-getters", + "futures", + "futures-util", + "halo2_proofs", + "jubjub", + "lazy_static", + "libzcash_script", + "metrics", + "mset", + "once_cell", + "orchard", + "rand 0.8.6", + "rayon", + "sapling-crypto", + "serde", + "thiserror 2.0.18", + "tokio", + "tower 0.4.13", + "tower-batch-control", + "tower-fallback", + "tracing", + "tracing-futures", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", + "zcash_script", + "zcash_transparent", + "zebra-chain", + "zebra-node-services", + "zebra-script", + "zebra-state", +] + +[[package]] +name = "zebra-network" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ba4f7dcd795eb84a5a33f5c4f29df60dd4971be363d2cd98d5fba5ce0477f2" +dependencies = [ + "bitflags 2.11.1", + "byteorder", + "bytes", + "chrono", + "dirs", + "futures", + "hex", + "humantime-serde", + "indexmap 2.14.0", + "itertools 0.14.0", + "lazy_static", + "metrics", + "num-integer", + "ordered-map", + "pin-project", + "rand 0.8.6", + "rayon", + "regex", + "schemars 1.2.1", + "serde", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.4.13", + "tracing", + "tracing-error", + "tracing-futures", + "zebra-chain", +] + +[[package]] +name = "zebra-node-services" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abeece0fb4503a1df00c5ab736754b55c702613968f622f6aa3c2f9842aed2b2" +dependencies = [ + "color-eyre", + "jsonrpsee-types", + "reqwest", + "serde", + "serde_json", + "tokio", + "tower 0.4.13", + "zebra-chain", +] + +[[package]] +name = "zebra-rpc" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b460869e352c9f1b49a00ed9beaae04ca3e6498381c7189d4b90a79e51c260bd" +dependencies = [ + "base64", + "chrono", + "color-eyre", + "derive-getters", + "derive-new", + "futures", + "hex", + "http-body-util", + "hyper", + "indexmap 2.14.0", + "jsonrpsee", + "jsonrpsee-proc-macros", + "jsonrpsee-types", + "lazy_static", + "metrics", + "nix", + "openrpsee", + "orchard", + "phf", + "prost", + "rand 0.8.6", + "sapling-crypto", + "schemars 1.2.1", + "semver", + "serde", + "serde_json", + "serde_with", + "strum", + "strum_macros", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tonic-reflection", + "tower 0.4.13", + "tracing", + "which 8.0.2", + "zcash_address", + "zcash_keys", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", + "zcash_script", + "zcash_transparent", + "zebra-chain", + "zebra-consensus", + "zebra-network", + "zebra-node-services", + "zebra-script", + "zebra-state", +] + +[[package]] +name = "zebra-script" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fc38c19388671df8a77c767e279b135ffaecd5e7df6ed7cd096390c080b4a3" +dependencies = [ + "libzcash_script", + "rand 0.8.6", + "thiserror 2.0.18", + "zcash_primitives", + "zcash_script", + "zebra-chain", +] + +[[package]] +name = "zebra-state" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b351fde5047dce0d505c9dc26863ee818b7579e9cf8d8e44489deb9decfbb7" +dependencies = [ + "bincode", + "chrono", + "crossbeam-channel", + "derive-getters", + "derive-new", + "dirs", + "futures", + "hex", + "hex-literal", + "human_bytes", + "humantime-serde", + "indexmap 2.14.0", + "itertools 0.14.0", + "lazy_static", + "metrics", + "mset", + "rayon", + "regex", + "rlimit", + "rocksdb", + "sapling-crypto", + "semver", + "serde", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tower 0.4.13", + "tracing", + "zebra-chain", + "zebra-node-services", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zingo_common_components" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3378e81bdef45a9659736a09deaef2b5e5cb3d1556031468debfca3a21d4fa76" +dependencies = [ + "hex", +] + +[[package]] +name = "zingo_test_vectors" +version = "0.0.1" +source = "git+https://github.com/zingolabs/infrastructure.git?rev=0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083#0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083" +dependencies = [ + "bip0039", +] + +[[package]] +name = "zip32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64bf5186a8916f7a48f2a98ef599bf9c099e2458b36b819e393db1c0e768c4b" +dependencies = [ + "bech32", + "blake2b_simd", + "memuse", + "subtle", + "zcash_spec", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/integration-tests/wallet-tests/Cargo.lock b/integration-tests/wallet-tests/Cargo.lock new file mode 100644 index 000000000..4e11086f1 --- /dev/null +++ b/integration-tests/wallet-tests/Cargo.lock @@ -0,0 +1,6317 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[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-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "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", + "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.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bellman" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afceed28bac7f9f5a508bca8aeeff51cdfa4770c0b967ac55c621e2ddfd6171" +dependencies = [ + "bitvec", + "blake2s_simd", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core 0.6.4", + "rayon", + "subtle", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.13.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn 2.0.117", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex 1.3.0", + "syn 2.0.117", +] + +[[package]] +name = "bip0039" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568b6890865156d9043af490d4c4081c385dd68ea10acd6ca15733d511e6b51c" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "rand 0.8.6", + "sha2 0.10.9", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bip32" +version = "0.6.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143f5327f23168716be068f8e1014ba2ea16a6c91e8777bc8927da7b51e1df1f" +dependencies = [ + "bs58", + "hmac 0.13.0-pre.4", + "rand_core 0.6.4", + "ripemd 0.2.0-pre.4", + "secp256k1", + "sha2 0.11.0-pre.4", + "subtle", + "zeroize", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bitflags-serde-legacy" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b64e60c28b6d25ad92e8b367801ff9aa12b41d05fc8798055d296bace4a60cc" +dependencies = [ + "bitflags 2.13.0", + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bounded-vec" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dc0086e469182132244e9b8d313a0742e1132da43a08c24b9dd3c18e0faf3a" +dependencies = [ + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2 0.10.9", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[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 = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "config" +version = "0.15.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f316c6237b2d38be61949ecd15268a4c6ca32570079394a2444d9ce2c72a72d8" +dependencies = [ + "pathdiff", + "serde_core", + "toml 1.1.2+spec-1.1.0", + "winnow", +] + +[[package]] +name = "const-crc32-nostd" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "corez" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df6f98652d30167eaeea34d77b730e07c8caba6df17bd4551842b9b8da01deb" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.0-pre.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" +dependencies = [ + "block-buffer 0.11.0-rc.3", + "crypto-common 0.2.0-rc.1", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "documented" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6b3e31251e87acd1b74911aed84071c8364fc9087972748ade2f1094ccce34" +dependencies = [ + "documented-macros", + "phf", + "thiserror 2.0.18", +] + +[[package]] +name = "documented-macros" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1149cf7462e5e79e17a3c05fd5b1f9055092bbfa95e04c319395c3beacc9370f" +dependencies = [ + "convert_case", + "itertools 0.14.0", + "optfield", + "proc-macro2", + "quote", + "strum", + "syn 2.0.117", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775765289f7c6336c18d3d66127527820dd45ffd9eb3b6b8ee4708590e6c20f5" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.16.1", + "pkcs8", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equihash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306286e8dcc39ab3dfceb74c792ce8baffdab90591321d3ffaae64829734c37f" +dependencies = [ + "blake2b_simd", + "corez", + "document-features", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "f4jumble" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d42773cb15447644d170be20231a3268600e0c4cea8987d013b93ac973d3cf7" +dependencies = [ + "blake2b_simd", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.6", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fpe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" +dependencies = [ + "cbc", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "frost-core" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ef2787af391c7e8bedc037a3b9ea03dde803fbd93e778e6bb369547800e5cd" +dependencies = [ + "byteorder", + "const-crc32-nostd", + "derive-getters", + "document-features", + "hex", + "itertools 0.14.0", + "postcard", + "rand_core 0.6.4", + "serde", + "serdect", + "thiserror 2.0.18", + "visibility", + "zeroize", + "zeroize_derive", +] + +[[package]] +name = "frost-rerandomized" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4c5cedd2426728adef2c0b1720f57676354c473836d1ccc50d0f0d1c91942b" +dependencies = [ + "derive-getters", + "document-features", + "frost-core", + "hex", + "rand_core 0.6.4", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +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 = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[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 0.11.1+wasi-snapshot-preview1", + "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 = "getset" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "memuse", + "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.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "halo2_gadgets" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2a697cad929f706b7987fe804ad57d43622cd37463ba7e4d662a926fdcfea3" +dependencies = [ + "arrayvec", + "bitvec", + "ff", + "group", + "halo2_poseidon", + "halo2_proofs", + "lazy_static", + "pasta_curves", + "rand 0.8.6", + "sinsemilla", + "subtle", + "uint 0.9.5", +] + +[[package]] +name = "halo2_legacy_pdqsort" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47716fe1ae67969c5e0b2ef826f32db8c3be72be325e1aa3c1951d06b5575ec5" + +[[package]] +name = "halo2_poseidon" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa3da60b81f02f9b33ebc6252d766f843291fb4d2247a07ae73d20b791fc56f" +dependencies = [ + "bitvec", + "ff", + "group", + "pasta_curves", +] + +[[package]] +name = "halo2_proofs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2_legacy_pdqsort", + "indexmap 1.9.3", + "maybe-rayon", + "pasta_curves", + "rand_core 0.6.4", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "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 = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[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" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58" +dependencies = [ + "digest 0.11.0-pre.9", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +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 = "human_bytes" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hybrid-array" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[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", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "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", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "incrementalmerkletree" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30821f91f0fa8660edca547918dc59812893b497d07c1144f326f07fdd94aba9" +dependencies = [ + "either", +] + +[[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", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[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.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +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 = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "jsonrpsee" +version = "0.24.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c4b1f204b655b36b24dc4939af20366c649431d4711863bbbae5c495f3eeb4" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.24.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49bfa9334963e1c85866b39dff3ffcc81f1c286eb23334267c5cb97677543a4" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "jsonrpsee-types", + "parking_lot", + "rand 0.8.6", + "rustc-hash 2.1.2", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.24.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5248249c016692f1465a753057ae8347681995dd490c2cb65c48b14b46215a8" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.24.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c625c78b8d545478370b6e7a2a191b0d921f831a9eef38dc1e7efb57e7a5472f" +dependencies = [ + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.24.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d86fc943f81dab0ecdd6c0240b6e0f55ad57a2ea9ad8ad7efe8456fb9cc7a4" +dependencies = [ + "http", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "known-folders" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[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.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "bitflags 2.13.0", + "libc", + "plain", + "redox_syscall 0.8.1", +] + +[[package]] +name = "librocksdb-sys" +version = "0.16.0+8.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" +dependencies = [ + "bindgen 0.69.5", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libzcash_script" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8ce05b56f3cbc65ec7d0908adb308ed91281e022f61c8c3a0c9388b5380b17" +dependencies = [ + "bindgen 0.72.1", + "cc", + "thiserror 2.0.18", + "tracing", + "zcash_script", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lmdb" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0908efb5d6496aa977d96f91413da2635a902e5e31dbef0bfb88986c248539" +dependencies = [ + "bitflags 1.3.2", + "libc", + "lmdb-sys", +] + +[[package]] +name = "lmdb-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b392838cfe8858e86fac37cf97a0e8c55cc60ba0a18365cadc33092f128ce9" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "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.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + +[[package]] +name = "metrics" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" +dependencies = [ + "portable-atomic", + "rapidhash", +] + +[[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", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "mset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4d16a3d2b0e89ec6e7d509cf791545fcb48cbc8fc2fb2e96a492defda9140" + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[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 = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" + +[[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-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-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-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 = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[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 = "openrpsee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faeb689cfe5fad5e7285f87b00c903366b307d97f41de53e894ec608968ca3a1" +dependencies = [ + "documented", + "jsonrpsee", + "quote", + "schemars 1.2.1", + "serde", + "serde_json", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "optfield" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orchard" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54f8d29bfb1e76a9d4e868a1a08cce2e57dd2bdc66232982822ad3114b91ab3" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "corez", + "ff", + "fpe", + "getset", + "group", + "halo2_gadgets", + "halo2_poseidon", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.6", + "rand_core 0.6.4", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "ordered-map" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac8f4a4a06c811aa24b151dbb3fe19f687cb52e0d5cca0493671ed88f973970" +dependencies = [ + "quickcheck", + "quickcheck_macros", +] + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +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 0.5.18", + "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 = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.6", + "static_assertions", + "subtle", +] + +[[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 0.10.7", + "password-hash", +] + +[[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 = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713258393a82f091ead52047ca779d37e5766226d009de21696c4e667044368" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[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 = "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 = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "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 = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand 0.8.6", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +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 = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec 0.6.0", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.1", + "uint 0.10.0", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[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.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.117", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags 2.13.0", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quickcheck" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +dependencies = [ + "env_logger", + "log", + "rand 0.7.3", + "rand_core 0.5.1", +] + +[[package]] +name = "quickcheck_macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 2.1.2", + "rustls", + "socket2", + "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.4", + "ring", + "rustc-hash 2.1.2", + "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", + "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 = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[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 = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + +[[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 = "reddsa" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4784b85c8bfd17b36b86e664e6e504ecdb586001086ee23749e4a633bbb84832" +dependencies = [ + "blake2b_simd", + "byteorder", + "frost-rerandomized", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core 0.6.4", + "serde", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "redjubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b0ac1bc6bb3696d2c6f52cff8fba57238b81da8c0214ee6cd146eb8fde364e" +dependencies = [ + "rand_core 0.6.4", + "reddsa", + "serde", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "redox_syscall" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "cookie", + "cookie_store", + "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 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[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 = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ripemd" +version = "0.2.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48cf93482ea998ad1302c42739bc73ab3adc574890c373ec89710e219357579" +dependencies = [ + "digest 0.11.0-pre.9", +] + +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + +[[package]] +name = "rocksdb" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + +[[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 = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "sapling-crypto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d70756ede56b5e4dd417979777bd87ddb83dfcbd0815dbf8175a9920537f8a0" +dependencies = [ + "aes", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381", + "corez", + "document-features", + "ff", + "fpe", + "getset", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "rand 0.8.6", + "rand_core 0.6.4", + "redjubjub", + "subtle", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-pre.9", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simple-mermaid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589144a964b4b30fe3a83b4bb1a09e2475aac194ec832a046a23e75bddf9eb29" + +[[package]] +name = "sinsemilla" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d268ae0ea06faafe1662e9967cd4f9022014f5eeb798e0c302c876df8b7af9c" +dependencies = [ + "group", + "pasta_curves", + "subtle", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "soketto" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +dependencies = [ + "base64", + "bytes", + "futures", + "http", + "httparse", + "log", + "rand 0.8.6", + "sha1", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +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" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +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 = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[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.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[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", +] + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "rustls-native-certs", + "socket2", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.117", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tonic-reflection" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acccd136a4bf19810a1fde9c74edc6129b42a66b44d0c1c8aaa67aeb49a146a7" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "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" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-batch-control" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6cf52578f98b4da47335c26c4f883f7993b1a9b9d2f5420eb8dbfd5dd19a28" +dependencies = [ + "futures", + "futures-core", + "pin-project", + "rayon", + "tokio", + "tokio-util", + "tower 0.4.13", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower-fallback" +version = "0.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434e19ee996ee5c6aa42f11463a355138452592e5c5b5b73b6f0f19534556af" +dependencies = [ + "futures-core", + "pin-project", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-tree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac87aa03b6a4d5a7e4810d1a80c19601dbe0f8a837e9177f23af721c7ba7beec" +dependencies = [ + "nu-ansi-term", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wagyu-zcash-parameters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb" +dependencies = [ + "wagyu-zcash-parameters-1", + "wagyu-zcash-parameters-2", + "wagyu-zcash-parameters-3", + "wagyu-zcash-parameters-4", + "wagyu-zcash-parameters-5", + "wagyu-zcash-parameters-6", +] + +[[package]] +name = "wagyu-zcash-parameters-1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8" + +[[package]] +name = "wagyu-zcash-parameters-2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c" + +[[package]] +name = "wagyu-zcash-parameters-3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147" + +[[package]] +name = "wagyu-zcash-parameters-4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6" + +[[package]] +name = "wagyu-zcash-parameters-5" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d" + +[[package]] +name = "wagyu-zcash-parameters-6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" + +[[package]] +name = "wallet-tests" +version = "0.2.0" +dependencies = [ + "corez", + "futures", + "hex", + "serde_json", + "tokio", + "tower 0.4.13", + "zaino-common", + "zaino-fetch", + "zaino-proto", + "zaino-state", + "zaino-testutils", + "zainod", + "zcash_local_net", + "zcash_primitives", + "zebra-chain", + "zebra-rpc", + "zebra-state", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "which" +version = "8.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c789537cf2f7f55be8e6192f92e464174ee55f91af622777f7f1ceb0dbccd03e" +dependencies = [ + "libc", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[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" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.13.0", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "xdg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zaino-common" +version = "0.2.0" +dependencies = [ + "hex", + "nu-ansi-term", + "serde", + "thiserror 1.0.69", + "time", + "tracing", + "tracing-subscriber", + "tracing-tree", + "zebra-chain", + "zingo_common_components", +] + +[[package]] +name = "zaino-fetch" +version = "0.2.0" +dependencies = [ + "base64", + "byteorder", + "derive_more", + "hex", + "http", + "indexmap 2.14.0", + "jsonrpsee-types", + "prost", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 1.0.69", + "tokio", + "tonic", + "tracing", + "url", + "zaino-common", + "zaino-proto", + "zebra-chain", + "zebra-rpc", +] + +[[package]] +name = "zaino-proto" +version = "0.1.3" +dependencies = [ + "prost", + "tonic", + "tonic-prost", + "tonic-prost-build", + "which 4.4.2", + "zebra-chain", + "zebra-state", +] + +[[package]] +name = "zaino-serve" +version = "0.3.0" +dependencies = [ + "futures", + "jsonrpsee", + "serde", + "thiserror 1.0.69", + "tokio", + "tonic", + "tower 0.4.13", + "tracing", + "zaino-fetch", + "zaino-proto", + "zaino-state", + "zebra-chain", + "zebra-rpc", +] + +[[package]] +name = "zaino-state" +version = "0.3.0" +dependencies = [ + "arc-swap", + "async-trait", + "bitflags 2.13.0", + "blake2", + "bs58", + "chrono", + "corez", + "dashmap", + "derive_more", + "futures", + "hex", + "incrementalmerkletree", + "indexmap 2.14.0", + "lmdb", + "lmdb-sys", + "nonempty", + "primitive-types 0.13.1", + "prost", + "reqwest", + "sapling-crypto", + "serde", + "serde_json", + "sha2 0.10.9", + "simple-mermaid", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tower 0.4.13", + "tracing", + "whoami", + "zaino-common", + "zaino-fetch", + "zaino-proto", + "zcash_address", + "zcash_keys", + "zcash_primitives", + "zcash_protocol", + "zcash_transparent", + "zebra-chain", + "zebra-rpc", + "zebra-state", +] + +[[package]] +name = "zaino-testutils" +version = "0.2.0" +dependencies = [ + "futures", + "http", + "once_cell", + "portpicker", + "tokio", + "tonic", + "tracing", + "zaino-common", + "zaino-fetch", + "zaino-proto", + "zaino-serve", + "zaino-state", + "zainod", + "zcash_local_net", + "zcash_protocol", + "zebra-chain", + "zebra-rpc", + "zebra-state", +] + +[[package]] +name = "zainod" +version = "0.4.1" +dependencies = [ + "clap", + "config", + "http", + "serde", + "tempfile", + "thiserror 1.0.69", + "tokio", + "toml 0.5.11", + "tracing", + "tracing-subscriber", + "zaino-common", + "zaino-fetch", + "zaino-serve", + "zaino-state", + "zebra-chain", + "zebra-state", +] + +[[package]] +name = "zcash_address" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58342d0aaa8e2fa98849636f52800ac4bf020574c944c974742fc933db58cac2" +dependencies = [ + "bech32", + "bs58", + "corez", + "f4jumble", + "zcash_encoding", + "zcash_protocol", +] + +[[package]] +name = "zcash_encoding" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1440921903cdb86133fb9e2fe800be488015db2939a30bedb413078a1acb0306" +dependencies = [ + "corez", + "hex", + "nonempty", +] + +[[package]] +name = "zcash_history" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fde17bf53792f9c756b313730da14880257d7661b5bfc69d0571c3a7c11a76d" +dependencies = [ + "blake2b_simd", + "byteorder", + "primitive-types 0.12.2", +] + +[[package]] +name = "zcash_keys" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbcdfbb5c8edb247439d72a397abaae9b7dd14a1c070e7e4fc3536924f9065f" +dependencies = [ + "bech32", + "blake2b_simd", + "bls12_381", + "bs58", + "corez", + "document-features", + "group", + "memuse", + "nonempty", + "orchard", + "rand_core 0.6.4", + "sapling-crypto", + "secrecy", + "subtle", + "tracing", + "zcash_address", + "zcash_encoding", + "zcash_protocol", + "zcash_transparent", + "zip32", +] + +[[package]] +name = "zcash_local_net" +version = "0.6.0" +source = "git+https://github.com/zingolabs/infrastructure.git?rev=0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083#0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083" +dependencies = [ + "getset", + "hex", + "json", + "reqwest", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", + "zcash_protocol", + "zebra-chain", + "zebra-node-services", + "zebra-rpc", + "zingo_common_components", + "zingo_test_vectors", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77efec759c3798b6e4d829fcc762070d9b229b0f13338c40bf993b7b609c2272" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c69e07f5eb3f682a6467b4b08ee4956f1acd1e886d70b21c4766953b3a1beba2" +dependencies = [ + "blake2b_simd", + "block-buffer 0.11.0-rc.3", + "corez", + "crypto-common 0.2.0-rc.1", + "document-features", + "equihash", + "ff", + "hex", + "incrementalmerkletree", + "jubjub", + "memuse", + "nonempty", + "orchard", + "rand_core 0.6.4", + "redjubjub", + "sapling-crypto", + "secp256k1", + "sha2 0.10.9", + "zcash_encoding", + "zcash_note_encryption", + "zcash_protocol", + "zcash_script", + "zcash_transparent", +] + +[[package]] +name = "zcash_proofs" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de6b0ca82e08a9d38b1121f87c5b180b5feac19fecba074cb582882210d2371" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381", + "document-features", + "group", + "home", + "jubjub", + "known-folders", + "rand_core 0.6.4", + "redjubjub", + "sapling-crypto", + "tracing", + "wagyu-zcash-parameters", + "xdg", + "zcash_primitives", +] + +[[package]] +name = "zcash_protocol" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bec496a0bd62dae98c4b26f51c5dab112d0c5350bbc2ccfdfd05bb3454f714d" +dependencies = [ + "corez", + "document-features", + "hex", + "memuse", + "zcash_encoding", +] + +[[package]] +name = "zcash_script" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f872800287d118be71bdf6fe8c869c6a6ff6fb0a5762f68fb2af54c97edf0f2" +dependencies = [ + "bip32", + "bitflags 2.13.0", + "bounded-vec", + "hex", + "ripemd 0.1.3", + "secp256k1", + "sha1", + "sha2 0.10.9", + "thiserror 2.0.18", +] + +[[package]] +name = "zcash_spec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" +dependencies = [ + "blake2b_simd", +] + +[[package]] +name = "zcash_transparent" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15df1908b428d4edeb7c7caae5692e05e2e92e5c38007a40b20ac098efdffd96" +dependencies = [ + "bip32", + "bs58", + "corez", + "document-features", + "getset", + "hex", + "nonempty", + "ripemd 0.1.3", + "secp256k1", + "sha2 0.10.9", + "subtle", + "zcash_address", + "zcash_encoding", + "zcash_protocol", + "zcash_script", + "zcash_spec", + "zip32", +] + +[[package]] +name = "zebra-chain" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed3a4e00e8f0b2197f3d8fd6cad02351a25ddbffcf0a6dc1fe463d7ea4ccfa" +dependencies = [ + "bech32", + "bitflags 2.13.0", + "bitflags-serde-legacy", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bounded-vec", + "bs58", + "byteorder", + "chrono", + "derive-getters", + "dirs", + "ed25519-zebra", + "equihash", + "futures", + "group", + "halo2_proofs", + "hex", + "humantime", + "incrementalmerkletree", + "itertools 0.14.0", + "jubjub", + "lazy_static", + "num-integer", + "orchard", + "primitive-types 0.12.2", + "rand_core 0.6.4", + "rayon", + "reddsa", + "redjubjub", + "ripemd 0.1.3", + "sapling-crypto", + "schemars 1.2.1", + "secp256k1", + "serde", + "serde-big-array", + "serde_json", + "serde_with", + "sha2 0.10.9", + "sinsemilla", + "static_assertions", + "strum", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", + "uint 0.10.0", + "x25519-dalek", + "zcash_address", + "zcash_encoding", + "zcash_history", + "zcash_note_encryption", + "zcash_primitives", + "zcash_protocol", + "zcash_script", + "zcash_transparent", +] + +[[package]] +name = "zebra-consensus" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15475adf8c271d03de99d18d622a8ae4c512c401e3e29da1f27a0ba62b22057c" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381", + "chrono", + "derive-getters", + "futures", + "futures-util", + "halo2_proofs", + "jubjub", + "lazy_static", + "libzcash_script", + "metrics", + "mset", + "once_cell", + "orchard", + "rand 0.8.6", + "rayon", + "sapling-crypto", + "serde", + "thiserror 2.0.18", + "tokio", + "tower 0.4.13", + "tower-batch-control", + "tower-fallback", + "tracing", + "tracing-futures", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", + "zcash_script", + "zcash_transparent", + "zebra-chain", + "zebra-node-services", + "zebra-script", + "zebra-state", +] + +[[package]] +name = "zebra-network" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ba4f7dcd795eb84a5a33f5c4f29df60dd4971be363d2cd98d5fba5ce0477f2" +dependencies = [ + "bitflags 2.13.0", + "byteorder", + "bytes", + "chrono", + "dirs", + "futures", + "hex", + "humantime-serde", + "indexmap 2.14.0", + "itertools 0.14.0", + "lazy_static", + "metrics", + "num-integer", + "ordered-map", + "pin-project", + "rand 0.8.6", + "rayon", + "regex", + "schemars 1.2.1", + "serde", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.4.13", + "tracing", + "tracing-error", + "tracing-futures", + "zebra-chain", +] + +[[package]] +name = "zebra-node-services" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abeece0fb4503a1df00c5ab736754b55c702613968f622f6aa3c2f9842aed2b2" +dependencies = [ + "color-eyre", + "jsonrpsee-types", + "reqwest", + "serde", + "serde_json", + "tokio", + "tower 0.4.13", + "zebra-chain", +] + +[[package]] +name = "zebra-rpc" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b460869e352c9f1b49a00ed9beaae04ca3e6498381c7189d4b90a79e51c260bd" +dependencies = [ + "base64", + "chrono", + "color-eyre", + "derive-getters", + "derive-new", + "futures", + "hex", + "http-body-util", + "hyper", + "indexmap 2.14.0", + "jsonrpsee", + "jsonrpsee-proc-macros", + "jsonrpsee-types", + "lazy_static", + "metrics", + "nix", + "openrpsee", + "orchard", + "phf", + "prost", + "rand 0.8.6", + "sapling-crypto", + "schemars 1.2.1", + "semver", + "serde", + "serde_json", + "serde_with", + "strum", + "strum_macros", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tonic-reflection", + "tower 0.4.13", + "tracing", + "which 8.0.3", + "zcash_address", + "zcash_keys", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", + "zcash_script", + "zcash_transparent", + "zebra-chain", + "zebra-consensus", + "zebra-network", + "zebra-node-services", + "zebra-script", + "zebra-state", +] + +[[package]] +name = "zebra-script" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fc38c19388671df8a77c767e279b135ffaecd5e7df6ed7cd096390c080b4a3" +dependencies = [ + "libzcash_script", + "rand 0.8.6", + "thiserror 2.0.18", + "zcash_primitives", + "zcash_script", + "zebra-chain", +] + +[[package]] +name = "zebra-state" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b351fde5047dce0d505c9dc26863ee818b7579e9cf8d8e44489deb9decfbb7" +dependencies = [ + "bincode", + "chrono", + "crossbeam-channel", + "derive-getters", + "derive-new", + "dirs", + "futures", + "hex", + "hex-literal", + "human_bytes", + "humantime-serde", + "indexmap 2.14.0", + "itertools 0.14.0", + "lazy_static", + "metrics", + "mset", + "rayon", + "regex", + "rlimit", + "rocksdb", + "sapling-crypto", + "semver", + "serde", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tower 0.4.13", + "tracing", + "zebra-chain", + "zebra-node-services", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zingo_common_components" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3378e81bdef45a9659736a09deaef2b5e5cb3d1556031468debfca3a21d4fa76" +dependencies = [ + "hex", +] + +[[package]] +name = "zingo_test_vectors" +version = "0.0.1" +source = "git+https://github.com/zingolabs/infrastructure.git?rev=0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083#0cc7dab7d12dd906c23deddd4e1fc3c42cf0f083" +dependencies = [ + "bip0039", +] + +[[package]] +name = "zip32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64bf5186a8916f7a48f2a98ef599bf9c099e2458b36b819e393db1c0e768c4b" +dependencies = [ + "bech32", + "blake2b_simd", + "memuse", + "subtle", + "zcash_spec", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/live-tests/sync-bench/Cargo.toml b/live-tests/sync-bench/Cargo.toml new file mode 100644 index 000000000..3e4373d0a --- /dev/null +++ b/live-tests/sync-bench/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "sync-bench" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +description = "Live benchmark: sync Zcash indexes against a Zebra validator." + +[[bin]] +name = "sync-bench" +path = "src/main.rs" + +[[bin]] +name = "provision-bench" +path = "src/provision_bench.rs" + +[dependencies] +zaino-primitives.workspace = true +zaino-indexes.workspace = true +zaino-rpc.workspace = true +zaino-source.workspace = true +zaino-source-zebra-rpc = { workspace = true, features = ["tracing"] } +zaino-source-zebra-readstate = { workspace = true, features = ["tracing"] } +tracing.workspace = true +tracing-subscriber.workspace = true +tracing-opentelemetry.workspace = true +opentelemetry.workspace = true +opentelemetry_sdk.workspace = true +opentelemetry-otlp.workspace = true +strum.workspace = true +zebra-chain = { workspace = true } +zaino-sync = { workspace = true, features = ["testing", "tracing"] } +zaino-persistence = { workspace = true, features = ["testing"] } +zaino-backend-lmdb.workspace = true +futures = "0.3" +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } + +[lints.rust] +unsafe_code = "forbid" diff --git a/live-tests/sync-bench/Containerfile b/live-tests/sync-bench/Containerfile new file mode 100644 index 000000000..7900c6b89 --- /dev/null +++ b/live-tests/sync-bench/Containerfile @@ -0,0 +1,17 @@ +# Stage 1: build (cached between runs via volume mounts) +FROM docker.io/archlinux:latest AS builder +RUN pacman -Syu --noconfirm rust cmake gcc make pkgconf clang protobuf +WORKDIR /src +COPY . . +# cargo registry + target cached via podman --mount args in deploy.sh +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/src/target \ + cargo build -p sync-bench --release && \ + cp target/release/sync-bench /sync-bench && \ + cp target/release/provision-bench /provision-bench + +# Stage 2: minimal runtime (just the binaries + glibc) +FROM docker.io/archlinux:base +COPY --from=builder /sync-bench /usr/local/bin/sync-bench +COPY --from=builder /provision-bench /usr/local/bin/provision-bench +ENTRYPOINT ["sync-bench"] diff --git a/live-tests/sync-bench/capture-logs.sh b/live-tests/sync-bench/capture-logs.sh new file mode 100644 index 000000000..02abef142 --- /dev/null +++ b/live-tests/sync-bench/capture-logs.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# capture-logs.sh — stream k8s Job logs into a local container, persist to volume. +# +# Usage: +# ./capture-logs.sh [job-name] [namespace] +# +# The logs are written to ./k8s/logs/.jsonl (host-mounted). +# When the job finishes (or you ctrl-c), parse with: +# python parse-logs.py k8s/logs/.jsonl +# +# Compare two runs: +# python parse-logs.py --compare k8s/logs/run-a.jsonl k8s/logs/run-b.jsonl + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +JOB="${1:-sync-bench}" +NS="${2:-golden-mainnet}" +LOG_DIR="$SCRIPT_DIR/k8s/logs" +LOG_FILE="$LOG_DIR/$JOB.jsonl" + +mkdir -p "$LOG_DIR" + +echo "Capturing logs from job/$JOB -n $NS -> $LOG_FILE" +echo "Press Ctrl-C to stop (logs are flushed continuously)." +echo "" + +kubectl logs -f "job/$JOB" -n "$NS" 2>&1 | tee "$LOG_FILE" diff --git a/live-tests/sync-bench/k8s/bench-job.yaml b/live-tests/sync-bench/k8s/bench-job.yaml new file mode 100644 index 000000000..cd857c50e --- /dev/null +++ b/live-tests/sync-bench/k8s/bench-job.yaml @@ -0,0 +1,53 @@ +# bench-job.yaml — k8s Job for in-cluster sync benchmarking. +# +# Usage: +# 1. Build and deploy binary: +# cargo build -p zaino-sync-demo --release +# # Then use bench.sh or manual kubectl cp +# +# 2. Create a ConfigMap with the binary (or use an init container): +# kubectl create configmap sync-headers-bin \ +# --from-file=sync-headers=target/release/sync-headers \ +# -n golden-mainnet +# +# 3. Apply the job (edit args as needed): +# kubectl apply -f bench-job.yaml +# +# 4. Watch logs: +# kubectl logs -f job/sync-bench -n golden-mainnet +# +# The job auto-cleans after 5 minutes (ttlSecondsAfterFinished). + +apiVersion: batch/v1 +kind: Job +metadata: + name: sync-bench + namespace: golden-mainnet + labels: + app: zaino-sync-bench +spec: + ttlSecondsAfterFinished: 300 + template: + spec: + restartPolicy: Never + containers: + - name: bench + image: archlinux:latest + command: ["/tmp/sync-headers"] + # Args: [block_count] [concurrency] [batch_size] + args: ["10000", "16", "50"] + env: + - name: ZEBRA_RPC_URL + value: "http://zebra.golden-mainnet.svc:8232" + # Set ZAINO_DB_PATH for LMDB, omit for in-memory. + - name: ZAINO_DB_PATH + value: "/tmp/zaino-bench-db" + volumeMounts: + - name: binary + mountPath: /tmp/sync-headers + subPath: sync-headers + volumes: + - name: binary + configMap: + name: sync-headers-bin + defaultMode: 0755 diff --git a/live-tests/sync-bench/k8s/bench.sh b/live-tests/sync-bench/k8s/bench.sh new file mode 100755 index 000000000..1044ea28a --- /dev/null +++ b/live-tests/sync-bench/k8s/bench.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# bench.sh — quick benchmark without rebuilding the image. +# +# Assumes the image is already on the node (run deploy.sh first). +# +# Usage: +# ./bench.sh [block_count] [concurrency] [batch_size] [source] +# +# Examples: +# ./bench.sh 10000 16 50 # ReadState (default) +# ./bench.sh 10000 16 50 rpc # RPC +# ./bench.sh 3410000 16 1000 # full chain + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SKIP_BUILD=1 SKIP_PUSH=1 exec "$SCRIPT_DIR/deploy.sh" "$@" diff --git a/live-tests/sync-bench/k8s/deploy.sh b/live-tests/sync-bench/k8s/deploy.sh new file mode 100755 index 000000000..072ac7cd4 --- /dev/null +++ b/live-tests/sync-bench/k8s/deploy.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# deploy.sh — build, push, and run the sync-bench Job on k8s. +# +# Usage: +# ./deploy.sh [block_count] [concurrency] [batch_size] +# +# Environment: +# K8S_NODE — target node for image push (default: tekau) +# JOB_NAME — k8s Job name (default: sync-bench) +# JOB_NAMESPACE — k8s namespace (default: golden-mainnet) +# RUST_LOG — tracing filter (default: zaino_sync=trace) +# ZAINO_LOG_JSON — set to 1 for JSON log output +# SKIP_BUILD — set to 1 to skip image build +# SKIP_PUSH — set to 1 to skip image push +# SOURCE — "readstate" (default) or "rpc" +# +# Examples: +# ./deploy.sh 3410000 16 1000 # full chain, ReadState+LMDB +# JOB_NAME=bench-v2 ./deploy.sh 100000 16 500 # named job alongside others +# SKIP_BUILD=1 SKIP_PUSH=1 ./deploy.sh 10000 # reuse existing image + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +BLOCK_COUNT="${1:-100}" +CONCURRENCY="${2:-16}" +BATCH_SIZE="${3:-50}" +SOURCE="${SOURCE:-readstate}" + +NODE="${K8S_NODE:-tekau}" +NS="${JOB_NAMESPACE:-golden-mainnet}" +COMMIT_SHORT=$(git -C "$REPO_ROOT" rev-parse --short=7 HEAD) +JOB_TAG="${JOB_TAG:-}" +JOB="${JOB_NAME:-bench-${COMMIT_SHORT}${JOB_TAG:+-$JOB_TAG}}" +IMAGE="sync-bench:local" +LOG="${RUST_LOG:-sync_bench=info,zaino_sync=trace}" +LOG_JSON="${ZAINO_LOG_JSON:-1}" +OTEL_ENDPOINT="${OTEL_EXPORTER_OTLP_ENDPOINT:-http://tempo.monitoring.svc:4317}" +SYNC_START="${SYNC_FROM:-}" + +# ── Build ────────────────────────────────────────────────────── +if [[ "${SKIP_BUILD:-}" != "1" ]]; then + echo "▸ building image…" + podman build -t "$IMAGE" -f "$SCRIPT_DIR/../Containerfile" "$REPO_ROOT" +fi + +# ── Push to node ─────────────────────────────────────────────── +if [[ "${SKIP_PUSH:-}" != "1" ]]; then + echo "▸ pushing image to $NODE…" + podman save "$IMAGE" | ssh "root@$NODE" 'ctr -n k8s.io images import -' +fi + +# ── Zebra state volume path ─────────────────────────────────── +# Accept ZEBRA_HOSTPATH from env, or resolve from cluster state. +if [[ -z "${ZEBRA_HOSTPATH:-}" ]]; then + # Try PVC first. + ZEBRA_HOSTPATH=$(kubectl get pvc zebra-state -n "$NS" -o jsonpath='{.spec.volumeName}' 2>/dev/null || true) + if [[ -n "$ZEBRA_HOSTPATH" ]]; then + ZEBRA_HOSTPATH=$(kubectl get pv "$ZEBRA_HOSTPATH" -o jsonpath='{.spec.csi.volumeHandle}' 2>/dev/null || true) + fi + # Fallback: any bench job with the label. + if [[ -z "$ZEBRA_HOSTPATH" ]]; then + ZEBRA_HOSTPATH=$(kubectl get job -n "$NS" -l app=zaino-sync-bench \ + -o jsonpath='{.items[0].spec.template.spec.volumes[?(@.name=="zebra-state")].hostPath.path}' 2>/dev/null || true) + fi + if [[ -z "$ZEBRA_HOSTPATH" ]]; then + echo "⚠ could not resolve zebra-state volume path; export ZEBRA_HOSTPATH=..." >&2 + exit 1 + fi +fi + +# ── Source-specific env/volumes ──────────────────────────────── +if [[ "$SOURCE" == "rpc" ]]; then + ENV_YAML=$(cat < list[dict]:\n \"\"\"Query Loki with automatic pagination. Returns parsed JSON log lines.\"\"\"\n now = datetime.now(timezone.utc)\n start_ns = int((now - timedelta(hours=hours_back)).timestamp() * 1e9)\n end_ns = int(now.timestamp() * 1e9)\n \n all_lines = []\n cursor = start_ns\n \n while cursor < end_ns:\n resp = requests.get(f'{LOKI_URL}/loki/api/v1/query_range', params={\n 'query': query,\n 'start': str(cursor),\n 'end': str(end_ns),\n 'limit': page_size,\n 'direction': 'forward',\n })\n resp.raise_for_status()\n data = resp.json()\n \n page_lines = []\n max_ts = cursor\n for stream in data.get('data', {}).get('result', []):\n for ts_ns_str, line in stream.get('values', []):\n ts_ns = int(ts_ns_str)\n max_ts = max(max_ts, ts_ns)\n try:\n obj = json.loads(line)\n page_lines.append(obj)\n except json.JSONDecodeError:\n pass\n \n all_lines.extend(page_lines)\n \n if not page_lines or max_ts <= cursor:\n break\n # Move cursor past the last seen timestamp\n cursor = max_ts + 1\n \n print(f' fetched {len(all_lines)} lines so far...', end='\\r')\n \n print(f' fetched {len(all_lines)} lines total. ')\n return all_lines\n\n\ndef list_pods(namespace: str = 'golden-mainnet') -> list[str]:\n \"\"\"List available job pods in Loki.\"\"\"\n resp = requests.get(f'{LOKI_URL}/loki/api/v1/label/pod/values')\n resp.raise_for_status()\n pods = resp.json().get('data', [])\n return [p for p in pods if 'bench' in p or 'sync' in p]\n\n\nprint('Available pods:', list_pods())" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Configure which runs to analyze.\nRUNS = {\n 'old (sequential)': '{namespace=\"golden-mainnet\",pod=~\"sync-bench-tw9xp\"}',\n 'new (par-merge)': '{namespace=\"golden-mainnet\",pod=~\"bench-df6f184-.*\"}',\n}\n\nraw_logs = {}\nfor label, query in RUNS.items():\n logs = loki_query(query)\n raw_logs[label] = logs\n print(f'{label}: {len(logs)} log lines')" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Parse into DataFrames" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def parse_duration(s: str) -> float:\n", + " \"\"\"Parse tracing duration string to milliseconds.\"\"\"\n", + " s = s.strip()\n", + " if s.endswith('ms'):\n", + " return float(s[:-2])\n", + " elif s.endswith('µs') or s.endswith('us'):\n", + " suffix_len = 2 if s.endswith('us') else len('µs')\n", + " return float(s[:-suffix_len]) / 1000\n", + " elif s.endswith('s'):\n", + " return float(s[:-1]) * 1000\n", + " return 0.0\n", + "\n", + "\n", + "def parse_ts(ts: str) -> datetime:\n", + " if '.' in ts:\n", + " base, frac = ts.split('.')\n", + " frac = frac.rstrip('Z')[:6].ljust(6, '0')\n", + " return datetime.fromisoformat(f'{base}.{frac}+00:00')\n", + " return datetime.fromisoformat(ts.replace('Z', '+00:00'))\n", + "\n", + "\n", + "def extract_commits(logs: list[dict]) -> pd.DataFrame:\n", + " rows = []\n", + " for obj in logs:\n", + " f = obj.get('fields', {})\n", + " if f.get('message') != 'atomic batch commit':\n", + " continue\n", + " task_count = None\n", + " for s in obj.get('spans', []):\n", + " if 'task_count' in s:\n", + " task_count = s['task_count']\n", + " break\n", + " rows.append({\n", + " 'timestamp': parse_ts(obj['timestamp']),\n", + " 'batch': f['batch'],\n", + " 'height': f['committed_height'],\n", + " 'op_count': f['op_count'],\n", + " 'task_count': task_count,\n", + " })\n", + " df = pd.DataFrame(rows)\n", + " if not df.empty:\n", + " df = df.sort_values('timestamp').reset_index(drop=True)\n", + " df['dt'] = df['timestamp'].diff().dt.total_seconds()\n", + " df['dh'] = df['height'].diff()\n", + " df['blocks_per_sec'] = df['dh'] / df['dt']\n", + " df['elapsed_min'] = (df['timestamp'] - df['timestamp'].iloc[0]).dt.total_seconds() / 60\n", + " return df\n", + "\n", + "\n", + "def extract_merges(logs: list[dict]) -> pd.DataFrame:\n", + " rows = []\n", + " for obj in logs:\n", + " f = obj.get('fields', {})\n", + " span = obj.get('span', {})\n", + " if span.get('name') != 'merge_persist' or f.get('message') != 'close':\n", + " continue\n", + " rows.append({\n", + " 'timestamp': parse_ts(obj['timestamp']),\n", + " 'index': span.get('index', '?'),\n", + " 'batch': span.get('batch', -1),\n", + " 'duration_ms': parse_duration(f.get('time.busy', '0us')),\n", + " })\n", + " df = pd.DataFrame(rows)\n", + " if not df.empty:\n", + " df = df.sort_values('timestamp').reset_index(drop=True)\n", + " return df\n", + "\n", + "\n", + "def extract_provisioner(logs: list[dict]) -> pd.DataFrame:\n", + " rows = []\n", + " for obj in logs:\n", + " f = obj.get('fields', {})\n", + " if f.get('message') != 'provisioner progress':\n", + " continue\n", + " rows.append({\n", + " 'timestamp': parse_ts(obj['timestamp']),\n", + " 'sent': f['sent'],\n", + " 'total': f['total'],\n", + " 'blocks_per_sec': float(f['blocks_per_sec']),\n", + " })\n", + " return pd.DataFrame(rows)\n", + "\n", + "\n", + "# Parse all runs\n", + "commits = {}\n", + "merges = {}\n", + "provisioner = {}\n", + "\n", + "for label, logs in raw_logs.items():\n", + " commits[label] = extract_commits(logs)\n", + " merges[label] = extract_merges(logs)\n", + " provisioner[label] = extract_provisioner(logs)\n", + " c = commits[label]\n", + " if not c.empty:\n", + " total_time = (c['timestamp'].iloc[-1] - c['timestamp'].iloc[0]).total_seconds()\n", + " total_blocks = c['height'].iloc[-1] - c['height'].iloc[0]\n", + " rate = total_blocks / total_time if total_time > 0 else 0\n", + " print(f'{label}: {c[\"height\"].iloc[0]:,} -> {c[\"height\"].iloc[-1]:,} '\n", + " f'({total_time/60:.1f}min, {rate:.0f} blk/s overall)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Throughput Over Time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 2, figsize=(16, 5))\n", + "\n", + "# Left: throughput vs wall time\n", + "ax = axes[0]\n", + "for label, df in commits.items():\n", + " if df.empty:\n", + " continue\n", + " valid = df.dropna(subset=['blocks_per_sec'])\n", + " ax.plot(valid['elapsed_min'], valid['blocks_per_sec'].rolling(10).mean(),\n", + " label=label, alpha=0.8)\n", + "ax.set_xlabel('Wall Time (min)')\n", + "ax.set_ylabel('blocks/s (rolling avg 10)')\n", + "ax.set_title('Throughput vs Wall Time')\n", + "ax.legend()\n", + "ax.grid(alpha=0.3)\n", + "\n", + "# Right: throughput vs chain height\n", + "ax = axes[1]\n", + "for label, df in commits.items():\n", + " if df.empty:\n", + " continue\n", + " valid = df.dropna(subset=['blocks_per_sec'])\n", + " ax.plot(valid['height'], valid['blocks_per_sec'].rolling(10).mean(),\n", + " label=label, alpha=0.8)\n", + "ax.set_xlabel('Chain Height')\n", + "ax.set_ylabel('blocks/s (rolling avg 10)')\n", + "ax.set_title('Throughput vs Chain Height')\n", + "ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f'{x/1e6:.1f}M'))\n", + "ax.legend()\n", + "ax.grid(alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Throughput by Height Band" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BAND_SIZE = 500_000\n", + "\n", + "fig, ax = plt.subplots(figsize=(14, 5))\n", + "width = 0.35\n", + "\n", + "all_bands = set()\n", + "band_data = {}\n", + "for label, df in commits.items():\n", + " if df.empty:\n", + " continue\n", + " valid = df.dropna(subset=['blocks_per_sec']).copy()\n", + " valid['band'] = (valid['height'] // BAND_SIZE) * BAND_SIZE\n", + " grouped = valid.groupby('band')['blocks_per_sec'].agg(['mean', 'median', 'std', 'count'])\n", + " band_data[label] = grouped\n", + " all_bands |= set(grouped.index)\n", + "\n", + "all_bands = sorted(all_bands)\n", + "x = np.arange(len(all_bands))\n", + "labels_list = list(band_data.keys())\n", + "\n", + "for i, (label, grouped) in enumerate(band_data.items()):\n", + " offset = (i - len(labels_list) / 2 + 0.5) * width\n", + " means = [grouped.loc[b, 'mean'] if b in grouped.index else 0 for b in all_bands]\n", + " stds = [grouped.loc[b, 'std'] if b in grouped.index else 0 for b in all_bands]\n", + " ax.bar(x + offset, means, width, yerr=stds, label=label, alpha=0.8, capsize=3)\n", + "\n", + "ax.set_xlabel('Chain Height Band')\n", + "ax.set_ylabel('blocks/s')\n", + "ax.set_title('Mean Throughput by Height Band')\n", + "ax.set_xticks(x)\n", + "ax.set_xticklabels([f'{b//1000}k' for b in all_bands], rotation=45)\n", + "ax.legend()\n", + "ax.grid(alpha=0.3, axis='y')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Batch Complexity (op_count)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1, 2, figsize=(16, 5))\n", + "\n", + "# ops per batch vs height\n", + "ax = axes[0]\n", + "for label, df in commits.items():\n", + " if df.empty:\n", + " continue\n", + " ax.scatter(df['height'], df['op_count'], s=2, alpha=0.4, label=label)\n", + "ax.set_xlabel('Chain Height')\n", + "ax.set_ylabel('op_count per batch')\n", + "ax.set_title('Batch Complexity vs Height')\n", + "ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f'{x/1e6:.1f}M'))\n", + "ax.legend()\n", + "ax.grid(alpha=0.3)\n", + "\n", + "# throughput vs op_count scatter\n", + "ax = axes[1]\n", + "for label, df in commits.items():\n", + " if df.empty:\n", + " continue\n", + " valid = df.dropna(subset=['blocks_per_sec'])\n", + " ax.scatter(valid['op_count'], valid['blocks_per_sec'], s=2, alpha=0.4, label=label)\n", + "ax.set_xlabel('op_count per batch')\n", + "ax.set_ylabel('blocks/s')\n", + "ax.set_title('Throughput vs Batch Complexity')\n", + "ax.legend()\n", + "ax.grid(alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Parallelism (task_count)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(14, 4))\n", + "\n", + "for label, df in commits.items():\n", + " if df.empty or 'task_count' not in df.columns:\n", + " continue\n", + " valid = df.dropna(subset=['task_count'])\n", + " ax.plot(valid['height'], valid['task_count'], '.', markersize=2, alpha=0.5, label=label)\n", + "\n", + "ax.set_xlabel('Chain Height')\n", + "ax.set_ylabel('task_count')\n", + "ax.set_title('Parallelism Over Chain Height')\n", + "ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f'{x/1e6:.1f}M'))\n", + "ax.legend()\n", + "ax.grid(alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Per-Index Merge Duration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "for label, df in merges.items():\n if df.empty:\n print(f'{label}: no merge events')\n continue\n\n indexes = sorted(df['index'].unique())\n fig, axes = plt.subplots(1, 1, figsize=(14, 5))\n\n data_for_box = [df[df['index'] == idx]['duration_ms'].values for idx in indexes]\n bp = axes.boxplot(data_for_box, tick_labels=indexes, vert=True, patch_artist=True,\n showfliers=False)\n axes.set_ylabel('duration (ms)')\n axes.set_title(f'Merge+Persist Duration by Index \\u2014 {label}')\n axes.tick_params(axis='x', rotation=30)\n axes.grid(alpha=0.3, axis='y')\n\n plt.tight_layout()\n plt.show()" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Merge duration over time for each run (line per index)\n", + "for label, df in merges.items():\n", + " if df.empty:\n", + " continue\n", + "\n", + " fig, ax = plt.subplots(figsize=(14, 5))\n", + " for idx in sorted(df['index'].unique()):\n", + " sub = df[df['index'] == idx].copy()\n", + " # Use batch as x-axis for alignment\n", + " rolled = sub.set_index('batch')['duration_ms'].rolling(20).mean()\n", + " ax.plot(rolled.index, rolled.values, label=idx, alpha=0.8)\n", + "\n", + " ax.set_xlabel('Batch')\n", + " ax.set_ylabel('duration (ms, rolling avg 20)')\n", + " ax.set_title(f'Merge Duration Over Time — {label}')\n", + " ax.legend(loc='upper left', fontsize='small')\n", + " ax.grid(alpha=0.3)\n", + "\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Summary Table" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "summary_rows = []\n", + "for label, df in commits.items():\n", + " if df.empty:\n", + " continue\n", + " valid = df.dropna(subset=['blocks_per_sec'])\n", + " total_time = (df['timestamp'].iloc[-1] - df['timestamp'].iloc[0]).total_seconds()\n", + " total_blocks = df['height'].iloc[-1] - df['height'].iloc[0]\n", + " \n", + " mdf = merges.get(label, pd.DataFrame())\n", + " merge_p50 = mdf['duration_ms'].median() if not mdf.empty else None\n", + " merge_p95 = mdf['duration_ms'].quantile(0.95) if not mdf.empty else None\n", + " \n", + " summary_rows.append({\n", + " 'run': label,\n", + " 'height_from': df['height'].iloc[0],\n", + " 'height_to': df['height'].iloc[-1],\n", + " 'batches': len(df),\n", + " 'wall_min': total_time / 60,\n", + " 'overall_bps': total_blocks / total_time if total_time > 0 else 0,\n", + " 'p50_bps': valid['blocks_per_sec'].median(),\n", + " 'p95_bps': valid['blocks_per_sec'].quantile(0.95),\n", + " 'merge_p50_ms': merge_p50,\n", + " 'merge_p95_ms': merge_p95,\n", + " })\n", + "\n", + "summary = pd.DataFrame(summary_rows)\n", + "summary.style.format({\n", + " 'height_from': '{:,.0f}',\n", + " 'height_to': '{:,.0f}',\n", + " 'wall_min': '{:.1f}',\n", + " 'overall_bps': '{:.0f}',\n", + " 'p50_bps': '{:.0f}',\n", + " 'p95_bps': '{:.0f}',\n", + " 'merge_p50_ms': '{:.1f}',\n", + " 'merge_p95_ms': '{:.1f}',\n", + "})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/live-tests/sync-bench/parse-logs.py b/live-tests/sync-bench/parse-logs.py new file mode 100755 index 000000000..cb1e30d56 --- /dev/null +++ b/live-tests/sync-bench/parse-logs.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python3 +"""Parse sync-bench JSON tracing logs and display metrics. + +Usage: + kubectl logs job/sync-bench -n golden-mainnet | python parse-logs.py + kubectl logs -f job/sync-bench -n golden-mainnet | python parse-logs.py --live + python parse-logs.py < logs.json + python parse-logs.py --compare <(kubectl logs job/A -n ns) <(kubectl logs job/B -n ns) + +Flags: + --live streaming mode, print rolling updates + --compare compare two log files side by side + --csv dump raw data as CSV to stdout + --summary only print the final summary (no rolling output) +""" + +import json +import sys +import argparse +from collections import defaultdict +from dataclasses import dataclass, field +from datetime import datetime +from typing import TextIO + + +# --------------------------------------------------------------------------- +# Data model +# --------------------------------------------------------------------------- + +@dataclass +class BatchCommit: + batch: int + height: int + op_count: int + timestamp: datetime + task_count: int | None = None + + +@dataclass +class MergeEvent: + index: str + batch: int + duration_us: float # microseconds + timestamp: datetime + + +@dataclass +class ProvisionerProgress: + sent: int + total: int + elapsed_secs: float + blocks_per_sec: float + timestamp: datetime + + +@dataclass +class Metrics: + commits: list[BatchCommit] = field(default_factory=list) + merges: list[MergeEvent] = field(default_factory=list) + provisioner: list[ProvisionerProgress] = field(default_factory=list) + + +# --------------------------------------------------------------------------- +# Parsing +# --------------------------------------------------------------------------- + +def parse_duration_str(s: str) -> float: + """Parse tracing duration like '41.2ms', '3.40s', '520us' to microseconds.""" + s = s.strip() + if s.endswith("ms"): + return float(s[:-2]) * 1000 + elif s.endswith("µs") or s.endswith("us"): + suffix_len = 2 if s.endswith("us") else len("µs") + return float(s[:-suffix_len]) + elif s.endswith("s"): + return float(s[:-1]) * 1_000_000 + return 0.0 + + +def parse_timestamp(ts: str) -> datetime: + # Handle fractional seconds of varying length + if "." in ts: + base, frac = ts.split(".") + frac = frac.rstrip("Z") + frac = frac[:6].ljust(6, "0") + return datetime.fromisoformat(f"{base}.{frac}+00:00") + return datetime.fromisoformat(ts.replace("Z", "+00:00")) + + +def parse_line(line: str, metrics: Metrics) -> str | None: + """Parse one JSON line, append to metrics. Returns event type or None.""" + line = line.strip() + if not line: + return None + try: + obj = json.loads(line) + except json.JSONDecodeError: + return None + + fields = obj.get("fields", {}) + msg = fields.get("message", "") + ts = parse_timestamp(obj["timestamp"]) + + # Batch commit + if msg == "atomic batch commit": + task_count = None + for s in obj.get("spans", []): + if "task_count" in s: + task_count = s["task_count"] + break + metrics.commits.append(BatchCommit( + batch=fields["batch"], + height=fields["committed_height"], + op_count=fields["op_count"], + timestamp=ts, + task_count=task_count, + )) + return "commit" + + # Merge/persist span close + span = obj.get("span", {}) + if span.get("name") == "merge_persist" and msg == "close": + dur = parse_duration_str(fields.get("time.busy", "0us")) + metrics.merges.append(MergeEvent( + index=span.get("index", "?"), + batch=span.get("batch", -1), + duration_us=dur, + timestamp=ts, + )) + return "merge" + + # Provisioner progress + if msg == "provisioner progress": + metrics.provisioner.append(ProvisionerProgress( + sent=fields["sent"], + total=fields["total"], + elapsed_secs=float(fields["elapsed_secs"]), + blocks_per_sec=float(fields["blocks_per_sec"]), + timestamp=ts, + )) + return "progress" + + return None + + +def parse_stream(f: TextIO) -> Metrics: + metrics = Metrics() + for line in f: + parse_line(line, metrics) + return metrics + + +# --------------------------------------------------------------------------- +# Analysis +# --------------------------------------------------------------------------- + +def percentile(values: list[float], p: float) -> float: + if not values: + return 0.0 + sorted_v = sorted(values) + k = (len(sorted_v) - 1) * p / 100 + f = int(k) + c = f + 1 + if c >= len(sorted_v): + return sorted_v[f] + return sorted_v[f] + (k - f) * (sorted_v[c] - sorted_v[f]) + + +def height_band(h: int) -> str: + """Group height into 500k bands.""" + band = (h // 500_000) * 500_000 + return f"{band // 1000}k-{(band + 500_000) // 1000}k" + + +def compute_throughput(commits: list[BatchCommit]) -> list[dict]: + """Compute per-batch throughput from consecutive commits.""" + rows = [] + for i in range(1, len(commits)): + prev, cur = commits[i - 1], commits[i] + dt = (cur.timestamp - prev.timestamp).total_seconds() + if dt <= 0: + continue + dh = cur.height - prev.height + rows.append({ + "batch": cur.batch, + "height": cur.height, + "blocks": dh, + "duration_s": dt, + "blocks_per_sec": dh / dt, + "op_count": cur.op_count, + "task_count": cur.task_count, + "timestamp": cur.timestamp, + }) + return rows + + +def merge_stats_by_index(merges: list[MergeEvent]) -> dict[str, dict]: + by_index: dict[str, list[float]] = defaultdict(list) + for m in merges: + by_index[m.index].append(m.duration_us) + stats = {} + for idx, durations in sorted(by_index.items()): + stats[idx] = { + "count": len(durations), + "mean_ms": sum(durations) / len(durations) / 1000, + "p50_ms": percentile(durations, 50) / 1000, + "p95_ms": percentile(durations, 95) / 1000, + "max_ms": max(durations) / 1000, + } + return stats + + +def throughput_by_band(rows: list[dict]) -> dict[str, dict]: + bands: dict[str, list[float]] = defaultdict(list) + for r in rows: + bands[height_band(r["height"])].append(r["blocks_per_sec"]) + stats = {} + for band, values in sorted(bands.items()): + stats[band] = { + "count": len(values), + "mean": sum(values) / len(values), + "p50": percentile(values, 50), + "p95": percentile(values, 95), + "min": min(values), + "max": max(values), + } + return stats + + +# --------------------------------------------------------------------------- +# Display +# --------------------------------------------------------------------------- + +BOLD = "\033[1m" +DIM = "\033[2m" +RESET = "\033[0m" +CYAN = "\033[36m" +GREEN = "\033[32m" +YELLOW = "\033[33m" + + +def print_header(title: str): + print(f"\n{BOLD}{CYAN}{'=' * 60}{RESET}") + print(f"{BOLD}{CYAN} {title}{RESET}") + print(f"{BOLD}{CYAN}{'=' * 60}{RESET}") + + +def print_throughput_table(rows: list[dict], last_n: int = 0): + """Print throughput table. If last_n > 0, only show last N rows.""" + display = rows[-last_n:] if last_n else rows + if not display: + print(" (no data)") + return + + print(f" {'batch':>6} {'height':>10} {'blocks':>6} {'dur(s)':>7} {'blk/s':>8} {'ops':>8} {'tasks':>5}") + print(f" {'-' * 6} {'-' * 10} {'-' * 6} {'-' * 7} {'-' * 8} {'-' * 8} {'-' * 5}") + for r in display: + tc = str(r["task_count"]) if r["task_count"] is not None else "?" + bps = r["blocks_per_sec"] + color = GREEN if bps > 1000 else YELLOW if bps > 500 else "" + end = RESET if color else "" + print(f" {r['batch']:>6} {r['height']:>10,} {r['blocks']:>6} {r['duration_s']:>7.1f} {color}{bps:>8.0f}{end} {r['op_count']:>8,} {tc:>5}") + + +def print_band_summary(bands: dict[str, dict]): + if not bands: + print(" (no data)") + return + + print(f" {'band':>12} {'n':>5} {'mean':>8} {'p50':>8} {'p95':>8} {'min':>8} {'max':>8}") + print(f" {'-' * 12} {'-' * 5} {'-' * 8} {'-' * 8} {'-' * 8} {'-' * 8} {'-' * 8}") + for band, s in bands.items(): + print(f" {band:>12} {s['count']:>5} {s['mean']:>8.0f} {s['p50']:>8.0f} {s['p95']:>8.0f} {s['min']:>8.0f} {s['max']:>8.0f}") + + +def print_merge_stats(stats: dict[str, dict]): + if not stats: + print(" (no data)") + return + + print(f" {'index':<22} {'n':>6} {'mean':>8} {'p50':>8} {'p95':>8} {'max':>8}") + print(f" {'-' * 22} {'-' * 6} {'-' * 8} {'-' * 8} {'-' * 8} {'-' * 8}") + for idx, s in stats.items(): + print(f" {idx:<22} {s['count']:>6} {s['mean_ms']:>7.1f}ms {s['p50_ms']:>7.1f}ms {s['p95_ms']:>7.1f}ms {s['max_ms']:>7.1f}ms") + + +def print_provisioner_stats(prov: list[ProvisionerProgress]): + if not prov: + return + print_header("Provisioner Progress") + last = prov[-1] + print(f" sent: {last.sent:,} / {last.total:,} ({last.sent / last.total * 100:.1f}%)") + print(f" avg provisioner rate: {last.blocks_per_sec:.0f} blocks/s") + if len(prov) >= 2: + rates = [p.blocks_per_sec for p in prov] + print(f" rate range: {min(rates):.0f} - {max(rates):.0f} blocks/s") + + +def print_ascii_sparkline(values: list[float], width: int = 50, label: str = ""): + """Simple ASCII sparkline.""" + if not values: + return + vmin, vmax = min(values), max(values) + spread = vmax - vmin if vmax > vmin else 1 + bars = " _.-~*" + line = "" + # Downsample if needed + if len(values) > width: + step = len(values) / width + sampled = [values[int(i * step)] for i in range(width)] + else: + sampled = values + for v in sampled: + idx = int((v - vmin) / spread * (len(bars) - 1)) + line += bars[idx] + print(f" {label} [{vmin:.0f} .. {vmax:.0f}]") + print(f" {DIM}{line}{RESET}") + + +def print_full_report(metrics: Metrics, label: str = ""): + title = f"Sync Bench Analysis" + if label: + title += f" ({label})" + print_header(title) + + if metrics.commits: + first = metrics.commits[0] + last = metrics.commits[-1] + total_time = (last.timestamp - first.timestamp).total_seconds() + total_blocks = last.height - first.height + print(f" height range: {first.height:,} -> {last.height:,}") + print(f" batches: {len(metrics.commits)}") + print(f" wall time: {total_time:.0f}s ({total_time / 60:.1f}m)") + if total_time > 0: + print(f" overall: {total_blocks / total_time:.0f} blocks/s") + + rows = compute_throughput(metrics.commits) + + # Throughput by height band + print_header("Throughput by Chain Height (blocks/s)") + bands = throughput_by_band(rows) + print_band_summary(bands) + + # Throughput sparkline + if rows: + print_header("Throughput Over Time") + print_ascii_sparkline([r["blocks_per_sec"] for r in rows], label="blocks/s") + print() + print_ascii_sparkline([r["op_count"] for r in rows], label="ops/batch") + + # Task count evolution + task_counts = [r["task_count"] for r in rows if r["task_count"] is not None] + if task_counts: + print_header("Parallelism (task_count)") + print_ascii_sparkline(task_counts, label="tasks") + tc_vals = sorted(set(task_counts)) + print(f" values seen: {', '.join(str(v) for v in tc_vals)}") + + # Per-index merge stats + print_header("Merge+Persist Duration by Index") + mstats = merge_stats_by_index(metrics.merges) + print_merge_stats(mstats) + + # Merge duration sparkline per index (top 3 slowest) + if mstats: + slowest = sorted(mstats.items(), key=lambda x: x[1]["mean_ms"], reverse=True)[:3] + print_header("Merge Duration Over Time (top 3 slowest)") + by_index: dict[str, list[float]] = defaultdict(list) + for m in metrics.merges: + by_index[m.index].append(m.duration_us / 1000) + for idx, _ in slowest: + if idx in by_index: + print_ascii_sparkline(by_index[idx], label=f"{idx} (ms)") + + # Provisioner + print_provisioner_stats(metrics.provisioner) + + # Recent batches + print_header("Recent Batches (last 10)") + print_throughput_table(rows, last_n=10) + + print() + + +def print_comparison(metrics_a: Metrics, metrics_b: Metrics, label_a: str, label_b: str): + """Side-by-side comparison of two runs.""" + print_header(f"Comparison: {label_a} vs {label_b}") + + for label, m in [(label_a, metrics_a), (label_b, metrics_b)]: + if m.commits: + first, last = m.commits[0], m.commits[-1] + total_time = (last.timestamp - first.timestamp).total_seconds() + total_blocks = last.height - first.height + rate = total_blocks / total_time if total_time > 0 else 0 + print(f" {label}:") + print(f" height: {first.height:,} -> {last.height:,}") + print(f" wall time: {total_time:.0f}s, overall: {rate:.0f} blocks/s") + + # Compare throughput at matching height bands + rows_a = compute_throughput(metrics_a.commits) + rows_b = compute_throughput(metrics_b.commits) + bands_a = throughput_by_band(rows_a) + bands_b = throughput_by_band(rows_b) + + all_bands = sorted(set(bands_a.keys()) | set(bands_b.keys())) + if all_bands: + print(f"\n {'band':>12} {'A mean':>8} {'B mean':>8} {'delta':>8} {'ratio':>6}") + print(f" {'-' * 12} {'-' * 8} {'-' * 8} {'-' * 8} {'-' * 6}") + for band in all_bands: + a_mean = bands_a.get(band, {}).get("mean", 0) + b_mean = bands_b.get(band, {}).get("mean", 0) + delta = b_mean - a_mean + ratio = b_mean / a_mean if a_mean > 0 else 0 + a_str = f"{a_mean:.0f}" if a_mean else "-" + b_str = f"{b_mean:.0f}" if b_mean else "-" + d_str = f"{delta:+.0f}" if a_mean and b_mean else "-" + r_str = f"{ratio:.2f}x" if a_mean and b_mean else "-" + print(f" {band:>12} {a_str:>8} {b_str:>8} {d_str:>8} {r_str:>6}") + + # Compare merge stats + mstats_a = merge_stats_by_index(metrics_a.merges) + mstats_b = merge_stats_by_index(metrics_b.merges) + all_indexes = sorted(set(mstats_a.keys()) | set(mstats_b.keys())) + if all_indexes: + print(f"\n {'index':<22} {'A p50':>8} {'B p50':>8} {'delta':>8}") + print(f" {'-' * 22} {'-' * 8} {'-' * 8} {'-' * 8}") + for idx in all_indexes: + a_p50 = mstats_a.get(idx, {}).get("p50_ms", 0) + b_p50 = mstats_b.get(idx, {}).get("p50_ms", 0) + delta = b_p50 - a_p50 + a_str = f"{a_p50:.1f}ms" if a_p50 else "-" + b_str = f"{b_p50:.1f}ms" if b_p50 else "-" + d_str = f"{delta:+.1f}ms" if a_p50 and b_p50 else "-" + print(f" {idx:<22} {a_str:>8} {b_str:>8} {d_str:>8}") + + print() + + +def live_mode(f: TextIO): + """Streaming mode: print rolling updates as data arrives.""" + metrics = Metrics() + commit_count = 0 + for line in f: + event = parse_line(line, metrics) + if event == "commit" and metrics.commits: + commit_count += 1 + c = metrics.commits[-1] + rows = compute_throughput(metrics.commits) + if rows: + r = rows[-1] + tc = r["task_count"] if r["task_count"] is not None else "?" + print( + f" batch {c.batch:>5} " + f"height {c.height:>10,} " + f"{r['blocks_per_sec']:>6.0f} blk/s " + f"{r['duration_s']:>5.1f}s " + f"ops={c.op_count:>8,} " + f"tasks={tc}", + flush=True, + ) + # Print summary every 100 commits + if commit_count % 100 == 0: + print_full_report(metrics, label="interim") + + if metrics.commits: + print_full_report(metrics, label="final") + + +def csv_mode(metrics: Metrics): + """Dump raw throughput data as CSV.""" + rows = compute_throughput(metrics.commits) + print("batch,height,blocks,duration_s,blocks_per_sec,op_count,task_count") + for r in rows: + tc = r["task_count"] if r["task_count"] is not None else "" + print(f"{r['batch']},{r['height']},{r['blocks']},{r['duration_s']:.3f},{r['blocks_per_sec']:.1f},{r['op_count']},{tc}") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser(description="Parse sync-bench JSON tracing logs") + parser.add_argument("--live", action="store_true", help="Streaming mode with rolling updates") + parser.add_argument("--csv", action="store_true", help="Output raw data as CSV") + parser.add_argument("--summary", action="store_true", help="Only print summary, no rolling table") + parser.add_argument("--compare", nargs=2, metavar=("FILE_A", "FILE_B"), help="Compare two log files") + parser.add_argument("--last", type=int, default=20, help="Number of recent batches to show (default: 20)") + parser.add_argument("files", nargs="*", help="Log files to parse (default: stdin)") + args = parser.parse_args() + + if args.compare: + with open(args.compare[0]) as fa, open(args.compare[1]) as fb: + ma = parse_stream(fa) + mb = parse_stream(fb) + print_comparison(ma, mb, args.compare[0], args.compare[1]) + # Also print individual reports + print_full_report(ma, label=args.compare[0]) + print_full_report(mb, label=args.compare[1]) + return + + if args.live: + f = open(args.files[0]) if args.files else sys.stdin + live_mode(f) + return + + # Batch mode + if args.files: + for path in args.files: + with open(path) as f: + metrics = parse_stream(f) + if args.csv: + csv_mode(metrics) + else: + print_full_report(metrics, label=path) + else: + metrics = parse_stream(sys.stdin) + if args.csv: + csv_mode(metrics) + else: + print_full_report(metrics) + + +if __name__ == "__main__": + main() diff --git a/live-tests/sync-bench/reports/2026-07-14-full-chain-batch-emission.md b/live-tests/sync-bench/reports/2026-07-14-full-chain-batch-emission.md new file mode 100644 index 000000000..7d9e8c1e4 --- /dev/null +++ b/live-tests/sync-bench/reports/2026-07-14-full-chain-batch-emission.md @@ -0,0 +1,177 @@ +--- +date: 2026-07-14 +ref: df6f1848 +ref_short: df6f184 +branch: review-sync +job_name: bench-df6f184-par-merge +cluster: golden-mainnet +node: tekau + +provisioner: zebra-readstate +backend: lmdb +map_size_gb: 120 +storage: nvme + +index_set: current_zaino +context: CurrentZainoContext +context_fields: + - height (BlockHeight) + - hash (BlockHash) + - prev_hash (BlockHash) + - time (BlockTime) + - bits (CompactDifficulty) + - txids (Vec) + - spends (Vec<(prev_txid, prev_index, spending_txid)>) + - txid_locations (Vec<(txid, height, tx_index)>) + - transparent_txs (Vec) + - sapling_txs (Vec) + - orchard_txs (Vec) +indexes: + - name: headers + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: HeaderValue (hash + prev_hash + time + bits; 72 bytes) + block_context: HeaderCtx (height, hash, prev_hash, time, bits) + - name: txids + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: TxidsValue (Vec; 32-byte chunks) + block_context: TxidsCtx (height, txids) + - name: hash_to_height + scope: BlockLocal + composition: Append + key: BlockHash (32 bytes) + value: BlockHeight (8 bytes LE) + block_context: HashToHeightCtx (hash, height) + - name: txid_location + scope: BlockLocal + composition: Append + key: TransactionHash (32 bytes) + value: TxLocation (height + tx_index; 12 bytes LE) + block_context: TxidLocationCtx (Vec<(txid, height, tx_index)>) + - name: transparent_data + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: TransparentBlockValue (Vec) + block_context: TransparentDataCtx (height, Vec) + - name: transparent_spends + scope: BlockLocal + composition: Append + key: OutpointKey (prev_txid + prev_index; 36 bytes) + value: TransactionHash (32 bytes) + block_context: SpendCtx (Vec<(prev_txid, prev_index, spending_txid)>) + - name: sapling + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: SaplingBlockValue (Vec) + block_context: SaplingCtx (height, Vec) + - name: orchard + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: OrchardBlockValue (Vec) + block_context: OrchardCtx (height, Vec) + +block_range: 1407..3411407 +block_count: 3410000 +concurrency: 16 +batch_size: 1000 + +algorithm: + scheduler: batch-emission + merge: sequential + extraction: rayon-parallel + commit: atomic-batch + +total_time_secs: 6996.65 +blocks_per_sec: 487.4 +db_size_mb: 53666 +bytes_per_block: 16502 +--- + +# Full Chain Sync — Batch Emission Scheduler + +Second complete chain sync with the scheduler batch-emission optimization. +This run emits ALL available blocks for BlockLocal indexes in a single +scheduler cycle instead of one extraction per cycle. Merge is still +sequential (the parallel merge fix was committed after this deployment). + +## Configuration + +| Parameter | Value | +|-----------|-------| +| Provisioner | `zebra-readstate` (direct RocksDB read-only) | +| Backend | LMDB, 120 GB map, NVMe (`/data/zaino-bench`) | +| Index set | `current_zaino` (8 indexes) | +| Block range | 1,407 → 3,411,407 (3,410,000 blocks) | +| Concurrency | 16 (provisioner sliding window) | +| Batch size | 1,000 blocks per batch | + +## Results + +| Metric | Value | +|--------|-------| +| **Total time** | 6,996.65 s (116.6 min) | +| **Overall throughput** | **487.4 blocks/s** | +| **DB size** | 53,666 MB (52.4 GB) | +| **Bytes/block** | 16,502 | + +## Throughput at Chain Tip (last 20 batches, heights 3.39M–3.41M) + +| Stat | blocks/s | +|------|----------| +| mean | 3,868 | +| p50 | 3,912 | +| p95 | 4,487 | +| min | 2,941 | +| max | 4,667 | + +## Per-Index Merge Duration (at tip) + +| Index | p50 | p95 | max | +|-------|-----|-----|-----| +| headers | 0.3 ms | 0.4 ms | 0.4 ms | +| txids | 0.3 ms | 0.4 ms | 0.5 ms | +| hash_to_height | 0.2 ms | 0.3 ms | 0.3 ms | +| txid_location | 1.1 ms | 1.2 ms | 1.3 ms | +| transparent_data | 1.6 ms | 1.9 ms | 2.1 ms | +| transparent_spends | 1.1 ms | 2.1 ms | 2.3 ms | +| sapling | 0.8 ms | 1.2 ms | 1.2 ms | +| orchard | 0.9 ms | 1.2 ms | 1.3 ms | + +## Comparison with Sequential Baseline + +| Metric | Sequential | Batch Emission | Delta | +|--------|-----------|----------------|-------| +| **Total time** | 7,337.66 s | 6,996.65 s | -341 s (-4.6%) | +| **Overall throughput** | 464.7 blk/s | 487.4 blk/s | +22.7 (+4.9%) | +| **Tip throughput (p50)** | 2,977 blk/s | 3,912 blk/s | +935 (+31.4%) | +| **Tip throughput (mean)** | 3,075 blk/s | 3,868 blk/s | +793 (+25.8%) | +| **DB size** | 53,668 MB | 53,666 MB | ~identical | + +## Observations + +1. **4.9% overall improvement** — modest because the bottleneck in mid-chain + (heights 500k–2M) is block deserialization and LMDB writes, not scheduler + overhead. The scheduler fix reduces dispatch round-trips from 1000 to ~10 + per batch, but this only matters when extraction is fast. + +2. **25–31% improvement at chain tip** — tip blocks are small so extraction + is fast, and the scheduler overhead that was removed becomes a larger + fraction of batch time. This is where the optimization shines. + +3. **Merge is still sequential** — the `report_extractions` path was calling + `merge_persist` per-index in a loop. The parallel merge fix (committed as + `095591cf`) was not included in this deployment. A follow-up run with both + optimizations will show the combined effect. + +4. **DB size is identical** — expected, same indexes indexing the same chain. + +5. **sync_channel span: busy=2064s, idle=4932s** — the engine spent 70% of + wall time idle, waiting for blocks from the provisioner. This confirms + the provisioner (ReadState block deserialization) is the primary bottleneck, + not the engine. diff --git a/live-tests/sync-bench/reports/2026-07-14-full-chain-sequential.md b/live-tests/sync-bench/reports/2026-07-14-full-chain-sequential.md new file mode 100644 index 000000000..b2d47e1cc --- /dev/null +++ b/live-tests/sync-bench/reports/2026-07-14-full-chain-sequential.md @@ -0,0 +1,165 @@ +--- +date: 2026-07-14 +ref: df6f1848 +ref_short: df6f184 +branch: review-sync +job_name: sync-bench +cluster: golden-mainnet +node: tekau + +provisioner: zebra-readstate +backend: lmdb +map_size_gb: 120 +storage: nvme + +index_set: current_zaino +context: CurrentZainoContext +context_fields: + - height (BlockHeight) + - hash (BlockHash) + - prev_hash (BlockHash) + - time (BlockTime) + - bits (CompactDifficulty) + - txids (Vec) + - spends (Vec<(prev_txid, prev_index, spending_txid)>) + - txid_locations (Vec<(txid, height, tx_index)>) + - transparent_txs (Vec) + - sapling_txs (Vec) + - orchard_txs (Vec) +indexes: + - name: headers + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: HeaderValue (hash + prev_hash + time + bits; 72 bytes) + block_context: HeaderCtx (height, hash, prev_hash, time, bits) + - name: txids + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: TxidsValue (Vec; 32-byte chunks) + block_context: TxidsCtx (height, txids) + - name: hash_to_height + scope: BlockLocal + composition: Append + key: BlockHash (32 bytes) + value: BlockHeight (8 bytes LE) + block_context: HashToHeightCtx (hash, height) + - name: txid_location + scope: BlockLocal + composition: Append + key: TransactionHash (32 bytes) + value: TxLocation (height + tx_index; 12 bytes LE) + block_context: TxidLocationCtx (Vec<(txid, height, tx_index)>) + - name: transparent_data + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: TransparentBlockValue (Vec) + block_context: TransparentDataCtx (height, Vec) + - name: transparent_spends + scope: BlockLocal + composition: Append + key: OutpointKey (prev_txid + prev_index; 36 bytes) + value: TransactionHash (32 bytes) + block_context: SpendCtx (Vec<(prev_txid, prev_index, spending_txid)>) + - name: sapling + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: SaplingBlockValue (Vec) + block_context: SaplingCtx (height, Vec) + - name: orchard + scope: BlockLocal + composition: Append + key: BlockHeight (8 bytes LE) + value: OrchardBlockValue (Vec) + block_context: OrchardCtx (height, Vec) + +block_range: 1372..3411372 +block_count: 3410000 +concurrency: 16 +batch_size: 1000 + +algorithm: + scheduler: one-extraction-per-cycle + merge: sequential + extraction: rayon-parallel + commit: atomic-batch + +total_time_secs: 7337.66 +blocks_per_sec: 464.7 +db_size_mb: 53668 +bytes_per_block: 16503 +--- + +# Full Chain Sync — Sequential Merge Baseline + +First complete chain sync with the new sync engine (8 indexes, `current_zaino` set). +This run uses the **pre-optimization** code: one extraction emitted per scheduler +cycle and sequential merge+persist across indexes. + +## Configuration + +| Parameter | Value | +|-----------|-------| +| Provisioner | `zebra-readstate` (direct RocksDB read-only) | +| Backend | LMDB, 120 GB map, NVMe (`/data/zaino-bench`) | +| Index set | `current_zaino` (8 indexes) | +| Block range | 1,372 → 3,411,372 (3,410,000 blocks) | +| Concurrency | 16 (provisioner sliding window) | +| Batch size | 1,000 blocks per batch | + +## Results + +| Metric | Value | +|--------|-------| +| **Total time** | 7,337.66 s (122.3 min) | +| **Overall throughput** | **464.7 blocks/s** | +| **DB size** | 53,668 MB (52.4 GB) | +| **Bytes/block** | 16,503 | + +## Throughput at Chain Tip (last 24 batches, heights 3.38M–3.41M) + +| Stat | blocks/s | +|------|----------| +| mean | 3,075 | +| p50 | 2,977 | +| p95 | 3,592 | +| min | 2,012 | +| max | 3,757 | + +Tip blocks are simpler (13k–25k ops/batch vs heavier middle-chain blocks), +so tip throughput is ~6x the overall average. + +## Per-Index Merge Duration (at tip) + +| Index | p50 | p95 | max | +|-------|-----|-----|-----| +| headers | 0.3 ms | 0.4 ms | 0.6 ms | +| txids | 0.3 ms | 0.4 ms | 0.4 ms | +| hash_to_height | 0.2 ms | 0.3 ms | 0.4 ms | +| txid_location | 1.2 ms | 1.4 ms | 1.7 ms | +| transparent_data | 1.6 ms | 2.1 ms | 4.5 ms | +| transparent_spends | 1.0 ms | 2.0 ms | 3.5 ms | +| sapling | 1.0 ms | 1.2 ms | 1.2 ms | +| orchard | 1.0 ms | 1.2 ms | 3.1 ms | + +## Observations + +1. **464.7 blocks/s overall** covers the full range including Sprout→Sapling + activation and shielded pool growth where blocks are heaviest. +2. **Sequential merge** means each batch waits for all 8 indexes to merge + one after another — wall time is the sum, not the max. +3. **Scheduler emits one extraction per cycle** — 1000 dispatch round-trips + per batch of 1000 blocks, significant scheduling overhead. +4. The provisioner (ReadState) sustains ~465 blocks/s feeding the channel, + indicating the engine is the bottleneck, not block fetching. +5. **DB at 52 GB** — reasonable for 8 indexes. ~16.5 KB/block average + is dominated by transparent_spends and transparent_data. + +## Next Steps + +- `bench-df6f184-par-merge` running with both optimizations (batch emission + + parallel merge) for A/B comparison at the same chain heights. +- OTLP tracing to Tempo for flame graph visualization of parallelism. diff --git a/live-tests/sync-bench/src/main.rs b/live-tests/sync-bench/src/main.rs new file mode 100644 index 000000000..a9a10ff14 --- /dev/null +++ b/live-tests/sync-bench/src/main.rs @@ -0,0 +1,388 @@ +//! Sync benchmark: measures index build performance against a live Zebra. +//! +//! Usage: +//! sync-headers [block_count] [concurrency] [batch_size] +//! +//! Environment: +//! ZEBRA_RPC_URL — RPC endpoint (default: http://127.0.0.1:8232) +//! ZEBRA_STATE_DIR — Zebra cache dir. If set, uses ReadState (direct DB). +//! ZAINO_DB_PATH — LMDB path. If set, uses LMDB. If unset, in-memory. + +use std::path::PathBuf; +use std::sync::{Arc, OnceLock}; +use std::time::Instant; + +use tracing::info; + +const BENCH_TARGET: &str = "sync_bench"; + +static TRACER_PROVIDER: OnceLock = OnceLock::new(); +use zaino_backend_lmdb::{LmdbBackend, LmdbConfig}; +use zaino_indexes::indexes::headers::ID as HEADERS_ID; +use zaino_indexes::indexes::txids::ID as TXIDS_ID; +use zaino_indexes::indexes::hash_to_height::ID as HASH_HEIGHT_ID; +use zaino_indexes::indexes::txid_location::ID as TXID_LOC_ID; +use zaino_indexes::indexes::transparent_data::ID as TDATA_ID; +use zaino_indexes::indexes::sapling::ID as SAPLING_ID; +use zaino_indexes::indexes::orchard::ID as ORCHARD_ID; +use zaino_indexes::indexes::transparent_spends::ID as SPENDS_ID; +use zaino_indexes::sets::current_zaino::{self, CurrentZainoContext}; +use zaino_persistence::in_memory::InMemoryBackend; +use zaino_persistence::{Backend, Namespace}; +use zaino_primitives::types::{BlockHash, Height}; +use zaino_rpc::{RpcClient, RpcClientConfig}; +use zaino_source::{GetBlock, GetChainTip, GetPreIndexCompactBlock}; +use zaino_source_zebra_readstate::ZebraReadStateAdapter; +use zaino_source_zebra_rpc::ZebraRpcAdapter; +use zaino_sync::engine::{EngineConfig, SyncEngine}; +use zaino_sync::primitives::BlockHeight; + +// --------------------------------------------------------------------------- +// Generic sync runner — takes a block-fetching closure +// --------------------------------------------------------------------------- + +struct RunResult { + elapsed_secs: f64, + blocks_per_sec: f64, + db_size_bytes: Option, +} + +async fn run_sync( + backend: B, + fetch_block: F, + sync_from: u32, + sync_to: u32, + concurrency: usize, + batch_size: u32, + db_path: Option<&str>, +) -> RunResult +where + B: Backend + 'static, + B::Reader: 'static, + B::Writer: 'static, + F: Fn(u32) -> Fut + Send + Sync + 'static, + Fut: std::future::Future + Send, +{ + let block_count = sync_to - sync_from + 1; + let set = current_zaino::index_set(); + let config = EngineConfig { + batch_size, + start_height: BlockHeight::new(u64::from(sync_from)), + }; + let mut engine = SyncEngine::from_index_set(set, backend, config).expect("valid index set"); + + let (tx, rx) = tokio::sync::mpsc::channel::(concurrency * 2); + let start = Instant::now(); + + let fetch_block = Arc::new(fetch_block); + + tokio::spawn(async move { + let mut in_flight = futures::stream::FuturesOrdered::new(); + let mut next_to_spawn = sync_from; + let mut sent = 0u32; + + loop { + while in_flight.len() < concurrency && next_to_spawn <= sync_to { + let h = next_to_spawn; + next_to_spawn += 1; + let fetch = Arc::clone(&fetch_block); + in_flight.push_back(async move { fetch(h).await }); + } + + use futures::StreamExt; + match in_flight.next().await { + Some(ctx) => { + tx.send(ctx).await.expect("engine channel open"); + sent += 1; + + if sent % 500 == 0 || sent == block_count { + let elapsed = start.elapsed().as_secs_f64(); + let rate = sent as f64 / elapsed; + info!( + target: BENCH_TARGET, + sent, + total = block_count, + elapsed_secs = format!("{elapsed:.1}"), + blocks_per_sec = format!("{rate:.0}"), + "provisioner progress", + ); + } + } + None => break, + } + } + }); + + engine.sync_channel(rx).await.expect("sync failed"); + + let elapsed_secs = start.elapsed().as_secs_f64(); + let blocks_per_sec = block_count as f64 / elapsed_secs; + + let db_size_bytes = db_path.and_then(|p| { + std::fs::read_dir(p) + .ok()? + .filter_map(|e| e.ok()) + .filter_map(|e| e.metadata().ok().map(|m| m.len())) + .reduce(|a, b| a + b) + }); + + RunResult { + elapsed_secs, + blocks_per_sec, + db_size_bytes, + } +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +#[tokio::main] +async fn main() { + // Always initialize tracing. RUST_LOG controls verbosity (default: sync_bench=info). + // JSON output when ZAINO_LOG_JSON=1 (for Loki/Grafana), human-readable otherwise. + // OTLP export to Tempo when OTEL_EXPORTER_OTLP_ENDPOINT is set. + { + use tracing_subscriber::fmt::format::FmtSpan; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + use tracing_subscriber::Layer; + + let filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("sync_bench=info")); + + let fmt_layer = if std::env::var("ZAINO_LOG_JSON").as_deref() == Ok("1") { + tracing_subscriber::fmt::layer() + .json() + .with_span_events(FmtSpan::CLOSE) + .boxed() + } else { + tracing_subscriber::fmt::layer() + .with_span_events(FmtSpan::CLOSE) + .with_target(false) + .boxed() + }; + + let registry = tracing_subscriber::registry() + .with(filter) + .with(fmt_layer); + + if std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_ok() { + use opentelemetry::trace::TracerProvider; + + let exporter = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .build() + .expect("OTLP exporter"); + + let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder() + .with_batch_exporter(exporter) + .with_resource( + opentelemetry_sdk::Resource::builder() + .with_service_name("sync-bench") + .build(), + ) + .build(); + + let otel_layer = tracing_opentelemetry::layer() + .with_tracer(tracer_provider.tracer("sync-bench")); + + registry.with(otel_layer).init(); + + // Stash provider so we can flush on shutdown. + TRACER_PROVIDER.set(tracer_provider).ok(); + } else { + registry.init(); + } + } + + let rpc_url = std::env::var("ZEBRA_RPC_URL") + .unwrap_or_else(|_| "http://127.0.0.1:8232".to_string()); + let state_dir = std::env::var("ZEBRA_STATE_DIR").ok().map(PathBuf::from); + let db_path = std::env::var("ZAINO_DB_PATH").ok(); + + let args: Vec = std::env::args().collect(); + let n_blocks: u32 = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(100); + let concurrency: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(8); + let batch_size: u32 = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(50); + + // Determine provisioner and get chain tip. + let (provisioner_name, tip_hash, tip_height): (&str, BlockHash, Height) = + if let Some(ref dir) = state_dir { + let adapter = ZebraReadStateAdapter::open( + dir, + &zebra_chain::parameters::Network::Mainnet, + ) + .expect("open zebra readstate failed"); + let (hash, height) = adapter.get_chain_tip().await.expect("get_chain_tip"); + ("zebra-readstate", hash, height) + } else { + let rpc = RpcClient::new(RpcClientConfig { + url: rpc_url.clone(), + auth: None, + ..Default::default() + }) + .expect("RPC client creation failed"); + let adapter = ZebraRpcAdapter::new(rpc); + let (hash, height) = adapter.get_chain_tip().await.expect("get_chain_tip"); + ("zebra-rpc", hash, height) + }; + + let tip_u32 = u32::from(tip_height); + // SYNC_FROM overrides the start height (for targeting specific chain ranges). + // Without it, syncs the last n_blocks before tip. + let sync_from = std::env::var("SYNC_FROM") + .ok() + .and_then(|s| s.parse::().ok()) + .unwrap_or_else(|| tip_u32.saturating_sub(n_blocks - 1)); + let sync_to = sync_from + n_blocks - 1; + let block_count = sync_to - sync_from + 1; + let backend_name = db_path.as_deref().unwrap_or("in-memory"); + + // Log configuration. + let state_dir_display = state_dir.as_ref().map(|d| d.display().to_string()); + info!( + target: BENCH_TARGET, + provisioner = provisioner_name, + backend = backend_name, + chain_tip_height = %tip_height, + chain_tip_hash = %tip_hash, + sync_from, + sync_to, + block_count, + concurrency, + batch_size, + state_dir = state_dir_display.as_deref().unwrap_or("n/a"), + rpc_url = if state_dir.is_none() { rpc_url.as_str() } else { "n/a" }, + "sync-bench configuration", + ); + + // Log index set description. + let set = current_zaino::index_set(); + let descriptions = set.describe(); + info!( + target: BENCH_TARGET, + count = descriptions.len(), + indexes = ?descriptions, + "index set: current_zaino", + ); + + let ns_headers: Namespace = HEADERS_ID.into(); + let ns_txids: Namespace = TXIDS_ID.into(); + let ns_hash_height: Namespace = HASH_HEIGHT_ID.into(); + let ns_txid_loc: Namespace = TXID_LOC_ID.into(); + let ns_tdata: Namespace = TDATA_ID.into(); + let ns_sapling: Namespace = SAPLING_ID.into(); + let ns_orchard: Namespace = ORCHARD_ID.into(); + let ns_spends: Namespace = SPENDS_ID.into(); + let ns_meta = Namespace::new("_engine_meta"); + + // Build the fetch closure based on provisioner type. + let result = match (state_dir.as_ref(), db_path.as_deref()) { + // ReadState + LMDB + (Some(dir), Some(lmdb_path)) => { + let adapter = Arc::new( + ZebraReadStateAdapter::open(dir, &zebra_chain::parameters::Network::Mainnet) + .expect("open readstate"), + ); + let backend = LmdbBackend::open(LmdbConfig { + path: lmdb_path.into(), + map_size_bytes: 120 << 30, // 120 GB + namespaces: vec![ns_headers, ns_spends, ns_txids, ns_hash_height, ns_txid_loc, ns_tdata, ns_sapling, ns_orchard, ns_meta], + }) + .expect("LMDB open failed"); + + let fetch = move |h: u32| { + let adapter = Arc::clone(&adapter); + async move { + let height = Height::try_from(h).expect("valid"); + let compact = adapter.get_pre_index_compact_block(height).await.expect("get_pre_index_compact_block"); + current_zaino::context_from_pre_index_compact_block(&compact) + } + }; + run_sync(backend, fetch, sync_from, sync_to, concurrency, batch_size, Some(lmdb_path)) + .await + } + // ReadState + in-memory + (Some(dir), None) => { + let adapter = Arc::new( + ZebraReadStateAdapter::open(dir, &zebra_chain::parameters::Network::Mainnet) + .expect("open readstate"), + ); + let backend = InMemoryBackend::new(); + let fetch = move |h: u32| { + let adapter = Arc::clone(&adapter); + async move { + let height = Height::try_from(h).expect("valid"); + let compact = adapter.get_pre_index_compact_block(height).await.expect("get_pre_index_compact_block"); + current_zaino::context_from_pre_index_compact_block(&compact) + } + }; + run_sync(backend, fetch, sync_from, sync_to, concurrency, batch_size, None).await + } + // RPC + LMDB + (None, Some(lmdb_path)) => { + let rpc = RpcClient::new(RpcClientConfig { + url: rpc_url.clone(), + auth: None, + ..Default::default() + }) + .expect("rpc client"); + let adapter = Arc::new(ZebraRpcAdapter::new(rpc)); + let backend = LmdbBackend::open(LmdbConfig { + path: lmdb_path.into(), + map_size_bytes: 120 << 30, // 120 GB + namespaces: vec![ns_headers, ns_spends, ns_txids, ns_hash_height, ns_txid_loc, ns_tdata, ns_sapling, ns_orchard, ns_meta], + }) + .expect("LMDB open failed"); + + let fetch = move |h: u32| { + let adapter = Arc::clone(&adapter); + async move { + let height = Height::try_from(h).expect("valid"); + let block = adapter.get_block(height).await.expect("get_block"); + current_zaino::context_from_block(&block) + } + }; + run_sync(backend, fetch, sync_from, sync_to, concurrency, batch_size, Some(lmdb_path)) + .await + } + // RPC + in-memory + (None, None) => { + let rpc = RpcClient::new(RpcClientConfig { + url: rpc_url.clone(), + auth: None, + ..Default::default() + }) + .expect("rpc client"); + let adapter = Arc::new(ZebraRpcAdapter::new(rpc)); + let backend = InMemoryBackend::new(); + let fetch = move |h: u32| { + let adapter = Arc::clone(&adapter); + async move { + let height = Height::try_from(h).expect("valid"); + let block = adapter.get_block(height).await.expect("get_block"); + current_zaino::context_from_block(&block) + } + }; + run_sync(backend, fetch, sync_from, sync_to, concurrency, batch_size, None).await + } + }; + + info!( + target: BENCH_TARGET, + total_blocks = block_count, + total_time_secs = format!("{:.2}", result.elapsed_secs), + blocks_per_sec = format!("{:.1}", result.blocks_per_sec), + db_size_mb = result.db_size_bytes.map(|s| format!("{:.2}", s as f64 / (1024.0 * 1024.0))), + bytes_per_block = result.db_size_bytes.map(|s| format!("{:.0}", s as f64 / block_count as f64)), + "sync-bench results", + ); + + // Flush any pending OTLP spans before exit. + if let Some(provider) = TRACER_PROVIDER.get() { + if let Err(e) = provider.shutdown() { + eprintln!("OTLP shutdown error: {e}"); + } + } +} diff --git a/live-tests/sync-bench/src/provision_bench.rs b/live-tests/sync-bench/src/provision_bench.rs new file mode 100644 index 000000000..95b425d1b --- /dev/null +++ b/live-tests/sync-bench/src/provision_bench.rs @@ -0,0 +1,276 @@ +//! Provision-only benchmark: measures block fetch + context extraction cost +//! without the sync engine. Establishes a throughput baseline for the +//! provisioner pipeline in isolation. +//! +//! Usage: +//! provision-bench [block_count] [concurrency] +//! +//! Environment: +//! ZEBRA_STATE_DIR — Zebra cache dir (required for ReadState). +//! SYNC_FROM — Start height (default: tip - block_count). +//! BENCH_MODE — What to measure (default: all). Options: +//! raw_block — get_block only, drop the Block +//! headers_only — get_block + HeadersOnlyContext extraction +//! headers_spends — get_block + HeadersAndSpendsContext extraction +//! current_zaino — get_block + CurrentZainoContext extraction +//! all — run all modes sequentially + +use std::sync::Arc; +use std::time::Instant; + +use futures::StreamExt; +use zaino_primitives::types::Height; +use zaino_source::{GetBlock, GetPreIndexCompactBlock}; +use zaino_source_zebra_readstate::ZebraReadStateAdapter; + +const TARGET: &str = "provision_bench"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumString)] +#[strum(serialize_all = "snake_case")] +enum BenchMode { + RawBlock, + CompactBlock, + HeadersOnly, + HeadersSpends, + CurrentZaino, + All, +} + +struct ModeResult { + mode: BenchMode, + block_count: u32, + elapsed_secs: f64, + blocks_per_sec: f64, +} + +async fn run_provision( + fetch: F, + sync_from: u32, + sync_to: u32, + concurrency: usize, + mode: BenchMode, +) -> ModeResult +where + F: Fn(u32) -> Fut + Send + Sync + 'static, + Fut: std::future::Future + Send, +{ + let block_count = sync_to - sync_from + 1; + let fetch = Arc::new(fetch); + let start = Instant::now(); + + let mut in_flight = futures::stream::FuturesOrdered::new(); + let mut next_to_spawn = sync_from; + let mut completed = 0u32; + + loop { + while in_flight.len() < concurrency && next_to_spawn <= sync_to { + let h = next_to_spawn; + next_to_spawn += 1; + let fetch = Arc::clone(&fetch); + in_flight.push_back(async move { fetch(h).await }); + } + + match in_flight.next().await { + Some(()) => { + completed += 1; + if completed % 5000 == 0 || completed == block_count { + let elapsed = start.elapsed().as_secs_f64(); + let rate = completed as f64 / elapsed; + tracing::info!( + target: TARGET, + mode = %mode, + completed, + total = block_count, + blocks_per_sec = format!("{rate:.0}"), + "progress", + ); + } + } + None => break, + } + } + + let elapsed_secs = start.elapsed().as_secs_f64(); + ModeResult { + mode, + block_count, + elapsed_secs, + blocks_per_sec: block_count as f64 / elapsed_secs, + } +} + +#[tokio::main] +async fn main() { + // Always init tracing. + { + use tracing_subscriber::fmt::format::FmtSpan; + let filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("provision_bench=info")); + + if std::env::var("ZAINO_LOG_JSON").as_deref() == Ok("1") { + tracing_subscriber::fmt() + .json() + .with_span_events(FmtSpan::CLOSE) + .with_env_filter(filter) + .init(); + } else { + tracing_subscriber::fmt() + .with_span_events(FmtSpan::CLOSE) + .with_target(false) + .with_env_filter(filter) + .init(); + } + } + + let state_dir = std::env::var("ZEBRA_STATE_DIR") + .expect("ZEBRA_STATE_DIR is required for provision-bench"); + let args: Vec = std::env::args().collect(); + let n_blocks: u32 = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(100_000); + let concurrency: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(16); + let bench_mode: BenchMode = std::env::var("BENCH_MODE") + .unwrap_or_else(|_| "all".to_string()) + .parse() + .expect("invalid BENCH_MODE; options: raw_block, headers_only, headers_spends, current_zaino, all"); + + let adapter = Arc::new( + ZebraReadStateAdapter::open( + state_dir.as_ref(), + &zebra_chain::parameters::Network::Mainnet, + ) + .expect("open zebra readstate failed"), + ); + + let (_, tip_height) = zaino_source::GetChainTip::get_chain_tip(adapter.as_ref()) + .await + .expect("get_chain_tip"); + let tip_u32 = u32::from(tip_height); + + let sync_from = std::env::var("SYNC_FROM") + .ok() + .and_then(|s| s.parse::().ok()) + .unwrap_or_else(|| tip_u32.saturating_sub(n_blocks - 1)); + let sync_to = sync_from + n_blocks - 1; + let block_count = sync_to - sync_from + 1; + + tracing::info!( + target: TARGET, + sync_from, + sync_to, + block_count, + concurrency, + bench_mode = %bench_mode, + chain_tip = tip_u32, + "provision-bench configuration", + ); + + let modes = match bench_mode { + BenchMode::All => vec![ + BenchMode::RawBlock, + BenchMode::CompactBlock, + BenchMode::HeadersOnly, + BenchMode::HeadersSpends, + BenchMode::CurrentZaino, + ], + single => vec![single], + }; + + let mut results = Vec::new(); + + for mode in modes { + tracing::info!(target: TARGET, mode = %mode, "starting mode"); + + let result = match mode { + BenchMode::RawBlock => { + let a = Arc::clone(&adapter); + run_provision( + move |h| { + let a = Arc::clone(&a); + async move { + let height = Height::try_from(h).expect("valid"); + let _block = a.get_block(height).await.expect("get_block"); + } + }, + sync_from, sync_to, concurrency, BenchMode::RawBlock, + ).await + } + BenchMode::CompactBlock => { + let a = Arc::clone(&adapter); + run_provision( + move |h| { + let a = Arc::clone(&a); + async move { + let height = Height::try_from(h).expect("valid"); + let _compact = a.get_pre_index_compact_block(height).await.expect("get_pre_index_compact_block"); + } + }, + sync_from, sync_to, concurrency, BenchMode::CompactBlock, + ).await + } + BenchMode::HeadersOnly => { + // Uses BlockHeader request — no transaction deserialization. + let a = Arc::clone(&adapter); + run_provision( + move |h| { + let a = Arc::clone(&a); + async move { + let height = Height::try_from(h).expect("valid"); + let _header = a.get_block_header(height).await.expect("get_block_header"); + } + }, + sync_from, sync_to, concurrency, BenchMode::HeadersOnly, + ).await + } + BenchMode::HeadersSpends => { + // Uses CompactBlock — has transparent outpoints, no proofs/scripts. + let a = Arc::clone(&adapter); + run_provision( + move |h| { + let a = Arc::clone(&a); + async move { + let height = Height::try_from(h).expect("valid"); + let _compact = a.get_pre_index_compact_block(height).await.expect("get_pre_index_compact_block"); + } + }, + sync_from, sync_to, concurrency, BenchMode::HeadersSpends, + ).await + } + BenchMode::CurrentZaino => { + // Uses CompactBlock — has everything the current index set needs. + let a = Arc::clone(&adapter); + run_provision( + move |h| { + let a = Arc::clone(&a); + async move { + let height = Height::try_from(h).expect("valid"); + let _compact = a.get_pre_index_compact_block(height).await.expect("get_pre_index_compact_block"); + } + }, + sync_from, sync_to, concurrency, BenchMode::CurrentZaino, + ).await + } + BenchMode::All => unreachable!("expanded above"), + }; + + tracing::info!( + target: TARGET, + mode = %result.mode, + block_count = result.block_count, + elapsed_secs = format!("{:.2}", result.elapsed_secs), + blocks_per_sec = format!("{:.1}", result.blocks_per_sec), + "mode complete", + ); + results.push(result); + } + + // Summary + tracing::info!(target: TARGET, "provision-bench summary"); + for r in &results { + tracing::info!( + target: TARGET, + mode = %r.mode, + blocks_per_sec = format!("{:.1}", r.blocks_per_sec), + elapsed_secs = format!("{:.2}", r.elapsed_secs), + "result", + ); + } +} diff --git a/live-tests/zallet-fit/Cargo.toml b/live-tests/zallet-fit/Cargo.toml new file mode 100644 index 000000000..7027075ae --- /dev/null +++ b/live-tests/zallet-fit/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "zallet-fit" +version = "0.1.0" +edition = "2021" +publish = false +description = "Compile-time proof that zaino-wallet can service zallet's Chain/ChainView trait. Scaffold: mirrors zallet's trait shape; swap for the real git dep once stable." + +[dependencies] +zaino-wallet = { path = "../../packages/zaino-wallet" } +zaino-service = { path = "../../packages/zaino-service" } +futures = "0.3" + +# The real acid test — flip to this once the shapes stabilise and prove the impl +# against zallet's ACTUAL trait rather than the mirror below: +# zallet-core = { git = "https://github.com/zcash/zallet", branch = "main" } diff --git a/live-tests/zallet-fit/src/lib.rs b/live-tests/zallet-fit/src/lib.rs new file mode 100644 index 000000000..14efc0705 --- /dev/null +++ b/live-tests/zallet-fit/src/lib.rs @@ -0,0 +1,268 @@ +//! Compile-time proof that `zaino-wallet`'s published client can service +//! zallet's `Chain` / `ChainView` trait. +//! +//! SCAFFOLD: the `mirror` module below is a faithful *shape* copy of +//! zcash/zallet@main `zallet-core/src/components/chain.rs` (default, no +//! `spend-index` feature), with placeholder foreign types so this crate builds +//! without zallet's full dependency tree. The real acid test — implementing +//! zallet's ACTUAL trait — is one line away: flip the commented `zallet-core` +//! git dependency in `Cargo.toml` and delete the mirror. +//! +//! What this file proves *today*: every method of zallet's trait has a home on +//! our client (see the `→` mapping comments). What it cannot prove until the +//! real dep is wired: exact type/lifetime/bound agreement. +#![forbid(unsafe_code)] +#![allow(dead_code)] + +use futures::stream::BoxStream; + +use zaino_wallet::{WalletClient, WalletSnapshot}; +use zaino_service::{IndexerService, Snapshot as InnerSnapshot}; + +/// Faithful shape-mirror of zallet's backend-neutral chain interface. +mod mirror { + use futures::stream::BoxStream; + use std::future::Future; + use std::ops::Range; + + // --- placeholder foreign types (real: zcash_primitives / zcash_protocol / zebra) --- + #[derive(Clone, Copy)] + pub struct Network; + #[derive(Clone, Copy, PartialEq)] + pub struct BlockHeight(pub u32); + #[derive(Clone, Copy, PartialEq)] + pub struct BlockHash(pub [u8; 32]); + #[derive(Clone)] + pub struct Block; + #[derive(Clone)] + pub struct BlockHeader; + #[derive(Clone)] + pub struct Transaction; + #[derive(Clone, Copy)] + pub struct TxId(pub [u8; 32]); + #[derive(Clone)] + pub struct TransparentAddress; + #[derive(Clone)] + pub struct ChainState; + #[derive(Clone)] + pub struct ChainTx; + #[derive(Clone, Copy)] + pub struct ChainBlock { + pub height: BlockHeight, + pub hash: BlockHash, + } + pub struct BlockLocator(pub Vec); + #[derive(Debug)] + pub struct ChainError; + #[derive(Debug)] + pub struct Error; + #[derive(Clone, Copy)] + pub enum TransactionStatus { + Mined(BlockHeight), + Orphaned, + Unknown, + } + #[derive(Clone)] + pub struct ReportedUpgrade; + /// Real sig uses `CommitmentTreeRoot` etc.; simplified here. + #[derive(Clone)] + pub struct SubtreeRoot; + + /// zallet's engine handle. (Mirror.) + pub trait Chain: Clone + Send + Sync + 'static { + type View: ChainView; + + fn params(&self) -> &Network; + fn reported_upgrades( + &self, + ) -> impl Future, Error>> + Send; + fn broadcast_transaction( + &self, + tx: &Transaction, + ) -> impl Future> + Send; + fn get_sapling_subtree_roots( + &self, + ) -> impl Future, ChainError>> + Send; + fn get_orchard_subtree_roots( + &self, + ) -> impl Future, ChainError>> + Send; + fn get_ironwood_subtree_roots( + &self, + ) -> impl Future, ChainError>> + Send; + fn snapshot(&self) -> impl Future> + Send; + } + + /// zallet's pinned view. (Mirror; default no-`spend-index` method set.) + pub trait ChainView: Clone + Send + Sync + 'static { + fn tip(&self) -> impl Future> + Send; + fn find_fork_point( + &self, + locator: &BlockLocator, + ) -> impl Future, ChainError>> + Send; + fn tree_state_as_of( + &self, + height: BlockHeight, + ) -> impl Future, ChainError>> + Send; + fn get_block_header( + &self, + height: BlockHeight, + ) -> impl Future, ChainError>> + Send; + fn get_block( + &self, + height: BlockHeight, + ) -> impl Future, ChainError>> + Send; + fn stream_blocks_to_tip( + &self, + start: BlockHeight, + ) -> BoxStream<'_, Result>; + fn stream_blocks( + &self, + range: &Range, + ) -> BoxStream<'_, Result>; + fn get_mempool_stream( + &self, + ) -> impl Future>, ChainError>> + Send; + fn get_transaction( + &self, + txid: TxId, + ) -> impl Future, ChainError>> + Send; + fn get_transaction_status( + &self, + txid: TxId, + ) -> impl Future> + Send; + fn get_address_unspent_outpoints( + &self, + address: &TransparentAddress, + ) -> impl Future, ChainError>> + Send; + fn get_address_tx_ids( + &self, + address: &TransparentAddress, + range: Range, + ) -> impl Future, ChainError>> + Send; + } +} + +use mirror::*; + +/// zallet's `backends/zaino` adapter, in our tree: wraps our published client +/// and presents it as zallet's `Chain`. +#[derive(Clone)] +pub struct ZainoBackend { + client: WalletClient, + network: Network, +} + +/// The view side: wraps our published snapshot. +#[derive(Clone)] +pub struct ZainoView { + snap: WalletSnapshot, +} + +impl Chain for ZainoBackend { + type View = ZainoView; + + fn params(&self) -> &Network { + &self.network + } + + // → WalletClient::reported_upgrades + async fn reported_upgrades(&self) -> Result, Error> { + todo!() + } + + // → WalletClient::broadcast (serialise their &Transaction to bytes, drop the txid) + async fn broadcast_transaction(&self, _tx: &Transaction) -> Result<(), ChainError> { + todo!() + } + + // → WalletClient::{sapling,orchard,ironwood}_subtree_roots + async fn get_sapling_subtree_roots(&self) -> Result, ChainError> { + todo!() + } + async fn get_orchard_subtree_roots(&self) -> Result, ChainError> { + todo!() + } + async fn get_ironwood_subtree_roots(&self) -> Result, ChainError> { + todo!() + } + + // → WalletClient::snapshot + async fn snapshot(&self) -> Result { + todo!() + } +} + +impl ChainView for ZainoView { + // → WalletSnapshot::tip + async fn tip(&self) -> Result { + todo!() + } + // → WalletSnapshot::fork_point + async fn find_fork_point( + &self, + _locator: &BlockLocator, + ) -> Result, ChainError> { + todo!() + } + // → WalletSnapshot::treestate + async fn tree_state_as_of( + &self, + _height: BlockHeight, + ) -> Result, ChainError> { + todo!() + } + // → WalletSnapshot::raw_block_header + async fn get_block_header( + &self, + _height: BlockHeight, + ) -> Result, ChainError> { + todo!() + } + // → WalletSnapshot::raw_block + async fn get_block(&self, _height: BlockHeight) -> Result, ChainError> { + todo!() + } + // → WalletSnapshot::stream_blocks_to_tip + fn stream_blocks_to_tip(&self, _start: BlockHeight) -> BoxStream<'_, Result> { + todo!() + } + // → WalletSnapshot::stream_blocks + fn stream_blocks( + &self, + _range: &std::ops::Range, + ) -> BoxStream<'_, Result> { + todo!() + } + // → WalletSnapshot::mempool_stream + async fn get_mempool_stream( + &self, + ) -> Result>, ChainError> { + todo!() + } + // → WalletSnapshot::raw_transaction + async fn get_transaction(&self, _txid: TxId) -> Result, ChainError> { + todo!() + } + // → WalletSnapshot::transaction_status + async fn get_transaction_status( + &self, + _txid: TxId, + ) -> Result { + todo!() + } + // → WalletSnapshot::unspent_outpoints + async fn get_address_unspent_outpoints( + &self, + _address: &TransparentAddress, + ) -> Result, ChainError> { + todo!() + } + // → WalletSnapshot::address_tx_ids + async fn get_address_tx_ids( + &self, + _address: &TransparentAddress, + _range: std::ops::Range, + ) -> Result, ChainError> { + todo!() + } +} diff --git a/packages/zaino-backend-lmdb/Cargo.toml b/packages/zaino-backend-lmdb/Cargo.toml new file mode 100644 index 000000000..3f8f0454c --- /dev/null +++ b/packages/zaino-backend-lmdb/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "zaino-backend-lmdb" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +description = "LMDB backend adapter implementing zaino-persistence traits." + +[dependencies] +zaino-persistence = { path = "../zaino-persistence" } +lmdb.workspace = true + +[dev-dependencies] +tempfile = "3" + +[lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" diff --git a/packages/zaino-backend-lmdb/src/lib.rs b/packages/zaino-backend-lmdb/src/lib.rs new file mode 100644 index 000000000..355cf8eb9 --- /dev/null +++ b/packages/zaino-backend-lmdb/src/lib.rs @@ -0,0 +1,410 @@ +//! LMDB backend adapter for zaino-persistence. +//! +//! One LMDB named database per [`Namespace`]. Atomic commits via +//! write transactions. Zero-copy reads via memory-mapped files. +//! +//! ```ignore +//! let backend = LmdbBackend::open(LmdbConfig { +//! path: "/tmp/zaino-db".into(), +//! map_size_bytes: 1 << 30, // 1 GB +//! namespaces: &["headers", "tx_count", "_engine_meta"], +//! })?; +//! ``` + +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; + +use lmdb::{ + Cursor, Database, DatabaseFlags, Environment, EnvironmentFlags, Transaction, WriteFlags, +}; +use zaino_persistence::{ + Backend, BackendReader, BackendWriter, CommitError, FlushError, Namespace, OpenError, + RawKey, RawValue, ReadError, WriteOp, +}; + +/// Configuration for [`LmdbBackend`]. +pub struct LmdbConfig { + /// Path to the LMDB environment directory. + pub path: PathBuf, + /// Maximum database size in bytes. LMDB requires this upfront. + /// Defaults to 1 GB if not set. + pub map_size_bytes: usize, + /// Namespaces to create (one LMDB named database each). + pub namespaces: Vec, +} + +impl Default for LmdbConfig { + fn default() -> Self { + Self { + path: PathBuf::from("./zaino-db"), + map_size_bytes: 1 << 30, // 1 GB + namespaces: Vec::new(), + } + } +} + +/// LMDB-backed persistence backend. +/// +/// Holds the environment and a map of namespace → LMDB database handle. +/// Thread-safe: LMDB allows concurrent read transactions and serializes +/// writes internally. +pub struct LmdbBackend { + env: Arc, + dbs: HashMap, +} + +impl LmdbBackend { + /// Open or create an LMDB environment with the given namespaces. + pub fn open(config: LmdbConfig) -> Result { + std::fs::create_dir_all(&config.path) + .map_err(|e| OpenError::Unavailable(format!("create dir: {e}")))?; + + let env = Environment::new() + .set_max_dbs(config.namespaces.len() as u32 + 1) + .set_map_size(config.map_size_bytes) + .set_flags( + // NO_TLS: allows sharing read transactions across threads. + // NO_READAHEAD: better for random-access patterns. + // NO_SYNC: skip fsync per commit — we flush explicitly at + // batch boundaries via Backend::flush(). Much faster for + // batch writes; crash between flushes loses at most one batch + // (the watermark ensures clean resume). + EnvironmentFlags::NO_TLS + | EnvironmentFlags::NO_READAHEAD + | EnvironmentFlags::NO_SYNC, + ) + .open(&config.path) + .map_err(|e| OpenError::Unavailable(format!("lmdb open: {e}")))?; + + let mut dbs = HashMap::new(); + for ns in &config.namespaces { + let db = open_or_create_db(&env, ns.as_str()) + .map_err(|e| OpenError::Unavailable(format!("create db {ns}: {e}")))?; + dbs.insert(*ns, db); + } + + Ok(Self { + env: Arc::new(env), + dbs, + }) + } + + fn resolve_db(&self, namespace: Namespace) -> Result { + self.dbs + .get(&namespace) + .copied() + .ok_or_else(|| ReadError::NamespaceNotFound(namespace.to_string())) + } +} + +fn open_or_create_db(env: &Environment, name: &str) -> Result { + match env.open_db(Some(name)) { + Ok(db) => Ok(db), + Err(lmdb::Error::NotFound) => env.create_db(Some(name), DatabaseFlags::empty()), + Err(e) => Err(e), + } +} + +impl Backend for LmdbBackend { + type Reader = LmdbReader; + type Writer = LmdbWriter; + + fn reader(&self) -> Result { + Ok(LmdbReader { + env: Arc::clone(&self.env), + dbs: self.dbs.clone(), + }) + } + + fn writer(&self) -> Result { + Ok(LmdbWriter { + env: Arc::clone(&self.env), + dbs: self.dbs.clone(), + }) + } + + fn flush(&self) -> Result<(), FlushError> { + self.env + .sync(true) + .map_err(|e| FlushError::IoError(format!("lmdb sync: {e}"))) + } +} + +/// LMDB read handle. +pub struct LmdbReader { + env: Arc, + dbs: HashMap, +} + +impl LmdbReader { + fn resolve_db(&self, namespace: Namespace) -> Result { + self.dbs + .get(&namespace) + .copied() + .ok_or_else(|| ReadError::NamespaceNotFound(namespace.to_string())) + } +} + +impl BackendReader for LmdbReader { + fn get(&self, namespace: Namespace, key: &[u8]) -> Result, ReadError> { + let db = self.resolve_db(namespace)?; + let txn = self + .env + .begin_ro_txn() + .map_err(|e| ReadError::ReadFailed(format!("begin ro txn: {e}")))?; + + match txn.get(db, &key) { + Ok(bytes) => Ok(Some(bytes.to_vec())), + Err(lmdb::Error::NotFound) => Ok(None), + Err(e) => Err(ReadError::ReadFailed(format!("get: {e}"))), + } + } + + fn scan(&self, namespace: Namespace) -> Result, ReadError> { + let db = self.resolve_db(namespace)?; + let txn = self + .env + .begin_ro_txn() + .map_err(|e| ReadError::ReadFailed(format!("begin ro txn: {e}")))?; + + let mut cursor = txn + .open_ro_cursor(db) + .map_err(|e| ReadError::ReadFailed(format!("open cursor: {e}")))?; + + let entries: Vec<(RawKey, RawValue)> = cursor + .iter() + .map(|(k, v)| (k.to_vec(), v.to_vec())) + .collect(); + + Ok(entries) + } +} + +/// LMDB write handle. +pub struct LmdbWriter { + env: Arc, + dbs: HashMap, +} + +impl LmdbWriter { + fn resolve_db(&self, namespace: Namespace) -> Result { + self.dbs + .get(&namespace) + .copied() + .ok_or_else(|| CommitError::NamespaceNotFound(namespace.to_string())) + } +} + +impl BackendWriter for LmdbWriter { + fn commit(&mut self, ops: Vec) -> Result<(), CommitError> { + let mut txn = self + .env + .begin_rw_txn() + .map_err(|e| CommitError::WriteFailed(format!("begin rw txn: {e}")))?; + + for op in ops { + match op { + WriteOp::Put { + namespace, + key, + value, + } => { + let db = self.resolve_db(namespace)?; + txn.put(db, &key, &value, WriteFlags::empty()) + .map_err(|e| CommitError::WriteFailed(format!("put: {e}")))?; + } + WriteOp::Delete { namespace, key } => { + let db = self.resolve_db(namespace)?; + match txn.del(db, &key, None) { + Ok(()) | Err(lmdb::Error::NotFound) => {} + Err(e) => { + return Err(CommitError::WriteFailed(format!("delete: {e}"))); + } + } + } + } + } + + txn.commit() + .map_err(|e| CommitError::WriteFailed(format!("commit: {e}")))?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_config(dir: &std::path::Path, namespaces: Vec) -> LmdbConfig { + LmdbConfig { + path: dir.to_path_buf(), + map_size_bytes: 1 << 20, // 1 MB for tests + namespaces, + } + } + + #[test] + fn open_and_write_read() { + let tmp = tempfile::tempdir().expect("tempdir"); + let ns = Namespace::new("test_ns"); + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("open"); + + // Write + let mut writer = backend.writer().expect("writer"); + writer + .commit(vec![WriteOp::Put { + namespace: ns, + key: b"hello".to_vec(), + value: b"world".to_vec(), + }]) + .expect("commit"); + + // Read + let reader = backend.reader().expect("reader"); + let val = reader.get(ns, b"hello").expect("get").expect("exists"); + assert_eq!(val, b"world"); + } + + #[test] + fn scan_returns_all_entries() { + let tmp = tempfile::tempdir().expect("tempdir"); + let ns = Namespace::new("scan_ns"); + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("open"); + + let mut writer = backend.writer().expect("writer"); + writer + .commit(vec![ + WriteOp::Put { + namespace: ns, + key: b"a".to_vec(), + value: b"1".to_vec(), + }, + WriteOp::Put { + namespace: ns, + key: b"b".to_vec(), + value: b"2".to_vec(), + }, + WriteOp::Put { + namespace: ns, + key: b"c".to_vec(), + value: b"3".to_vec(), + }, + ]) + .expect("commit"); + + let reader = backend.reader().expect("reader"); + let entries = reader.scan(ns).expect("scan"); + assert_eq!(entries.len(), 3); + // LMDB returns in key order + assert_eq!(entries[0], (b"a".to_vec(), b"1".to_vec())); + assert_eq!(entries[2], (b"c".to_vec(), b"3".to_vec())); + } + + #[test] + fn get_missing_key_returns_none() { + let tmp = tempfile::tempdir().expect("tempdir"); + let ns = Namespace::new("empty_ns"); + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("open"); + + let reader = backend.reader().expect("reader"); + assert!(reader.get(ns, b"nope").expect("get").is_none()); + } + + #[test] + fn unknown_namespace_is_error() { + let tmp = tempfile::tempdir().expect("tempdir"); + let backend = + LmdbBackend::open(test_config(tmp.path(), vec![Namespace::new("known")])).expect("open"); + + let reader = backend.reader().expect("reader"); + let err = reader.get(Namespace::new("unknown"), b"key").unwrap_err(); + assert!(matches!(err, ReadError::NamespaceNotFound(_))); + } + + #[test] + fn delete_removes_entry() { + let tmp = tempfile::tempdir().expect("tempdir"); + let ns = Namespace::new("del_ns"); + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("open"); + + let mut writer = backend.writer().expect("writer"); + writer + .commit(vec![WriteOp::Put { + namespace: ns, + key: b"gone".to_vec(), + value: b"soon".to_vec(), + }]) + .expect("put"); + + writer + .commit(vec![WriteOp::Delete { + namespace: ns, + key: b"gone".to_vec(), + }]) + .expect("delete"); + + let reader = backend.reader().expect("reader"); + assert!(reader.get(ns, b"gone").expect("get").is_none()); + } + + #[test] + fn atomic_commit_all_or_nothing() { + let tmp = tempfile::tempdir().expect("tempdir"); + let ns = Namespace::new("atomic_ns"); + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("open"); + + // Put two entries in one commit + let mut writer = backend.writer().expect("writer"); + writer + .commit(vec![ + WriteOp::Put { + namespace: ns, + key: b"k1".to_vec(), + value: b"v1".to_vec(), + }, + WriteOp::Put { + namespace: ns, + key: b"k2".to_vec(), + value: b"v2".to_vec(), + }, + ]) + .expect("commit"); + + let reader = backend.reader().expect("reader"); + assert!(reader.get(ns, b"k1").expect("get").is_some()); + assert!(reader.get(ns, b"k2").expect("get").is_some()); + } + + #[test] + fn reopen_persists_data() { + let tmp = tempfile::tempdir().expect("tempdir"); + let ns = Namespace::new("persist_ns"); + + // Session 1: write + { + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("open"); + let mut writer = backend.writer().expect("writer"); + writer + .commit(vec![WriteOp::Put { + namespace: ns, + key: b"durable".to_vec(), + value: b"yes".to_vec(), + }]) + .expect("commit"); + backend.flush().expect("flush"); + } + + // Session 2: read + { + let backend = LmdbBackend::open(test_config(tmp.path(), vec![ns])).expect("reopen"); + let reader = backend.reader().expect("reader"); + let val = reader + .get(ns, b"durable") + .expect("get") + .expect("persisted"); + assert_eq!(val, b"yes"); + } + } +} diff --git a/packages/zaino-convert-zebra/Cargo.toml b/packages/zaino-convert-zebra/Cargo.toml new file mode 100644 index 000000000..d3be38d84 --- /dev/null +++ b/packages/zaino-convert-zebra/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "zaino-convert-zebra" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +description = "Convert zebra-chain types to zaino-primitives domain types." + +[dependencies] +zaino-primitives.workspace = true +zebra-chain = { workspace = true } +thiserror.workspace = true + +[lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" diff --git a/packages/zaino-convert-zebra/src/lib.rs b/packages/zaino-convert-zebra/src/lib.rs new file mode 100644 index 000000000..1892e76cb --- /dev/null +++ b/packages/zaino-convert-zebra/src/lib.rs @@ -0,0 +1,242 @@ +//! Conversion: `zebra_chain` types → `zaino_primitives` domain types. +//! +//! Each function maps one zebra type to one domain type. +//! The `block_from_zebra` entry point composes them. + +use zaino_primitives::types::{ + Block, BlockCommitments, BlockHash, BlockHeader, ChainMetadata, EncryptedCiphertext, + EphemeralKey, Height, MerkleRoot, NoteCommitment, Nullifier, OrchardAction, OrchardData, + PreIndexCompactBlock, PreIndexCompactTx, SaplingData, SaplingOutput, SaplingSpend, Script, + SignedZatoshis, Transaction, TransactionHash, TransparentData, TransparentInput, + TransparentOutput, Zatoshis, +}; + +/// Errors during conversion from zebra types. +#[derive(Debug, thiserror::Error)] +pub enum ConvertError { + /// Block height couldn't be extracted or validated. + #[error("height: {0}")] + Height(String), + /// A value exceeded protocol limits. + #[error("value overflow: {0}")] + Value(String), +} + +/// Convert a zebra block into a domain [`Block`]. +pub fn block_from_zebra( + zb: &zebra_chain::block::Block, + sapling_tree_size: u32, + orchard_tree_size: u32, +) -> Result { + Ok(Block { + header: header_from_zebra(&zb)?, + transactions: zb + .transactions + .iter() + .enumerate() + .map(|(i, tx)| transaction_from_zebra(tx, i as u32)) + .collect::, _>>()?, + chain_metadata: ChainMetadata { + sapling_tree_size, + orchard_tree_size, + }, + }) +} + +/// Convert just the header — skips all transaction parsing. +/// Much faster for header-only indexes on large blocks. +pub fn header_from_zebra(zb: &zebra_chain::block::Block) -> Result { + let h = &zb.header; + let height = zb + .coinbase_height() + .ok_or_else(|| ConvertError::Height("no coinbase height".into()))?; + + Ok(BlockHeader { + hash: BlockHash::from(zb.hash().0), + prev_hash: BlockHash::from(h.previous_block_hash.0), + height: Height::try_from(height.0).map_err(|e| ConvertError::Height(e.to_string()))?, + time: h.time.timestamp() as u32, + merkle_root: MerkleRoot::from(h.merkle_root.0), + block_commitments: BlockCommitments::from(*h.commitment_bytes), + // TODO: upstream PR to zebra adding CompactDifficulty::to_bits() -> u32. + // Workaround: round-trip through display-order bytes. + bits: u32::from_be_bytes(h.difficulty_threshold.bytes_in_display_order()), + nonce: *h.nonce, + }) +} + +/// Convert from pre-parsed header components (from ReadRequest::BlockHeader). +/// No block deserialization needed at all. +pub fn header_from_parts( + header: &zebra_chain::block::Header, + hash: zebra_chain::block::Hash, + height: zebra_chain::block::Height, +) -> Result { + Ok(BlockHeader { + hash: BlockHash::from(hash.0), + prev_hash: BlockHash::from(header.previous_block_hash.0), + height: Height::try_from(height.0).map_err(|e| ConvertError::Height(e.to_string()))?, + time: header.time.timestamp() as u32, + merkle_root: MerkleRoot::from(header.merkle_root.0), + block_commitments: BlockCommitments::from(*header.commitment_bytes), + bits: u32::from_be_bytes(header.difficulty_threshold.bytes_in_display_order()), + nonce: *header.nonce, + }) +} + +fn transaction_from_zebra( + tx: &zebra_chain::transaction::Transaction, + index: u32, +) -> Result { + Ok(Transaction { + txid: TransactionHash::from(tx.hash().0), + index, + transparent: transparent_from_zebra(tx)?, + sapling: sapling_from_zebra(tx), + orchard: orchard_from_zebra(tx), + }) +} + +fn transparent_from_zebra( + tx: &zebra_chain::transaction::Transaction, +) -> Result { + let inputs = tx + .inputs() + .iter() + .filter_map(|input| match input { + zebra_chain::transparent::Input::PrevOut { outpoint, .. } => Some(TransparentInput { + prev_txid: TransactionHash::from(outpoint.hash.0), + prev_index: outpoint.index, + }), + zebra_chain::transparent::Input::Coinbase { .. } => None, + }) + .collect(); + + let outputs = tx + .outputs() + .iter() + .map(|out| { + Ok(TransparentOutput { + value: Zatoshis::new(u64::from(out.value)) + .map_err(|e| ConvertError::Height(e.to_string()))?, + script: Script::new(out.lock_script.as_raw_bytes().to_vec()), + }) + }) + .collect::, ConvertError>>()?; + + Ok(TransparentData { inputs, outputs }) +} + +fn sapling_from_zebra(tx: &zebra_chain::transaction::Transaction) -> SaplingData { + SaplingData { + spends: tx + .sapling_nullifiers() + .map(|nf| SaplingSpend { + nullifier: Nullifier::from(<[u8; 32]>::from(*nf)), + }) + .collect(), + outputs: tx + .sapling_outputs() + .map(|out| { + let epk_bytes: [u8; 32] = (&out.ephemeral_key).into(); + let enc_bytes: [u8; 580] = out.enc_ciphertext.into(); + SaplingOutput { + cmu: NoteCommitment::from(out.cm_u.to_bytes()), + ephemeral_key: EphemeralKey::from(epk_bytes), + enc_ciphertext: EncryptedCiphertext::new(enc_bytes[..52].to_vec()), + } + }) + .collect(), + value_balance: SignedZatoshis::new( + i64::from(tx.sapling_value_balance().sapling_amount()), + ), + } +} + +/// Convert a zebra compact block into a domain [`PreIndexCompactBlock`]. +pub fn pre_index_compact_block_from_zebra( + cb: &zebra_chain::transaction::compact::CompactBlock, +) -> PreIndexCompactBlock { + PreIndexCompactBlock { + hash: BlockHash::from(cb.hash.0), + prev_hash: BlockHash::from(cb.header.previous_block_hash.0), + height: cb.height.0, + time: cb.header.time.timestamp() as u32, + bits: u32::from_be_bytes(cb.header.difficulty_threshold.bytes_in_display_order()), + transactions: cb + .transactions + .iter() + .map(pre_index_compact_tx_from_zebra) + .collect(), + } +} + +fn pre_index_compact_tx_from_zebra( + ctx: &zebra_chain::transaction::compact::CompactTransaction, +) -> PreIndexCompactTx { + PreIndexCompactTx { + txid: TransactionHash::from(ctx.txid.0), + transparent_inputs: ctx + .transparent_inputs + .iter() + .map(|inp| TransparentInput { + prev_txid: TransactionHash::from(inp.hash.0), + prev_index: inp.index, + }) + .collect(), + transparent_outputs: ctx + .transparent_outputs + .iter() + .map(|out| TransparentOutput { + value: Zatoshis::new(out.value).expect("valid zatoshis from zebra"), + script: Script::new(out.script.clone()), + }) + .collect(), + sapling_nullifiers: ctx + .sapling_nullifiers + .iter() + .map(|nf| Nullifier::from(*nf)) + .collect(), + sapling_outputs: ctx + .sapling_outputs + .iter() + .map(|o| SaplingOutput { + cmu: NoteCommitment::from(o.cmu), + ephemeral_key: EphemeralKey::from(o.ephemeral_key), + enc_ciphertext: EncryptedCiphertext::new(o.enc_ciphertext_head.to_vec()), + }) + .collect(), + orchard_actions: ctx + .orchard_actions + .iter() + .map(|a| OrchardAction { + nullifier: Nullifier::from(a.nullifier), + cmx: NoteCommitment::from(a.cmx), + ephemeral_key: EphemeralKey::from(a.ephemeral_key), + enc_ciphertext: EncryptedCiphertext::new(a.enc_ciphertext_head.to_vec()), + }) + .collect(), + } +} + +fn orchard_from_zebra(tx: &zebra_chain::transaction::Transaction) -> OrchardData { + OrchardData { + actions: tx + .orchard_actions() + .map(|act| { + let nf_bytes: [u8; 32] = act.nullifier.into(); + let epk_bytes: [u8; 32] = (&act.ephemeral_key).into(); + let enc_bytes: [u8; 580] = act.enc_ciphertext.into(); + OrchardAction { + nullifier: Nullifier::from(nf_bytes), + cmx: NoteCommitment::from(<[u8; 32]>::from(act.cm_x)), + ephemeral_key: EphemeralKey::from(epk_bytes), + enc_ciphertext: EncryptedCiphertext::new(enc_bytes[..52].to_vec()), + } + }) + .collect(), + value_balance: SignedZatoshis::new( + i64::from(tx.orchard_value_balance().orchard_amount()), + ), + } +} diff --git a/packages/zaino-core/Cargo.toml b/packages/zaino-core/Cargo.toml new file mode 100644 index 000000000..e41ad338d --- /dev/null +++ b/packages/zaino-core/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zaino-core" +version = "0.1.0" +edition = "2021" +publish = false +description = "Pure domain vocabulary + (future) pure logic for Zaino's inner driving surface. No async, no runtime." + +[dependencies] +zaino-primitives = { path = "../zaino-primitives" } diff --git a/packages/zaino-core/src/events.rs b/packages/zaino-core/src/events.rs new file mode 100644 index 000000000..cd926e7c3 --- /dev/null +++ b/packages/zaino-core/src/events.rs @@ -0,0 +1,18 @@ +//! Stream payloads for the tip and mempool subscriptions. + +use zaino_primitives::types::TransactionHash; + +use crate::refs::BlockId; + +/// The best chain moved; carries the new tip. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TipEvent { + pub tip: BlockId, +} + +/// A mempool delivery, tagged with the tip it was validated against (ADR-0001). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MempoolTx { + pub txid: TransactionHash, + pub validated_against: BlockId, +} diff --git a/packages/zaino-core/src/lib.rs b/packages/zaino-core/src/lib.rs new file mode 100644 index 000000000..db2bcc30b --- /dev/null +++ b/packages/zaino-core/src/lib.rs @@ -0,0 +1,29 @@ +//! Pure domain vocabulary for Zaino's inner driving surface. +//! +//! Types only — no async, no runtime. Domain data and identifiers come from +//! `zaino-primitives` (re-exported below); this crate adds the driving-surface +//! vocabulary primitives doesn't carry, and will host the pure logic +//! (fork-point, tx-status, serviceability derivation) later. It never awaits, +//! spawns, or sets status. +#![forbid(unsafe_code)] + +// Domain data + identifiers — the real vocabulary, from the zero-dependency crate. +pub use zaino_primitives::types::{ + AddressBalance, AddressDelta, Block, BlockHash, BlockHeader, CompactBlock, Height, + PreIndexCompactBlock, ShieldedPool, SubtreeRoot, Transaction, TransactionHash, + TransactionLocation, TransparentAddress, Treestate, Utxo, +}; + +mod events; +mod locator; +mod refs; +mod serviceability; +mod status; +mod upgrades; + +pub use events::{MempoolTx, TipEvent}; +pub use locator::{ForkPoint, Locator}; +pub use refs::{BlockId, BlockRef, HeightRange, Outpoint}; +pub use serviceability::{Capability, ServiceabilityManifest, ServiceableRange}; +pub use status::{SpendStatus, TxStatus}; +pub use upgrades::{ReportedUpgrade, UpgradeStatus}; diff --git a/packages/zaino-core/src/locator.rs b/packages/zaino-core/src/locator.rs new file mode 100644 index 000000000..956f54c30 --- /dev/null +++ b/packages/zaino-core/src/locator.rs @@ -0,0 +1,15 @@ +//! Fork-reconciliation vocabulary. + +use zaino_primitives::types::{BlockHash, Height}; + +/// A descending-by-height sample of block hashes, offered to locate the fork +/// point between a driver's view and the pinned best chain. +#[derive(Clone, Debug)] +pub struct Locator(pub Vec); + +/// The highest locator entry that sits on the pinned chain. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ForkPoint { + pub height: Height, + pub hash: BlockHash, +} diff --git a/packages/zaino-core/src/refs.rs b/packages/zaino-core/src/refs.rs new file mode 100644 index 000000000..3a32f9d6f --- /dev/null +++ b/packages/zaino-core/src/refs.rs @@ -0,0 +1,31 @@ +//! Block/output references and ranges the read surface addresses blocks by. + +use zaino_primitives::types::{BlockHash, Height, TransactionHash}; + +/// A block's height and hash together — the tip/fork "block id". +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct BlockId { + pub height: Height, + pub hash: BlockHash, +} + +/// How a caller names a block to read. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BlockRef { + Height(Height), + Hash(BlockHash), +} + +/// `[start, end)` height range. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HeightRange { + pub start: Height, + pub end: Height, +} + +/// A transparent outpoint `(txid, index)`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Outpoint { + pub txid: TransactionHash, + pub index: u32, +} diff --git a/packages/zaino-core/src/serviceability.rs b/packages/zaino-core/src/serviceability.rs new file mode 100644 index 000000000..790920ea5 --- /dev/null +++ b/packages/zaino-core/src/serviceability.rs @@ -0,0 +1,34 @@ +//! Which capabilities are answerable now, and a snapshot's serviceable range. + +use zaino_primitives::types::Height; + +/// The capability axis — one variant per capability trait, mirroring the index +/// set that backs it. Serviceability is "is this capability's index built?". +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Capability { + Blocks, + Transactions, + Treestate, + AddressHistory, + SpendStatus, + SubtreeRoots, + Mempool, + Broadcast, + ReportedUpgrades, +} + +/// For each capability, the height it is answerable up to (`None` = not yet). +/// Derived from per-index sync progress (derivation lands here later). +#[derive(Clone, Debug, Default)] +pub struct ServiceabilityManifest { + pub answerable: Vec<(Capability, Option)>, +} + +/// The heights a snapshot can answer, and the FS/NFS boundary within them. +#[derive(Clone, Copy, Debug)] +pub struct ServiceableRange { + /// Top of append-only finalised state. + pub finalized_tip: Height, + /// Pinned best-chain tip; `finalized_tip..=tip` is the non-finalised window. + pub tip: Height, +} diff --git a/packages/zaino-core/src/status.rs b/packages/zaino-core/src/status.rs new file mode 100644 index 000000000..e385ee569 --- /dev/null +++ b/packages/zaino-core/src/status.rs @@ -0,0 +1,23 @@ +//! Read-result statuses. + +use zaino_primitives::types::{Height, TransactionHash}; + +/// Where a snapshot places a transaction. Chain state only — mempool presence +/// is observed through its own stream, not reported here. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TxStatus { + Mined(Height), + Orphaned, + Unknown, +} + +/// Whether the pinned view considers a transparent output spent. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SpendStatus { + Unspent, + Spent { by: TransactionHash }, + /// Known spent, spender unresolved — caller retries (ZcashFoundation/zebra#10806). + SpentSpenderUnknown, + /// No in-view transaction created this outpoint. + NoSuchOutput, +} diff --git a/packages/zaino-core/src/upgrades.rs b/packages/zaino-core/src/upgrades.rs new file mode 100644 index 000000000..ed2164fb5 --- /dev/null +++ b/packages/zaino-core/src/upgrades.rs @@ -0,0 +1,18 @@ +//! Reported network-upgrade schedule, passed through from the validator. + +use zaino_primitives::types::Height; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UpgradeStatus { + Active, + Pending, + Disabled, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ReportedUpgrade { + pub branch_id: u32, + pub name: String, + pub activation_height: Height, + pub status: UpgradeStatus, +} diff --git a/packages/zaino-fs/Cargo.toml b/packages/zaino-fs/Cargo.toml new file mode 100644 index 000000000..4f1410387 --- /dev/null +++ b/packages/zaino-fs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "zaino-fs" +version = "0.1.0" +edition = "2021" +publish = false +description = "Finalised-state component: elevates the sync engine + indexes into finalised-state semantics (serve compact blocks + aux lookups, bulk-build + freeze). Hides zaino-sync internally." + +[dependencies] +zaino-core = { path = "../zaino-core" } +futures = "0.3" diff --git a/packages/zaino-fs/src/error.rs b/packages/zaino-fs/src/error.rs new file mode 100644 index 000000000..4a8aa70f6 --- /dev/null +++ b/packages/zaino-fs/src/error.rs @@ -0,0 +1,53 @@ +//! Per-operation errors for the finalised-state component. +//! +//! Distinct types because the operations fail in different ways — height-keyed +//! reads can be above the watermark, address reads can be absent in a +//! deployment that doesn't run the index, and the ingest paths fail on the +//! source or the commit. None of those failures is shared across all methods, +//! so a single error would over-state what each call can return. + +use zaino_core::Height; + +/// Errors from height-keyed reads (`compact_block`, `treestate`). +#[derive(Debug)] +pub enum HeightReadError { + /// Backend I/O failure. + Backend(String), + /// The requested height is above the finalised watermark. + AboveWatermark(Height), +} + +/// Errors from key→value lookups (`height_of`, `tx_location`, `spend_status`). +/// A miss is `Ok(None)` / a domain answer, never an error here. +#[derive(Debug)] +pub enum LookupError { + /// Backend I/O failure. + Backend(String), +} + +/// Errors from address-history reads (`address_balance`, `address_unspent`). +#[derive(Debug)] +pub enum AddressReadError { + /// Backend I/O failure. + Backend(String), + /// This deployment does not run the address-history index. + NotEnabled, +} + +/// Errors from the boot-time bulk build (`bulk_build_to`). +#[derive(Debug)] +pub enum BuildError { + /// The validator source failed. + Source(String), + /// A backend commit failed. + Commit(String), +} + +/// Errors from freezing one block (`freeze`). +#[derive(Debug)] +pub enum FreezeError { + /// A backend commit failed. + Commit(String), + /// The block failed continuity/validation at the freeze boundary. + Invalid(String), +} diff --git a/packages/zaino-fs/src/indexes.rs b/packages/zaino-fs/src/indexes.rs new file mode 100644 index 000000000..5321a1141 --- /dev/null +++ b/packages/zaino-fs/src/indexes.rs @@ -0,0 +1,22 @@ +//! Addon reverse indexes over the finalised block stream. +//! +//! Each is a *synthetic* index the sync engine extracts during freeze/bulk-build. +//! They are **addons on the spine** ([`crate::spine`]): a deployment builds only +//! the ones it serves, selected by feature or config. Not building one means: +//! +//! - less work per block — the delta is never extracted (work is a property of +//! the index set), and +//! - the capability is absent — at the **type level** (the trait isn't +//! implemented, so a consumer can't even ask) or, for a runtime toggle, via a +//! `NotEnabled` error. +//! +//! Privacy: the address index is privacy-sensitive and belongs behind a +//! **non-default** feature flag. + +mod address; +mod spend; +mod tx_location; + +pub use address::AddressIndex; +pub use spend::SpendIndex; +pub use tx_location::TxLocationIndex; diff --git a/packages/zaino-fs/src/indexes/address.rs b/packages/zaino-fs/src/indexes/address.rs new file mode 100644 index 000000000..cbd0a1d08 --- /dev/null +++ b/packages/zaino-fs/src/indexes/address.rs @@ -0,0 +1,22 @@ +//! Addon index: transparent-address history (privacy-sensitive). + +use std::future::Future; + +use zaino_core::{AddressBalance, TransparentAddress, Utxo}; + +use crate::error::AddressReadError; + +/// Transparent-address history: balance and unspent outputs for an address. +/// Privacy-sensitive — belongs behind a **non-default** feature flag; a +/// deployment that doesn't run it either omits this impl (type-level absence) or +/// returns `NotEnabled` (runtime toggle). +pub trait AddressIndex: Send + Sync { + fn address_balance( + &self, + addr: &TransparentAddress, + ) -> impl Future> + Send; + fn address_unspent( + &self, + addr: &TransparentAddress, + ) -> impl Future, AddressReadError>> + Send; +} diff --git a/packages/zaino-fs/src/indexes/spend.rs b/packages/zaino-fs/src/indexes/spend.rs new file mode 100644 index 000000000..e2e9ca81b --- /dev/null +++ b/packages/zaino-fs/src/indexes/spend.rs @@ -0,0 +1,17 @@ +//! Addon index: spend status of a transparent outpoint. + +use std::future::Future; + +use zaino_core::{Outpoint, SpendStatus}; + +use crate::error::LookupError; + +/// Whether/where a transparent outpoint was spent. Backed by the spent-outpoint +/// index. `NoSuchOutput`/unspent are domain answers, not errors; only the +/// backend can fail. +pub trait SpendIndex: Send + Sync { + fn spend_status( + &self, + outpoint: Outpoint, + ) -> impl Future> + Send; +} diff --git a/packages/zaino-fs/src/indexes/tx_location.rs b/packages/zaino-fs/src/indexes/tx_location.rs new file mode 100644 index 000000000..486fe01ad --- /dev/null +++ b/packages/zaino-fs/src/indexes/tx_location.rs @@ -0,0 +1,16 @@ +//! Addon index: transaction location by txid. + +use std::future::Future; + +use zaino_core::{TransactionHash, TransactionLocation}; + +use crate::error::LookupError; + +/// Resolve a txid to where it was mined. Backed by the txid→location index. +/// A miss is `Ok(None)`; only the backend can fail. +pub trait TxLocationIndex: Send + Sync { + fn tx_location( + &self, + txid: TransactionHash, + ) -> impl Future, LookupError>> + Send; +} diff --git a/packages/zaino-fs/src/lib.rs b/packages/zaino-fs/src/lib.rs new file mode 100644 index 000000000..cac033a38 --- /dev/null +++ b/packages/zaino-fs/src/lib.rs @@ -0,0 +1,36 @@ +//! Finalised state — the immutable, append-only side of the chain. +//! +//! Domain semantics over finalised blocks: serve compact blocks + aux lookups, +//! and ingest (bulk-build on boot, freeze one block in steady state). Internally +//! this drives `zaino-sync` + `zaino-indexes` over a `zaino-persistence` backend +//! — but that is hidden; consumers see finalised *state*, not indices. +//! +//! The surface is decomposed by *provenance* so deployment variants compose by +//! subset: +//! - [`FinalisedSpine`] — always present: the block store + intrinsic +//! derivations + ingest. +//! - [`indexes`] — addon reverse indexes ([`TxLocationIndex`], [`SpendIndex`], +//! [`AddressIndex`]), each built only if the deployment serves it. +//! - [`FinalisedState`] — the convenience bundle of all of them, for a full +//! deployment. Per-capability consumers should bound on exactly the +//! spine/addon traits they use, so a variant that omits an index is a +//! compile-time subset, not a runtime miss. +//! +//! Scaffold: capability algebra only. Implementations follow. +#![forbid(unsafe_code)] + +pub mod error; +pub mod indexes; +mod spine; + +pub use indexes::{AddressIndex, SpendIndex, TxLocationIndex}; +pub use spine::{FinalisedSpine, FrozenBlock}; + +/// The full finalised-state surface: spine + every addon index. A convenience +/// bundle for deployments that build the complete index set. The blanket impl +/// makes any type providing the parts a `FinalisedState` — but prefer bounding +/// on the specific spine/addon traits a consumer actually uses, so omitting an +/// index yields a compile-time-smaller capability set rather than a runtime miss. +pub trait FinalisedState: FinalisedSpine + TxLocationIndex + SpendIndex + AddressIndex {} + +impl FinalisedState for T {} diff --git a/packages/zaino-fs/src/spine.rs b/packages/zaino-fs/src/spine.rs new file mode 100644 index 000000000..c35c0f549 --- /dev/null +++ b/packages/zaino-fs/src/spine.rs @@ -0,0 +1,53 @@ +//! The finalised-state spine — the always-present core. +//! +//! The irreducible finalised block store: the compact-block spine, its intrinsic +//! height/hash/treestate derivations, and the ingest lifecycle. *Every* +//! deployment has this. The heavier reverse indexes (tx-location, spend, +//! address) are addon traits in [`crate::indexes`], selected per deployment. + +use std::future::Future; + +use zaino_core::{BlockHash, CompactBlock, Height, PreIndexCompactBlock, Treestate}; + +use crate::error::{BuildError, FreezeError, HeightReadError, LookupError}; + +/// A block handed over the freeze boundary (NFS → FS): now final, to be indexed. +/// Carries enough to store the compact block and extract the aux indexes. +pub type FrozenBlock = PreIndexCompactBlock; + +/// The finalised-state spine: the block store + intrinsic derivations + ingest. +/// Everything it answers is at or below [`FinalisedSpine::watermark`] and +/// immutable — so no reorg machinery. Each method carries the error type +/// appropriate to *its* failure modes. +pub trait FinalisedSpine: Send + Sync { + /// The finalised tip. Reads are valid for heights `<= watermark`. Infallible. + fn watermark(&self) -> Height; + + // --- height-keyed reads (can be above the watermark) --- + fn compact_block( + &self, + height: Height, + ) -> impl Future, HeightReadError>> + Send; + fn treestate( + &self, + height: Height, + ) -> impl Future> + Send; + + // --- hash lookup (miss is Ok, only the backend can fail) --- + fn height_of( + &self, + hash: BlockHash, + ) -> impl Future, LookupError>> + Send; + + // --- ingest --- + /// Bulk-build the finalised state up to `target` (boot catch-up), pulling + /// from `source` (a `zaino-source`-shaped validator port — bounded in the + /// impl). Where the sync engine's parallel pipeline runs. + fn bulk_build_to( + &self, + target: Height, + source: &S, + ) -> impl Future> + Send; + /// Extend by one finalised block (steady-state freeze). + fn freeze(&self, block: FrozenBlock) -> impl Future> + Send; +} diff --git a/packages/zaino-indexes/Cargo.toml b/packages/zaino-indexes/Cargo.toml new file mode 100644 index 000000000..1ac4d0c01 --- /dev/null +++ b/packages/zaino-indexes/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "zaino-indexes" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +description = "Zcash index definitions for the zaino sync engine." + +[dependencies] +zaino-primitives.workspace = true +zaino-sync.workspace = true + +[lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" diff --git a/packages/zaino-indexes/src/indexes.rs b/packages/zaino-indexes/src/indexes.rs new file mode 100644 index 000000000..0bf7c6034 --- /dev/null +++ b/packages/zaino-indexes/src/indexes.rs @@ -0,0 +1,13 @@ +//! Individual index definitions. +//! +//! Each module defines one index: its context projection, extraction, +//! merge, schema, and encoding. + +pub mod hash_to_height; +pub mod headers; +pub mod orchard; +pub mod sapling; +pub mod transparent_data; +pub mod transparent_spends; +pub mod txid_location; +pub mod txids; diff --git a/packages/zaino-indexes/src/indexes/hash_to_height.rs b/packages/zaino-indexes/src/indexes/hash_to_height.rs new file mode 100644 index 000000000..f16b26d4b --- /dev/null +++ b/packages/zaino-indexes/src/indexes/hash_to_height.rs @@ -0,0 +1,79 @@ +//! HashToHeightIndex (BlockLocal × Append): block hash → height. + +use zaino_primitives::types::BlockHash; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Per-index context. +pub struct HashToHeightCtx { + /// Block hash. + pub hash: BlockHash, + /// Block height. + pub height: BlockHeight, +} + +/// Delta. +pub struct HashToHeightEntry { + /// Block hash (key). + pub hash: BlockHash, + /// Block height (value). + pub height: BlockHeight, +} + +/// Index definition. +pub struct HashToHeightIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("hash_to_height"); + +impl IndexDef for HashToHeightIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = HashToHeightEntry; + type BlockContext = HashToHeightCtx; + const NAME: IndexId = ID; +} + +impl ExtractLocal for HashToHeightIndex { + fn extract(ctx: &HashToHeightCtx) -> Result { + Ok(HashToHeightEntry { hash: ctx.hash, height: ctx.height }) + } +} + +impl MergeAppend for HashToHeightIndex {} + +impl Schema> for HashToHeightIndex { + type Key = BlockHash; + type Value = BlockHeight; + + fn into_entries(entries: Vec) -> Vec<(Self::Key, Self::Value)> { + entries.into_iter().map(|e| (e.hash, e.height)).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec { + entries.into_iter().map(|(hash, height)| HashToHeightEntry { hash, height }).collect() + } + + fn encode_key(key: &BlockHash) -> Vec { + <[u8; 32]>::from(*key).to_vec() + } + + fn encode_value(value: &BlockHeight) -> Vec { + value.value().to_le_bytes().to_vec() + } + + fn decode_key(bytes: &[u8]) -> Result { + let mut arr = [0u8; 32]; + if bytes.len() != 32 { return Err(SchemaDecodeError::Invalid(format!("expected 32 bytes, got {}", bytes.len()))); } + arr.copy_from_slice(bytes); + Ok(BlockHash::from(arr)) + } + + fn decode_value(bytes: &[u8]) -> Result { + let arr: [u8; 8] = bytes.try_into().map_err(|_| SchemaDecodeError::Invalid(format!("expected 8 bytes, got {}", bytes.len())))?; + Ok(BlockHeight::new(u64::from_le_bytes(arr))) + } +} diff --git a/packages/zaino-indexes/src/indexes/headers.rs b/packages/zaino-indexes/src/indexes/headers.rs new file mode 100644 index 000000000..414d53425 --- /dev/null +++ b/packages/zaino-indexes/src/indexes/headers.rs @@ -0,0 +1,129 @@ +//! HeadersIndex (BlockLocal × Append): height → (hash, prev_hash, time, bits). + +use zaino_primitives::types::{BlockHash, BlockTime, CompactDifficulty}; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Per-index context for HeadersIndex. +pub struct HeaderCtx { + /// Block height. + pub height: BlockHeight, + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Timestamp. + pub time: BlockTime, + /// Compact difficulty. + pub bits: CompactDifficulty, +} + +/// Header delta. +pub struct HeaderEntry { + /// Block height (key). + pub height: BlockHeight, + /// Header data (value). + pub value: HeaderValue, +} + +/// Persisted header value: hash(32) + prev_hash(32) + time(4) + bits(4) = 72 bytes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HeaderValue { + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Timestamp. + pub time: BlockTime, + /// Compact difficulty. + pub bits: CompactDifficulty, +} + +/// Headers index definition. +pub struct HeadersIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("headers"); + +impl IndexDef for HeadersIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = HeaderEntry; + type BlockContext = HeaderCtx; + + const NAME: IndexId = ID; +} + +impl ExtractLocal for HeadersIndex { + fn extract(ctx: &HeaderCtx) -> Result { + Ok(HeaderEntry { + height: ctx.height, + value: HeaderValue { + hash: ctx.hash, + prev_hash: ctx.prev_hash, + time: ctx.time, + bits: ctx.bits, + }, + }) + } +} + +impl MergeAppend for HeadersIndex {} + +impl Schema> for HeadersIndex { + type Key = BlockHeight; + type Value = HeaderValue; + + fn into_entries(entries: Vec) -> Vec<(Self::Key, Self::Value)> { + entries.into_iter().map(|e| (e.height, e.value)).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec { + entries + .into_iter() + .map(|(height, value)| HeaderEntry { height, value }) + .collect() + } + + fn encode_key(key: &BlockHeight) -> Vec { + key.value().to_le_bytes().to_vec() + } + + fn encode_value(value: &HeaderValue) -> Vec { + let mut buf = Vec::with_capacity(72); + buf.extend_from_slice(&<[u8; 32]>::from(value.hash)); + buf.extend_from_slice(&<[u8; 32]>::from(value.prev_hash)); + buf.extend_from_slice(&value.time.to_le_bytes()); + buf.extend_from_slice(&value.bits.to_le_bytes()); + buf + } + + fn decode_key(bytes: &[u8]) -> Result { + let arr: [u8; 8] = bytes.try_into().map_err(|_| { + SchemaDecodeError::Invalid(format!("expected 8 bytes, got {}", bytes.len())) + })?; + Ok(BlockHeight::new(u64::from_le_bytes(arr))) + } + + fn decode_value(bytes: &[u8]) -> Result { + if bytes.len() != 72 { + return Err(SchemaDecodeError::Invalid(format!( + "expected 72 bytes, got {}", + bytes.len() + ))); + } + let mut hash = [0u8; 32]; + let mut prev_hash = [0u8; 32]; + hash.copy_from_slice(&bytes[0..32]); + prev_hash.copy_from_slice(&bytes[32..64]); + Ok(HeaderValue { + hash: BlockHash::from(hash), + prev_hash: BlockHash::from(prev_hash), + time: u32::from_le_bytes(bytes[64..68].try_into().expect("4 bytes")), + bits: u32::from_le_bytes(bytes[68..72].try_into().expect("4 bytes")), + }) + } +} diff --git a/packages/zaino-indexes/src/indexes/orchard.rs b/packages/zaino-indexes/src/indexes/orchard.rs new file mode 100644 index 000000000..dab99a435 --- /dev/null +++ b/packages/zaino-indexes/src/indexes/orchard.rs @@ -0,0 +1,101 @@ +//! OrchardIndex (BlockLocal × Append): height → compact orchard data per block. + +use zaino_primitives::types::{EncryptedCiphertext, EphemeralKey, NoteCommitment, Nullifier}; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Compact orchard data for one transaction. +#[derive(Debug, Clone)] +pub struct OrchardTxCompact { + /// Orchard actions: (nullifier, cmx, epk, enc_ciphertext_52bytes). + pub actions: Vec<(Nullifier, NoteCommitment, EphemeralKey, EncryptedCiphertext)>, +} + +/// Per-index context. +pub struct OrchardCtx { + /// Block height. + pub height: BlockHeight, + /// Per-tx orchard data. + pub txs: Vec, +} + +/// Delta. +pub struct OrchardEntry { + /// Block height (key). + pub height: BlockHeight, + /// Value. + pub value: OrchardBlockValue, +} + +/// Persisted value. +#[derive(Debug, Clone)] +pub struct OrchardBlockValue(pub Vec); + +/// Index definition. +pub struct OrchardIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("orchard"); + +impl IndexDef for OrchardIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = OrchardEntry; + type BlockContext = OrchardCtx; + const NAME: IndexId = ID; +} + +impl ExtractLocal for OrchardIndex { + fn extract(ctx: &OrchardCtx) -> Result { + Ok(OrchardEntry { + height: ctx.height, + value: OrchardBlockValue(ctx.txs.clone()), + }) + } +} + +impl MergeAppend for OrchardIndex {} + +impl Schema> for OrchardIndex { + type Key = BlockHeight; + type Value = OrchardBlockValue; + + fn into_entries(entries: Vec) -> Vec<(Self::Key, Self::Value)> { + entries.into_iter().map(|e| (e.height, e.value)).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec { + entries.into_iter().map(|(h, v)| OrchardEntry { height: h, value: v }).collect() + } + + fn encode_key(key: &BlockHeight) -> Vec { key.value().to_le_bytes().to_vec() } + + fn encode_value(value: &OrchardBlockValue) -> Vec { + let mut buf = Vec::new(); + buf.extend_from_slice(&(value.0.len() as u32).to_le_bytes()); + for tx in &value.0 { + buf.extend_from_slice(&(tx.actions.len() as u32).to_le_bytes()); + for (nf, cmx, epk, enc) in &tx.actions { + buf.extend_from_slice(&<[u8; 32]>::from(*nf)); + buf.extend_from_slice(&<[u8; 32]>::from(*cmx)); + buf.extend_from_slice(&<[u8; 32]>::from(*epk)); + let enc_bytes: Vec = enc.clone().into(); + buf.extend_from_slice(&(enc_bytes.len() as u32).to_le_bytes()); + buf.extend_from_slice(&enc_bytes); + } + } + buf + } + + fn decode_key(bytes: &[u8]) -> Result { + let arr: [u8; 8] = bytes.try_into().map_err(|_| SchemaDecodeError::Invalid("bad height".into()))?; + Ok(BlockHeight::new(u64::from_le_bytes(arr))) + } + + fn decode_value(_bytes: &[u8]) -> Result { + Err(SchemaDecodeError::Invalid("orchard decode not yet implemented".into())) + } +} diff --git a/packages/zaino-indexes/src/indexes/sapling.rs b/packages/zaino-indexes/src/indexes/sapling.rs new file mode 100644 index 000000000..cff1aa38a --- /dev/null +++ b/packages/zaino-indexes/src/indexes/sapling.rs @@ -0,0 +1,107 @@ +//! SaplingIndex (BlockLocal × Append): height → compact sapling data per block. + +use zaino_primitives::types::{EncryptedCiphertext, EphemeralKey, NoteCommitment, Nullifier}; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Compact sapling data for one transaction. +#[derive(Debug, Clone)] +pub struct SaplingTxCompact { + /// Sapling spend nullifiers. + pub nullifiers: Vec, + /// Sapling outputs: (cmu, epk, enc_ciphertext_52bytes). + pub outputs: Vec<(NoteCommitment, EphemeralKey, EncryptedCiphertext)>, +} + +/// Per-index context. +pub struct SaplingCtx { + /// Block height. + pub height: BlockHeight, + /// Per-tx sapling data. + pub txs: Vec, +} + +/// Delta. +pub struct SaplingEntry { + /// Block height (key). + pub height: BlockHeight, + /// Value. + pub value: SaplingBlockValue, +} + +/// Persisted value: all sapling data for the block. +#[derive(Debug, Clone)] +pub struct SaplingBlockValue(pub Vec); + +/// Index definition. +pub struct SaplingIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("sapling"); + +impl IndexDef for SaplingIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = SaplingEntry; + type BlockContext = SaplingCtx; + const NAME: IndexId = ID; +} + +impl ExtractLocal for SaplingIndex { + fn extract(ctx: &SaplingCtx) -> Result { + Ok(SaplingEntry { + height: ctx.height, + value: SaplingBlockValue(ctx.txs.clone()), + }) + } +} + +impl MergeAppend for SaplingIndex {} + +impl Schema> for SaplingIndex { + type Key = BlockHeight; + type Value = SaplingBlockValue; + + fn into_entries(entries: Vec) -> Vec<(Self::Key, Self::Value)> { + entries.into_iter().map(|e| (e.height, e.value)).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec { + entries.into_iter().map(|(h, v)| SaplingEntry { height: h, value: v }).collect() + } + + fn encode_key(key: &BlockHeight) -> Vec { key.value().to_le_bytes().to_vec() } + + fn encode_value(value: &SaplingBlockValue) -> Vec { + let mut buf = Vec::new(); + buf.extend_from_slice(&(value.0.len() as u32).to_le_bytes()); + for tx in &value.0 { + buf.extend_from_slice(&(tx.nullifiers.len() as u32).to_le_bytes()); + for nf in &tx.nullifiers { + buf.extend_from_slice(&<[u8; 32]>::from(*nf)); + } + buf.extend_from_slice(&(tx.outputs.len() as u32).to_le_bytes()); + for (cmu, epk, enc) in &tx.outputs { + buf.extend_from_slice(&<[u8; 32]>::from(*cmu)); + buf.extend_from_slice(&<[u8; 32]>::from(*epk)); + let enc_bytes: Vec = enc.clone().into(); + buf.extend_from_slice(&(enc_bytes.len() as u32).to_le_bytes()); + buf.extend_from_slice(&enc_bytes); + } + } + buf + } + + fn decode_key(bytes: &[u8]) -> Result { + let arr: [u8; 8] = bytes.try_into().map_err(|_| SchemaDecodeError::Invalid("bad height".into()))?; + Ok(BlockHeight::new(u64::from_le_bytes(arr))) + } + + fn decode_value(_bytes: &[u8]) -> Result { + // Full decode deferred — not needed for sync, only for serving. + Err(SchemaDecodeError::Invalid("sapling decode not yet implemented".into())) + } +} diff --git a/packages/zaino-indexes/src/indexes/transparent_data.rs b/packages/zaino-indexes/src/indexes/transparent_data.rs new file mode 100644 index 000000000..fe6065607 --- /dev/null +++ b/packages/zaino-indexes/src/indexes/transparent_data.rs @@ -0,0 +1,106 @@ +//! TransparentDataIndex (BlockLocal × Append): height → compact transparent data per block. + +use zaino_primitives::types::{OutputIndex, Script, TransactionHash, Zatoshis}; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Compact transparent data for one transaction. +#[derive(Debug, Clone)] +pub struct TransparentTxCompact { + /// Transparent inputs: (prev_txid, prev_index). + pub inputs: Vec<(TransactionHash, OutputIndex)>, + /// Transparent outputs: (value, script). + pub outputs: Vec<(Zatoshis, Script)>, +} + +/// Per-index context. +pub struct TransparentDataCtx { + /// Block height. + pub height: BlockHeight, + /// Per-tx transparent data. + pub txs: Vec, +} + +/// Delta. +pub struct TransparentDataEntry { + /// Block height (key). + pub height: BlockHeight, + /// Value. + pub value: TransparentBlockValue, +} + +/// Persisted value. +#[derive(Debug, Clone)] +pub struct TransparentBlockValue(pub Vec); + +/// Index definition. +pub struct TransparentDataIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("transparent_data"); + +impl IndexDef for TransparentDataIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = TransparentDataEntry; + type BlockContext = TransparentDataCtx; + const NAME: IndexId = ID; +} + +impl ExtractLocal for TransparentDataIndex { + fn extract(ctx: &TransparentDataCtx) -> Result { + Ok(TransparentDataEntry { + height: ctx.height, + value: TransparentBlockValue(ctx.txs.clone()), + }) + } +} + +impl MergeAppend for TransparentDataIndex {} + +impl Schema> for TransparentDataIndex { + type Key = BlockHeight; + type Value = TransparentBlockValue; + + fn into_entries(entries: Vec) -> Vec<(Self::Key, Self::Value)> { + entries.into_iter().map(|e| (e.height, e.value)).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec { + entries.into_iter().map(|(h, v)| TransparentDataEntry { height: h, value: v }).collect() + } + + fn encode_key(key: &BlockHeight) -> Vec { key.value().to_le_bytes().to_vec() } + + fn encode_value(value: &TransparentBlockValue) -> Vec { + let mut buf = Vec::new(); + buf.extend_from_slice(&(value.0.len() as u32).to_le_bytes()); + for tx in &value.0 { + buf.extend_from_slice(&(tx.inputs.len() as u32).to_le_bytes()); + for (txid, idx) in &tx.inputs { + buf.extend_from_slice(&<[u8; 32]>::from(*txid)); + buf.extend_from_slice(&idx.to_le_bytes()); + } + buf.extend_from_slice(&(tx.outputs.len() as u32).to_le_bytes()); + for (value, script) in &tx.outputs { + buf.extend_from_slice(&u64::from(*value).to_le_bytes()); + let script_bytes: Vec = script.clone().into(); + buf.extend_from_slice(&(script_bytes.len() as u32).to_le_bytes()); + buf.extend_from_slice(&script_bytes); + } + } + buf + } + + fn decode_key(bytes: &[u8]) -> Result { + let arr: [u8; 8] = bytes.try_into().map_err(|_| SchemaDecodeError::Invalid("bad height".into()))?; + Ok(BlockHeight::new(u64::from_le_bytes(arr))) + } + + fn decode_value(_bytes: &[u8]) -> Result { + Err(SchemaDecodeError::Invalid("transparent_data decode not yet implemented".into())) + } +} diff --git a/packages/zaino-indexes/src/indexes/transparent_spends.rs b/packages/zaino-indexes/src/indexes/transparent_spends.rs new file mode 100644 index 000000000..2e246c0c6 --- /dev/null +++ b/packages/zaino-indexes/src/indexes/transparent_spends.rs @@ -0,0 +1,138 @@ +//! TransparentSpendsIndex (BlockLocal × Append): outpoint → spending txid. +//! +//! For each transparent input in each transaction, records which +//! transaction spent that outpoint. + +use zaino_primitives::types::{OutputIndex, TransactionHash}; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::IndexId; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Per-index context: the block's transparent inputs. +pub struct SpendCtx { + /// All transparent spends in the block: (prev_txid, prev_index, spending_txid). + pub spends: Vec<(TransactionHash, OutputIndex, TransactionHash)>, +} + +/// One spend entry. +pub struct SpendEntry { + /// The outpoint being spent (txid + output index). + pub prev_txid: TransactionHash, + /// Output index within the previous transaction. + pub prev_index: OutputIndex, + /// The transaction that spent this outpoint. + pub spending_txid: TransactionHash, +} + +/// Persisted key: prev_txid(32) + prev_index(4) = 36 bytes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OutpointKey { + /// Previous transaction hash. + pub prev_txid: TransactionHash, + /// Output index. + pub prev_index: OutputIndex, +} + +/// Index definition. +pub struct TransparentSpendsIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("transparent_spends"); + +impl IndexDef for TransparentSpendsIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = Vec; + type BlockContext = SpendCtx; + + const NAME: IndexId = ID; +} + +impl ExtractLocal for TransparentSpendsIndex { + fn extract(ctx: &SpendCtx) -> Result { + Ok(ctx + .spends + .iter() + .map(|(prev_txid, prev_index, spending_txid)| SpendEntry { + prev_txid: *prev_txid, + prev_index: *prev_index, + spending_txid: *spending_txid, + }) + .collect()) + } +} + +impl MergeAppend for TransparentSpendsIndex {} + +impl Schema>> for TransparentSpendsIndex { + type Key = OutpointKey; + type Value = TransactionHash; + + fn into_entries(batches: Vec>) -> Vec<(Self::Key, Self::Value)> { + batches + .into_iter() + .flatten() + .map(|e| { + ( + OutpointKey { + prev_txid: e.prev_txid, + prev_index: e.prev_index, + }, + e.spending_txid, + ) + }) + .collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec> { + vec![entries + .into_iter() + .map(|(key, spending_txid)| SpendEntry { + prev_txid: key.prev_txid, + prev_index: key.prev_index, + spending_txid, + }) + .collect()] + } + + fn encode_key(key: &OutpointKey) -> Vec { + let mut buf = Vec::with_capacity(36); + buf.extend_from_slice(&<[u8; 32]>::from(key.prev_txid)); + buf.extend_from_slice(&key.prev_index.to_le_bytes()); + buf + } + + fn encode_value(value: &TransactionHash) -> Vec { + <[u8; 32]>::from(*value).to_vec() + } + + fn decode_key(bytes: &[u8]) -> Result { + if bytes.len() != 36 { + return Err(SchemaDecodeError::Invalid(format!( + "expected 36 bytes, got {}", + bytes.len() + ))); + } + let mut txid = [0u8; 32]; + txid.copy_from_slice(&bytes[0..32]); + let index = u32::from_le_bytes(bytes[32..36].try_into().expect("4 bytes")); + Ok(OutpointKey { + prev_txid: TransactionHash::from(txid), + prev_index: index, + }) + } + + fn decode_value(bytes: &[u8]) -> Result { + if bytes.len() != 32 { + return Err(SchemaDecodeError::Invalid(format!( + "expected 32 bytes, got {}", + bytes.len() + ))); + } + let mut txid = [0u8; 32]; + txid.copy_from_slice(bytes); + Ok(TransactionHash::from(txid)) + } +} diff --git a/packages/zaino-indexes/src/indexes/txid_location.rs b/packages/zaino-indexes/src/indexes/txid_location.rs new file mode 100644 index 000000000..7365dcbb8 --- /dev/null +++ b/packages/zaino-indexes/src/indexes/txid_location.rs @@ -0,0 +1,94 @@ +//! TxidLocationIndex (BlockLocal × Append): txid → (height, tx_index). + +use zaino_primitives::types::TransactionHash; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Per-index context. +pub struct TxidLocationCtx { + /// (txid, height, tx_index) for each transaction. + pub locations: Vec<(TransactionHash, BlockHeight, u32)>, +} + +/// Delta: one entry per transaction. +pub struct TxidLocationEntry { + /// Transaction hash (key). + pub txid: TransactionHash, + /// Location (value). + pub location: TxLocation, +} + +/// Persisted value: height + tx_index. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxLocation { + /// Block height. + pub height: BlockHeight, + /// Transaction index within the block. + pub tx_index: u32, +} + +/// Index definition. +pub struct TxidLocationIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("txid_location"); + +impl IndexDef for TxidLocationIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = Vec; + type BlockContext = TxidLocationCtx; + const NAME: IndexId = ID; +} + +impl ExtractLocal for TxidLocationIndex { + fn extract(ctx: &TxidLocationCtx) -> Result { + Ok(ctx.locations.iter().map(|(txid, height, idx)| TxidLocationEntry { + txid: *txid, + location: TxLocation { height: *height, tx_index: *idx }, + }).collect()) + } +} + +impl MergeAppend for TxidLocationIndex {} + +impl Schema>> for TxidLocationIndex { + type Key = TransactionHash; + type Value = TxLocation; + + fn into_entries(batches: Vec>) -> Vec<(Self::Key, Self::Value)> { + batches.into_iter().flatten().map(|e| (e.txid, e.location)).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec> { + vec![entries.into_iter().map(|(txid, location)| TxidLocationEntry { txid, location }).collect()] + } + + fn encode_key(key: &TransactionHash) -> Vec { + <[u8; 32]>::from(*key).to_vec() + } + + fn encode_value(value: &TxLocation) -> Vec { + let mut buf = Vec::with_capacity(12); + buf.extend_from_slice(&value.height.value().to_le_bytes()); + buf.extend_from_slice(&value.tx_index.to_le_bytes()); + buf + } + + fn decode_key(bytes: &[u8]) -> Result { + let mut arr = [0u8; 32]; + if bytes.len() != 32 { return Err(SchemaDecodeError::Invalid(format!("expected 32, got {}", bytes.len()))); } + arr.copy_from_slice(bytes); + Ok(TransactionHash::from(arr)) + } + + fn decode_value(bytes: &[u8]) -> Result { + if bytes.len() != 12 { return Err(SchemaDecodeError::Invalid(format!("expected 12, got {}", bytes.len()))); } + let height = BlockHeight::new(u64::from_le_bytes(bytes[0..8].try_into().expect("8"))); + let tx_index = u32::from_le_bytes(bytes[8..12].try_into().expect("4")); + Ok(TxLocation { height, tx_index }) + } +} diff --git a/packages/zaino-indexes/src/indexes/txids.rs b/packages/zaino-indexes/src/indexes/txids.rs new file mode 100644 index 000000000..ca08abb95 --- /dev/null +++ b/packages/zaino-indexes/src/indexes/txids.rs @@ -0,0 +1,95 @@ +//! TxidsIndex (BlockLocal × Append): height → list of transaction ids. + +use zaino_primitives::types::TransactionHash; +use zaino_sync::descriptor::{Append, BlockLocal}; +use zaino_sync::primitives::{BlockHeight, IndexId}; +use zaino_sync::traits::{ + ExtractError, ExtractLocal, IndexDef, MergeAppend, Schema, SchemaDecodeError, +}; + +/// Per-index context. +pub struct TxidsCtx { + /// Block height. + pub height: BlockHeight, + /// Transaction ids in block order. + pub txids: Vec, +} + +/// Delta. +pub struct TxidsEntry { + /// Block height (key). + pub height: BlockHeight, + /// Txids (value). + pub txids: Vec, +} + +/// Persisted value: concatenated 32-byte txids. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxidsValue(pub Vec); + +/// Index definition. +pub struct TxidsIndex; + +/// Index identity. +pub const ID: IndexId = IndexId::new("txids"); + +impl IndexDef for TxidsIndex { + type Scope = BlockLocal; + type Composition = Append; + type Delta = TxidsEntry; + type BlockContext = TxidsCtx; + const NAME: IndexId = ID; +} + +impl ExtractLocal for TxidsIndex { + fn extract(ctx: &TxidsCtx) -> Result { + Ok(TxidsEntry { + height: ctx.height, + txids: ctx.txids.clone(), + }) + } +} + +impl MergeAppend for TxidsIndex {} + +impl Schema> for TxidsIndex { + type Key = BlockHeight; + type Value = TxidsValue; + + fn into_entries(entries: Vec) -> Vec<(Self::Key, Self::Value)> { + entries.into_iter().map(|e| (e.height, TxidsValue(e.txids))).collect() + } + + fn from_entries(entries: Vec<(Self::Key, Self::Value)>) -> Vec { + entries.into_iter().map(|(h, v)| TxidsEntry { height: h, txids: v.0 }).collect() + } + + fn encode_key(key: &BlockHeight) -> Vec { + key.value().to_le_bytes().to_vec() + } + + fn encode_value(value: &TxidsValue) -> Vec { + let mut buf = Vec::with_capacity(value.0.len() * 32); + for txid in &value.0 { + buf.extend_from_slice(&<[u8; 32]>::from(*txid)); + } + buf + } + + fn decode_key(bytes: &[u8]) -> Result { + let arr: [u8; 8] = bytes.try_into().map_err(|_| SchemaDecodeError::Invalid(format!("expected 8 bytes, got {}", bytes.len())))?; + Ok(BlockHeight::new(u64::from_le_bytes(arr))) + } + + fn decode_value(bytes: &[u8]) -> Result { + if bytes.len() % 32 != 0 { + return Err(SchemaDecodeError::Invalid(format!("txids length {} not multiple of 32", bytes.len()))); + } + let txids = bytes.chunks_exact(32).map(|c| { + let mut arr = [0u8; 32]; + arr.copy_from_slice(c); + TransactionHash::from(arr) + }).collect(); + Ok(TxidsValue(txids)) + } +} diff --git a/packages/zaino-indexes/src/lib.rs b/packages/zaino-indexes/src/lib.rs new file mode 100644 index 000000000..ecfacc356 --- /dev/null +++ b/packages/zaino-indexes/src/lib.rs @@ -0,0 +1,39 @@ +//! Zcash index definitions and pre-composed index sets. +//! +//! # Architecture +//! +//! The implementation flow follows the sync engine's trait hierarchy: +//! +//! ## 1. Individual Indexes ([`indexes`]) +//! +//! Each index module defines: +//! - A **narrow context type** (`HeaderCtx`) — the minimal per-block +//! data this index needs to produce its delta. +//! - An **`IndexDef`** impl — pins the scope (BlockLocal, SelfCumulative) +//! and composition (Append, Monoidal, Fold). +//! - An **extraction function** (`ExtractLocal::extract`) — pure, +//! produces a delta from the narrow context. +//! - A **`Schema`** impl — maps deltas to key/value entries and +//! defines encoding/decoding for persistence. +//! +//! Indexes know nothing about the set-wide context or other indexes. +//! They are reusable across different index sets. +//! +//! ## 2. Index Sets ([`sets`]) +//! +//! Each set module defines: +//! - A **set-wide context type** (e.g. `HeadersOnlyContext`) — the +//! union of all data any index in the set might need. The +//! provisioner produces one of these per block. +//! - **`ProvideContext` impls** — one per index in the set, projecting +//! the set-wide context into each index's narrow context type. +//! - A **builder function** (`index_set()`) — registers all indexes +//! and returns a configured `IndexSet`. +//! +//! Different sets can compose the same indexes with different set-wide +//! contexts. A "headers-only" set has a minimal context. A "full" set +//! would carry transaction data too — but the HeadersIndex definition +//! is the same in both; only the `ProvideContext` projection differs. + +pub mod indexes; +pub mod sets; diff --git a/packages/zaino-indexes/src/sets.rs b/packages/zaino-indexes/src/sets.rs new file mode 100644 index 000000000..cf98df741 --- /dev/null +++ b/packages/zaino-indexes/src/sets.rs @@ -0,0 +1,8 @@ +//! Pre-composed index sets. +//! +//! Each module defines a set-wide context, the `ProvideContext` +//! projections for its indexes, and a builder function. + +pub mod current_zaino; +pub mod headers_and_spends; +pub mod headers_only; diff --git a/packages/zaino-indexes/src/sets/current_zaino.rs b/packages/zaino-indexes/src/sets/current_zaino.rs new file mode 100644 index 000000000..d29ed2886 --- /dev/null +++ b/packages/zaino-indexes/src/sets/current_zaino.rs @@ -0,0 +1,306 @@ +//! current_zaino index set: all indexes matching zaino-state's V1 schema. +//! +//! 8 BlockLocal×Append indexes covering headers, txids, transparent, +//! sapling, orchard, hash→height, txid→location, and outpoint→spender. + +use zaino_primitives::types::{ + Block, BlockHash, BlockTime, CompactDifficulty, OutputIndex, TransactionHash, +}; +use zaino_sync::index_set::IndexSet; +use zaino_sync::primitives::BlockHeight; +use zaino_sync::traits::ProvideContext; + +use crate::indexes::hash_to_height::{HashToHeightCtx, HashToHeightIndex}; +use crate::indexes::headers::{HeaderCtx, HeadersIndex}; +use crate::indexes::orchard::{OrchardCtx, OrchardIndex, OrchardTxCompact}; +use crate::indexes::sapling::{SaplingCtx, SaplingIndex, SaplingTxCompact}; +use crate::indexes::transparent_data::{ + TransparentDataCtx, TransparentDataIndex, TransparentTxCompact, +}; +use crate::indexes::transparent_spends::{SpendCtx, TransparentSpendsIndex}; +use crate::indexes::txid_location::{TxidLocationCtx, TxidLocationIndex}; +use crate::indexes::txids::{TxidsCtx, TxidsIndex}; + +/// Set-wide context for the full zaino index set. +#[derive(Debug, Clone)] +pub struct CurrentZainoContext { + /// Block height. + pub height: BlockHeight, + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Block timestamp. + pub time: BlockTime, + /// Compact difficulty. + pub bits: CompactDifficulty, + /// Transaction ids. + pub txids: Vec, + /// Transparent spends: (prev_txid, prev_index, spending_txid). + pub spends: Vec<(TransactionHash, OutputIndex, TransactionHash)>, + /// Txid locations: (txid, height, tx_index). + pub txid_locations: Vec<(TransactionHash, BlockHeight, u32)>, + /// Per-tx transparent data. + pub transparent_txs: Vec, + /// Per-tx sapling data. + pub sapling_txs: Vec, + /// Per-tx orchard data. + pub orchard_txs: Vec, +} + +/// Build context from a domain Block. +pub fn context_from_block(block: &Block) -> CurrentZainoContext { + let height = BlockHeight::new(u64::from(block.header.height)); + + let txids: Vec = block.transactions.iter().map(|tx| tx.txid).collect(); + + let spends: Vec<(TransactionHash, OutputIndex, TransactionHash)> = block + .transactions + .iter() + .flat_map(|tx| { + tx.transparent + .inputs + .iter() + .map(move |inp| (inp.prev_txid, inp.prev_index, tx.txid)) + }) + .collect(); + + let txid_locations: Vec<(TransactionHash, BlockHeight, u32)> = block + .transactions + .iter() + .map(|tx| (tx.txid, height, tx.index)) + .collect(); + + let transparent_txs: Vec = block + .transactions + .iter() + .map(|tx| TransparentTxCompact { + inputs: tx + .transparent + .inputs + .iter() + .map(|inp| (inp.prev_txid, inp.prev_index)) + .collect(), + outputs: tx + .transparent + .outputs + .iter() + .map(|out| (out.value, out.script.clone())) + .collect(), + }) + .collect(); + + let sapling_txs: Vec = block + .transactions + .iter() + .map(|tx| SaplingTxCompact { + nullifiers: tx.sapling.spends.iter().map(|s| s.nullifier).collect(), + outputs: tx + .sapling + .outputs + .iter() + .map(|o| (o.cmu, o.ephemeral_key, o.enc_ciphertext.clone())) + .collect(), + }) + .collect(); + + let orchard_txs: Vec = block + .transactions + .iter() + .map(|tx| OrchardTxCompact { + actions: tx + .orchard + .actions + .iter() + .map(|a| { + ( + a.nullifier, + a.cmx, + a.ephemeral_key, + a.enc_ciphertext.clone(), + ) + }) + .collect(), + }) + .collect(); + + CurrentZainoContext { + height, + hash: block.header.hash, + prev_hash: block.header.prev_hash, + time: block.header.time, + bits: block.header.bits, + txids, + spends, + txid_locations, + transparent_txs, + sapling_txs, + orchard_txs, + } +} + + +/// Build context from a [`PreIndexCompactBlock`](zaino_primitives::types::PreIndexCompactBlock). +/// +/// Same output as [`context_from_block`] but sourced from the compact +/// representation that skips proof/signature deserialization. +pub fn context_from_pre_index_compact_block( + cb: &zaino_primitives::types::PreIndexCompactBlock, +) -> CurrentZainoContext { + use zaino_primitives::types::EncryptedCiphertext; + let height = BlockHeight::new(u64::from(cb.height)); + + let mut txids = Vec::with_capacity(cb.transactions.len()); + let mut spends = Vec::new(); + let mut txid_locations = Vec::with_capacity(cb.transactions.len()); + let mut transparent_txs = Vec::with_capacity(cb.transactions.len()); + let mut sapling_txs = Vec::with_capacity(cb.transactions.len()); + let mut orchard_txs = Vec::with_capacity(cb.transactions.len()); + + for (tx_index, ctx) in cb.transactions.iter().enumerate() { + txids.push(ctx.txid); + txid_locations.push((ctx.txid, height, tx_index as u32)); + + for inp in &ctx.transparent_inputs { + spends.push((inp.prev_txid, inp.prev_index, ctx.txid)); + } + + transparent_txs.push(TransparentTxCompact { + inputs: ctx + .transparent_inputs + .iter() + .map(|inp| (inp.prev_txid, inp.prev_index)) + .collect(), + outputs: ctx + .transparent_outputs + .iter() + .map(|out| (out.value, out.script.clone())) + .collect(), + }); + + sapling_txs.push(SaplingTxCompact { + nullifiers: ctx.sapling_nullifiers.clone(), + outputs: ctx + .sapling_outputs + .iter() + .map(|o| (o.cmu, o.ephemeral_key, o.enc_ciphertext.clone())) + .collect(), + }); + + orchard_txs.push(OrchardTxCompact { + actions: ctx + .orchard_actions + .iter() + .map(|a| (a.nullifier, a.cmx, a.ephemeral_key, a.enc_ciphertext.clone())) + .collect(), + }); + } + + CurrentZainoContext { + height, + hash: cb.hash, + prev_hash: cb.prev_hash, + time: cb.time, + bits: cb.bits, + txids, + spends, + txid_locations, + transparent_txs, + sapling_txs, + orchard_txs, + } +} + +// --------------------------------------------------------------------------- +// ProvideContext projections +// --------------------------------------------------------------------------- + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> HeaderCtx { + HeaderCtx { + height: self.height, + hash: self.hash, + prev_hash: self.prev_hash, + time: self.time, + bits: self.bits, + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> TxidsCtx { + TxidsCtx { + height: self.height, + txids: self.txids.clone(), + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> HashToHeightCtx { + HashToHeightCtx { + hash: self.hash, + height: self.height, + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> SpendCtx { + SpendCtx { + spends: self.spends.clone(), + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> TxidLocationCtx { + TxidLocationCtx { + locations: self.txid_locations.clone(), + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> TransparentDataCtx { + TransparentDataCtx { + height: self.height, + txs: self.transparent_txs.clone(), + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> SaplingCtx { + SaplingCtx { + height: self.height, + txs: self.sapling_txs.clone(), + } + } +} + +impl ProvideContext for CurrentZainoContext { + fn context(&self) -> OrchardCtx { + OrchardCtx { + height: self.height, + txs: self.orchard_txs.clone(), + } + } +} + +// --------------------------------------------------------------------------- +// Index set builder +// --------------------------------------------------------------------------- + +/// Build the full current-zaino index set (8 indexes). +pub fn index_set() -> IndexSet { + IndexSet::new() + .with::() + .with::() + .with::() + .with::() + .with::() + .with::() + .with::() + .with::() +} diff --git a/packages/zaino-indexes/src/sets/headers_and_spends.rs b/packages/zaino-indexes/src/sets/headers_and_spends.rs new file mode 100644 index 000000000..c892a6b6b --- /dev/null +++ b/packages/zaino-indexes/src/sets/headers_and_spends.rs @@ -0,0 +1,78 @@ +//! Headers + transparent spends index set. +//! +//! Set-wide context carries header fields + transparent spend data. + +use zaino_primitives::types::{Block, BlockHash, BlockTime, CompactDifficulty, OutputIndex, TransactionHash}; +use zaino_sync::index_set::IndexSet; +use zaino_sync::primitives::BlockHeight; +use zaino_sync::traits::ProvideContext; + +use crate::indexes::headers::{HeaderCtx, HeadersIndex}; +use crate::indexes::transparent_spends::{SpendCtx, TransparentSpendsIndex}; + +/// Set-wide context: header fields + transparent spends. +#[derive(Debug, Clone)] +pub struct HeadersAndSpendsContext { + /// Block height (sync-engine type). + pub height: BlockHeight, + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Block timestamp. + pub time: BlockTime, + /// Compact difficulty (nBits). + pub bits: CompactDifficulty, + /// All transparent spends: (prev_txid, prev_index, spending_txid). + pub spends: Vec<(TransactionHash, OutputIndex, TransactionHash)>, +} + +/// Build context from a domain Block. +pub fn context_from_block(block: &Block) -> HeadersAndSpendsContext { + let spends: Vec<(TransactionHash, OutputIndex, TransactionHash)> = block + .transactions + .iter() + .flat_map(|tx| { + tx.transparent + .inputs + .iter() + .map(move |input| (input.prev_txid, input.prev_index, tx.txid)) + }) + .collect(); + + HeadersAndSpendsContext { + height: BlockHeight::new(u64::from(block.header.height)), + hash: block.header.hash, + prev_hash: block.header.prev_hash, + time: block.header.time, + bits: block.header.bits, + spends, + } +} + +impl ProvideContext for HeadersAndSpendsContext { + fn context(&self) -> HeaderCtx { + HeaderCtx { + height: self.height, + hash: self.hash, + prev_hash: self.prev_hash, + time: self.time, + bits: self.bits, + } + } +} + +impl ProvideContext for HeadersAndSpendsContext { + fn context(&self) -> SpendCtx { + SpendCtx { + spends: self.spends.clone(), + } + } +} + +/// Build the headers + transparent spends index set. +pub fn index_set() -> IndexSet { + IndexSet::new() + .with::() + .with::() +} diff --git a/packages/zaino-indexes/src/sets/headers_only.rs b/packages/zaino-indexes/src/sets/headers_only.rs new file mode 100644 index 000000000..5d42bdd1e --- /dev/null +++ b/packages/zaino-indexes/src/sets/headers_only.rs @@ -0,0 +1,54 @@ +//! Headers-only index set: minimal context, one index. +//! +//! Suitable for benchmarking header sync throughput. The set-wide +//! context carries only header fields. + +use zaino_primitives::types::{Block, BlockHash, BlockTime, CompactDifficulty}; +use zaino_sync::index_set::IndexSet; +use zaino_sync::primitives::BlockHeight; +use zaino_sync::traits::ProvideContext; + +use crate::indexes::headers::{HeaderCtx, HeadersIndex}; + +/// Set-wide context for the headers-only index set. +#[derive(Debug, Clone)] +pub struct HeadersOnlyContext { + /// Block height (sync-engine type). + pub height: BlockHeight, + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Block timestamp. + pub time: BlockTime, + /// Compact difficulty (nBits). + pub bits: CompactDifficulty, +} + +/// Build a [`HeadersOnlyContext`] from a domain [`Block`]. +pub fn context_from_block(block: &Block) -> HeadersOnlyContext { + HeadersOnlyContext { + height: BlockHeight::new(u64::from(block.header.height)), + hash: block.header.hash, + prev_hash: block.header.prev_hash, + time: block.header.time, + bits: block.header.bits, + } +} + +impl ProvideContext for HeadersOnlyContext { + fn context(&self) -> HeaderCtx { + HeaderCtx { + height: self.height, + hash: self.hash, + prev_hash: self.prev_hash, + time: self.time, + bits: self.bits, + } + } +} + +/// Build the headers-only index set. +pub fn index_set() -> IndexSet { + IndexSet::new().with::() +} diff --git a/packages/zaino-nfs/Cargo.toml b/packages/zaino-nfs/Cargo.toml new file mode 100644 index 000000000..ada44de95 --- /dev/null +++ b/packages/zaino-nfs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "zaino-nfs" +version = "0.1.0" +edition = "2021" +publish = false +description = "Non-finalised-state component: the reorg-prone recent window. Adopts Hahn's zaino-store machinery narrowly (Chain + find_trim_index + snapshot + side branches), driven by a tip-follow loop. No LMDB." + +[dependencies] +zaino-core = { path = "../zaino-core" } +futures = "0.3" diff --git a/packages/zaino-nfs/src/facts.rs b/packages/zaino-nfs/src/facts.rs new file mode 100644 index 000000000..f5723f2ad --- /dev/null +++ b/packages/zaino-nfs/src/facts.rs @@ -0,0 +1,17 @@ +//! Re-derived transparent facts over the reorg window. +//! +//! Each answers a transparent-history question for the *recent* range, and — the +//! key asymmetry with the FS addon indexes — is **re-derived on demand from the +//! window's retained blocks**, not a persistent index. The window is small +//! (~100 blocks) and churns on every reorg, so there is nothing worth +//! materializing or rebuilding; a scan of what's already held answers it. +//! +//! Split out (mirroring the FS `indexes` split) for modelling clarity, and so a +//! disabled capability is off on *both* tiers: the same feature that omits the +//! FS index omits its NFS facet. + +mod address; +mod spend; + +pub use address::NfsAddressFacts; +pub use spend::NfsSpendFacts; diff --git a/packages/zaino-nfs/src/facts/address.rs b/packages/zaino-nfs/src/facts/address.rs new file mode 100644 index 000000000..7675b5c6b --- /dev/null +++ b/packages/zaino-nfs/src/facts/address.rs @@ -0,0 +1,10 @@ +//! Recent facet: transparent-address unspent outputs within the window. + +use zaino_core::{TransparentAddress, Utxo}; + +/// Unspent outpoints for `addr` created within (and still unspent within) this +/// window — re-derived from the window's blocks, infallible. Merged with the FS +/// address index by the runtime for a snapshot-coherent unspent set (US-1.3). +pub trait NfsAddressFacts: Send + Sync { + fn address_unspent(&self, addr: &TransparentAddress) -> Vec; +} diff --git a/packages/zaino-nfs/src/facts/spend.rs b/packages/zaino-nfs/src/facts/spend.rs new file mode 100644 index 000000000..abbca1110 --- /dev/null +++ b/packages/zaino-nfs/src/facts/spend.rs @@ -0,0 +1,9 @@ +//! Recent facet: spend status of a transparent outpoint within the window. + +use zaino_core::{Outpoint, SpendStatus}; + +/// Whether an outpoint was spent within the reorg window. Re-derived from the +/// window's blocks; infallible. Merged with the FS spend index by the runtime. +pub trait NfsSpendFacts: Send + Sync { + fn spend_status(&self, outpoint: Outpoint) -> SpendStatus; +} diff --git a/packages/zaino-nfs/src/lib.rs b/packages/zaino-nfs/src/lib.rs new file mode 100644 index 000000000..6712fde45 --- /dev/null +++ b/packages/zaino-nfs/src/lib.rs @@ -0,0 +1,95 @@ +//! Non-finalised state — the reorg-prone recent window. +//! +//! Adopts Hahn's `zaino-store` reorg machinery, narrowly: an in-memory `Chain` +//! (persistent vector) + `find_trim_index` (Lean-verified reorg) + a pinned +//! snapshot + a side-branch set (Q2), driven by a light tip-follow loop. +//! Internally `im`; no LMDB. Consumers see non-finalised *state*. +//! +//! The pinned-view surface is decomposed like the FS side, for modelling +//! clarity: a [`NfsSpine`] (window essentials + reorg queries) plus re-derived +//! [`facts`] facets ([`NfsSpendFacts`], [`NfsAddressFacts`]). [`NfsSnapshot`] is +//! the convenience bundle. Unlike FS, the facets are re-derived on demand (the +//! window is tiny and reorg-churny), not persistent indexes — see [`facts`]. +//! +//! Scaffold: capability algebra only. Implementations follow. +#![forbid(unsafe_code)] + +pub mod facts; +mod spine; + +use std::future::Future; + +use futures::stream::BoxStream; + +use zaino_core::{Height, TipEvent}; + +pub use facts::{NfsAddressFacts, NfsSpendFacts}; +pub use spine::NfsSpine; + +/// A block that has crossed the freeze horizon (the NFS → FS handoff). +pub type FrozenOut = zaino_core::PreIndexCompactBlock; + +/// The result of pinning the reorg window. Encodes readiness in the type so a +/// consumer *cannot* read a not-yet-synced window (locality of correctness) — +/// there is no `S` to call reads on until the window is established. +pub enum NfsView { + /// The window is live; `S` is a coherent pinned snapshot. + Ready(S), + /// Not established yet (boot catch-up). Recent reads are unavailable; the + /// finalised state is caught up to `finalised`. + Syncing { + /// The finalised height the FS is caught up to. + finalised: Height, + }, +} + +/// The non-finalised-state component (the reorg window). +pub trait NonFinalisedState: Send + Sync { + /// A pinned view (Q1). + type Snapshot: NfsSnapshot; + + /// Pin the current reorg-window view. Returns [`NfsView::Syncing`] until the + /// window is established (boot catch-up) — the not-ready state is in the + /// type, so recent reads can't be issued against an empty window. + fn snapshot(&self) -> NfsView; + + /// Explicit tip-change subscription (drives mempool re-validation etc.). + fn subscribe_tip(&self) -> BoxStream<'_, TipEvent>; + + /// Blocks graduating past the freeze horizon — the runtime forwards each to + /// the FS component's `freeze`. + fn frozen(&self) -> BoxStream<'_, FrozenOut>; + + /// Run the tip-follow loop against `source` (a `zaino-source`-shaped + /// validator port — bounded in the impl). Where `find_trim_index` resolves + /// reorgs, one block at a time. + fn follow( + &self, + source: &S, + ) -> impl Future> + Send; +} + +/// A pinned view over the reorg window: the full surface = spine + every recent +/// facet. Reads are **in-memory over the pinned `Chain`, so infallible** — a +/// miss is `None`, not an error (contrast the FS component, whose reads hit a +/// backend). Coherent for the view's lifetime, across reorgs (ADR-0003). +/// +/// A convenience bundle for a full deployment; per-capability consumers should +/// bound on exactly the spine/facet traits they use (mirroring +/// `zaino_fs::FinalisedState`), so a variant that omits a facet is a +/// compile-time subset. +pub trait NfsSnapshot: NfsSpine + NfsSpendFacts + NfsAddressFacts {} + +impl NfsSnapshot for T {} + +/// Errors from the tip-follow loop (`follow`). +#[derive(Debug)] +pub enum FollowError { + /// The validator source failed (retryable). + Source(String), + /// A reorg deeper than the window (`find_trim_index` fuel exhausted) — + /// unresolvable by the loop; needs a resync. + ReorgTooDeep, + /// Unrecoverable. + Fatal(String), +} diff --git a/packages/zaino-nfs/src/spine.rs b/packages/zaino-nfs/src/spine.rs new file mode 100644 index 000000000..80d9f5169 --- /dev/null +++ b/packages/zaino-nfs/src/spine.rs @@ -0,0 +1,25 @@ +//! The reorg-window spine — the always-present core of a pinned NFS view. +//! +//! The window essentials + reorg/side-branch queries. Every pinned view has +//! these. The re-derived transparent facts (spend, address) are facet traits in +//! [`crate::facts`] — mirroring the FS spine/addon split, but in NFS's idiom: +//! **sync + infallible** (in-memory over the pinned `Chain`), not async/fallible. + +use zaino_core::{BlockHash, BlockId, CompactBlock, ForkPoint, Height, Locator}; + +/// A pinned view's spine: tip/range, block lookups, and reorg/side-branch +/// queries. Reads are in-memory over the pinned `Chain`, so **infallible** — a +/// miss is `None`, never an error. Coherent for the view's lifetime (ADR-0003). +pub trait NfsSpine: Clone + Send + Sync { + /// The pinned tip. + fn tip(&self) -> BlockId; + /// The height range this window covers: `[finalised + 1, tip]`. + fn range(&self) -> (Height, Height); + + fn compact_block(&self, height: Height) -> Option; + fn height_of(&self, hash: BlockHash) -> Option; + fn fork_point(&self, locator: Locator) -> Option; + + /// All current chain tips, including non-best branches (`getchaintips`). + fn chain_tips(&self) -> Vec; +} diff --git a/packages/zaino-persistence/Cargo.toml b/packages/zaino-persistence/Cargo.toml new file mode 100644 index 000000000..3700a2660 --- /dev/null +++ b/packages/zaino-persistence/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "zaino-persistence" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +description = "Driven port traits for index persistence (read/write)." + +[dependencies] +zaino-primitives = { path = "../zaino-primitives" } +thiserror.workspace = true + +[features] +testing = [] + +[lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" diff --git a/packages/zaino-persistence/src/backend.rs b/packages/zaino-persistence/src/backend.rs new file mode 100644 index 000000000..5051b5bd0 --- /dev/null +++ b/packages/zaino-persistence/src/backend.rs @@ -0,0 +1,124 @@ +//! Backend trait — the storage interface for indexed data. +//! +//! The sync engine writes through this, the serving layer reads from it. +//! Both depend on the same abstraction; neither knows the concrete +//! storage technology. +//! +//! # Namespaces +//! +//! The backend organises data into **namespaces** — independent +//! keyspaces, each with its own key ordering. In LMDB these map to +//! named databases; in RocksDB to column families; in the in-memory +//! backend to separate `HashMap`s. +//! +//! Namespaces are declared at construction time. All writes and reads +//! target a declared namespace. +//! +//! > **Note:** the upfront declaration requirement exists because LMDB +//! > (our primary backend) needs the full set of named databases at +//! > environment open time. A backend backed by RocksDB or a HashMap +//! > could support dynamic namespace creation. If we add such a backend, +//! > consider relaxing this to an `ensure_namespace` method. + +use crate::error::{CommitError, FlushError, OpenError, ReadError}; + +/// A namespace identifier — names an independent keyspace within the backend. +/// +/// Not an `IndexId`: a namespace is a storage concept (where bytes live), +/// an index is a domain concept (what the bytes mean). The engine maps +/// index IDs and its own metadata to separate namespaces. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Namespace(&'static str); + +impl Namespace { + /// Create a namespace from a static string. + pub const fn new(name: &'static str) -> Self { + Self(name) + } + + /// The string value. + pub const fn as_str(&self) -> &'static str { + self.0 + } +} + +impl core::fmt::Display for Namespace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.0) + } +} + +impl From for Namespace { + fn from(id: zaino_primitives::types::IndexId) -> Self { + Self(id.as_str()) + } +} + +/// Encoded key bytes, as produced by the index's schema encoding. +/// +/// Opaque to the backend — it stores and retrieves these without +/// interpretation. Key ordering is lexicographic on the raw bytes. +pub type RawKey = Vec; + +/// Encoded value bytes, as produced by the index's schema encoding. +/// +/// Opaque to the backend — it stores and retrieves these without +/// interpretation. +pub type RawValue = Vec; + +/// A write operation: put or delete a key-value pair in a namespace. +#[derive(Debug)] +pub enum WriteOp { + /// Insert or overwrite a key-value pair. + Put { + /// Target namespace. + namespace: Namespace, + /// Encoded key. + key: RawKey, + /// Encoded value. + value: RawValue, + }, + /// Remove a key. + Delete { + /// Target namespace. + namespace: Namespace, + /// Encoded key. + key: RawKey, + }, +} + +/// The storage backend. +/// +/// Generic — no blockchain or storage-technology knowledge. +/// Concurrency is the backend's concern: if the underlying store +/// only supports one writer at a time, the backend locks internally. +pub trait Backend: Send + Sync { + /// Reader handle type. + type Reader: BackendReader; + /// Writer handle type. + type Writer: BackendWriter; + + /// Obtain a read handle. May be called concurrently. + fn reader(&self) -> Result; + + /// Obtain a write handle. + fn writer(&self) -> Result; + + /// Force durability of all committed data. + fn flush(&self) -> Result<(), FlushError>; +} + +/// Write handle. The engine sends batches of [`WriteOp`]s through this. +pub trait BackendWriter: Send { + /// Commit a batch of write operations atomically. + fn commit(&mut self, ops: Vec) -> Result<(), CommitError>; +} + +/// Read handle. Used for state loading and query serving. +pub trait BackendReader: Send { + /// Read a single key from the given namespace. + fn get(&self, namespace: Namespace, key: &[u8]) -> Result, ReadError>; + + /// Return all entries for a namespace as raw key-value byte pairs. + fn scan(&self, namespace: Namespace) -> Result, ReadError>; +} diff --git a/packages/zaino-persistence/src/error.rs b/packages/zaino-persistence/src/error.rs new file mode 100644 index 000000000..2239fb2ee --- /dev/null +++ b/packages/zaino-persistence/src/error.rs @@ -0,0 +1,39 @@ +//! Backend error types — one per operation. + +/// Error when obtaining a reader or writer handle. +#[derive(Debug, thiserror::Error)] +pub enum OpenError { + /// The backend is not available (closed, corrupted, etc.). + #[error("backend unavailable: {0}")] + Unavailable(String), +} + +/// Error when committing a batch of write operations. +#[derive(Debug, thiserror::Error)] +pub enum CommitError { + /// A referenced namespace does not exist. + #[error("namespace not found: {0}")] + NamespaceNotFound(String), + /// The write failed (IO, transaction conflict, etc.). + #[error("write failed: {0}")] + WriteFailed(String), +} + +/// Error when reading from the backend. +#[derive(Debug, thiserror::Error)] +pub enum ReadError { + /// A referenced namespace does not exist. + #[error("namespace not found: {0}")] + NamespaceNotFound(String), + /// The read failed (IO, corruption, etc.). + #[error("read failed: {0}")] + ReadFailed(String), +} + +/// Error when flushing the backend. +#[derive(Debug, thiserror::Error)] +pub enum FlushError { + /// The flush failed (IO error). + #[error("flush failed: {0}")] + IoError(String), +} diff --git a/packages/zaino-persistence/src/in_memory.rs b/packages/zaino-persistence/src/in_memory.rs new file mode 100644 index 000000000..22a6cef13 --- /dev/null +++ b/packages/zaino-persistence/src/in_memory.rs @@ -0,0 +1,163 @@ +//! In-memory backend: correct, no IO, data lost on drop. +//! +//! Useful for tests, benchmarks, and ephemeral demo runs. + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use crate::backend::{Backend, BackendReader, BackendWriter, Namespace, RawKey, RawValue, WriteOp}; +use crate::error::{CommitError, FlushError, OpenError, ReadError}; + +/// In-memory backend. Stores key-value pairs per namespace. +/// +/// Thread-safe via `Arc>` — readers and writers share the +/// same underlying map. +#[derive(Clone)] +pub struct InMemoryBackend { + data: Arc>>>, +} + +impl InMemoryBackend { + /// Create an empty backend. + pub fn new() -> Self { + Self { + data: Arc::new(Mutex::new(HashMap::new())), + } + } + + /// Read all entries for a given namespace. For assertions. + pub fn entries(&self, namespace: Namespace) -> HashMap { + let guard = self.data.lock().expect("mutex poisoned"); + guard.get(&namespace).cloned().unwrap_or_default() + } + + /// Read a single value. For assertions. + pub fn get_value(&self, namespace: Namespace, key: &[u8]) -> Option { + let guard = self.data.lock().expect("mutex poisoned"); + guard.get(&namespace).and_then(|m| m.get(key).cloned()) + } +} + +impl Default for InMemoryBackend { + fn default() -> Self { + Self::new() + } +} + +impl Backend for InMemoryBackend { + type Reader = InMemoryReader; + type Writer = InMemoryWriter; + + fn reader(&self) -> Result { + Ok(InMemoryReader { + data: Arc::clone(&self.data), + }) + } + + fn writer(&self) -> Result { + Ok(InMemoryWriter { + data: Arc::clone(&self.data), + }) + } + + fn flush(&self) -> Result<(), FlushError> { + Ok(()) + } +} + +/// Read handle for the in-memory backend. +pub struct InMemoryReader { + data: Arc>>>, +} + +impl BackendReader for InMemoryReader { + fn get(&self, namespace: Namespace, key: &[u8]) -> Result, ReadError> { + let guard = self.data.lock().expect("mutex poisoned"); + Ok(guard.get(&namespace).and_then(|m| m.get(key).cloned())) + } + + fn scan(&self, namespace: Namespace) -> Result, ReadError> { + let guard = self.data.lock().expect("mutex poisoned"); + Ok(guard + .get(&namespace) + .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect()) + .unwrap_or_default()) + } +} + +/// Write handle for the in-memory backend. +pub struct InMemoryWriter { + data: Arc>>>, +} + +impl BackendWriter for InMemoryWriter { + fn commit(&mut self, ops: Vec) -> Result<(), CommitError> { + let mut guard = self.data.lock().expect("mutex poisoned"); + for op in ops { + match op { + WriteOp::Put { namespace, key, value } => { + guard.entry(namespace).or_default().insert(key, value); + } + WriteOp::Delete { namespace, key } => { + if let Some(map) = guard.get_mut(&namespace) { + map.remove(&key); + } + } + } + } + Ok(()) + } +} + +/// Backend wrapper that adds configurable latency to commits. +/// +/// Simulates durable write cost. Reads are unaffected. +#[derive(Clone)] +pub struct SlowBackend { + inner: B, + commit_delay: std::time::Duration, +} + +impl SlowBackend { + /// Wrap `inner` with a fixed delay per `commit` call. + pub fn new(inner: B, commit_delay: std::time::Duration) -> Self { + Self { + inner, + commit_delay, + } + } +} + +impl Backend for SlowBackend { + type Reader = B::Reader; + type Writer = SlowWriter; + + fn reader(&self) -> Result { + self.inner.reader() + } + + fn writer(&self) -> Result { + let inner = self.inner.writer()?; + Ok(SlowWriter { + inner, + delay: self.commit_delay, + }) + } + + fn flush(&self) -> Result<(), FlushError> { + self.inner.flush() + } +} + +/// Writer that sleeps before delegating to the inner writer. +pub struct SlowWriter { + inner: W, + delay: std::time::Duration, +} + +impl BackendWriter for SlowWriter { + fn commit(&mut self, ops: Vec) -> Result<(), CommitError> { + std::thread::sleep(self.delay); + self.inner.commit(ops) + } +} diff --git a/packages/zaino-persistence/src/lib.rs b/packages/zaino-persistence/src/lib.rs new file mode 100644 index 000000000..4857e6611 --- /dev/null +++ b/packages/zaino-persistence/src/lib.rs @@ -0,0 +1,14 @@ +//! Driven port traits for index persistence. +//! +//! Defines the read/write interface that both the sync engine (writer) +//! and the serving layer (reader) depend on. Backend adapters (LMDB, +//! in-memory) implement these traits. + +mod backend; +mod error; + +pub use backend::{Backend, BackendReader, BackendWriter, Namespace, RawKey, RawValue, WriteOp}; +pub use error::{CommitError, FlushError, OpenError, ReadError}; + +#[cfg(feature = "testing")] +pub mod in_memory; diff --git a/packages/zaino-primitives/Cargo.toml b/packages/zaino-primitives/Cargo.toml new file mode 100644 index 000000000..7dcfd0332 --- /dev/null +++ b/packages/zaino-primitives/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "zaino-primitives" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true + +[dependencies] +thiserror = { workspace = true } + +[lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" diff --git a/packages/zaino-primitives/src/lib.rs b/packages/zaino-primitives/src/lib.rs new file mode 100644 index 000000000..5e79ceb8b --- /dev/null +++ b/packages/zaino-primitives/src/lib.rs @@ -0,0 +1,6 @@ +//! Zaino primitives — vocabulary types for the Zcash chain. +//! +//! Zero-dependency crate. All Zaino crates that need chain-level types +//! (heights, hashes) depend on this crate instead of on each other. + +pub mod types; diff --git a/packages/zaino-primitives/src/types.rs b/packages/zaino-primitives/src/types.rs new file mode 100644 index 000000000..cd8456b6a --- /dev/null +++ b/packages/zaino-primitives/src/types.rs @@ -0,0 +1,65 @@ +//! Core vocabulary types shared across the Zaino stack. + +mod address_balance; +mod address_delta; +mod aliases; +mod block; +mod block_commitments; +mod compact_block; +mod block_hash; +mod block_verbose; +mod chain_work; +mod encrypted_ciphertext; +mod ephemeral_key; +mod height; +mod index_id; +mod merkle_root; +mod note_commitment; +mod nullifier; +mod script; +mod shielded_pool; +mod subtree_root; +pub mod transaction; +mod transaction_hash; +mod transaction_location; +mod transparent_address; +mod tree_root; +mod tree_roots; +mod treestate; +mod utxo; +mod zatoshis; + +pub use address_balance::AddressBalance; +pub use address_delta::AddressDelta; +pub use aliases::{ + BlockTime, CompactDifficulty, Confirmations, Difficulty, EquihashNonce, OutputIndex, + SubtreeIndex, TreeSize, TxIndex, +}; +pub use block::{Block, BlockHeader, ChainMetadata}; +pub use compact_block::{CompactBlock, PreIndexCompactBlock, PreIndexCompactTx}; +pub use block_commitments::BlockCommitments; +pub use block_hash::BlockHash; +pub use block_verbose::BlockVerbose; +pub use chain_work::ChainWork; +pub use encrypted_ciphertext::EncryptedCiphertext; +pub use ephemeral_key::EphemeralKey; +pub use height::{Height, HeightOverflow}; +pub use index_id::IndexId; +pub use merkle_root::MerkleRoot; +pub use note_commitment::NoteCommitment; +pub use nullifier::Nullifier; +pub use script::Script; +pub use shielded_pool::ShieldedPool; +pub use subtree_root::SubtreeRoot; +pub use transaction::{ + OrchardAction, OrchardData, SaplingData, SaplingOutput, SaplingSpend, Transaction, + TransparentData, TransparentInput, TransparentOutput, +}; +pub use transaction_hash::TransactionHash; +pub use transaction_location::TransactionLocation; +pub use transparent_address::TransparentAddress; +pub use tree_root::TreeRoot; +pub use tree_roots::{TreeRootInfo, TreeRoots}; +pub use treestate::{TreeBytes, Treestate}; +pub use utxo::Utxo; +pub use zatoshis::{SignedZatoshis, Zatoshis, ZatoshisOverflow}; diff --git a/packages/zaino-primitives/src/types/address_balance.rs b/packages/zaino-primitives/src/types/address_balance.rs new file mode 100644 index 000000000..23fbcd600 --- /dev/null +++ b/packages/zaino-primitives/src/types/address_balance.rs @@ -0,0 +1,12 @@ +//! Transparent address balance. + +use super::Zatoshis; + +/// Balance information for a set of transparent addresses. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AddressBalance { + /// Total current balance in zatoshis. + pub balance: Zatoshis, + /// Total received in zatoshis (lifetime). + pub received: Zatoshis, +} diff --git a/packages/zaino-primitives/src/types/address_delta.rs b/packages/zaino-primitives/src/types/address_delta.rs new file mode 100644 index 000000000..ec5ecd8c7 --- /dev/null +++ b/packages/zaino-primitives/src/types/address_delta.rs @@ -0,0 +1,18 @@ +//! Transparent address balance delta. + +use super::{Height, OutputIndex, SignedZatoshis, TransactionHash, TransparentAddress}; + +/// A single balance change for a transparent address. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AddressDelta { + /// Change in zatoshis (negative for spends, positive for receives). + pub satoshis: SignedZatoshis, + /// The transaction that caused this delta. + pub txid: TransactionHash, + /// Input or output index within the transaction. + pub index: OutputIndex, + /// Block height where this delta occurred. + pub height: Height, + /// The transparent address affected. + pub address: TransparentAddress, +} diff --git a/packages/zaino-primitives/src/types/aliases.rs b/packages/zaino-primitives/src/types/aliases.rs new file mode 100644 index 000000000..4e455c557 --- /dev/null +++ b/packages/zaino-primitives/src/types/aliases.rs @@ -0,0 +1,31 @@ +//! Type aliases for fields not yet promoted to newtypes. +//! +//! Each alias is a grep target: when misuse surfaces, promote +//! the alias to a newtype with private inner + constructor. + +/// Output index within a transaction. +pub type OutputIndex = u32; + +/// Number of confirmations (depth from tip). Ephemeral, query-time only. +pub type Confirmations = i64; + +/// Difficulty target. Protocol-specific float representation. +pub type Difficulty = f64; + +/// Cumulative number of notes in a commitment tree. +pub type TreeSize = u64; + +/// Subtree index within a shielded pool's commitment tree. +pub type SubtreeIndex = u16; + +/// Compact difficulty target (nBits encoding). +pub type CompactDifficulty = u32; + +/// Block timestamp (Unix epoch seconds). +pub type BlockTime = u32; + +/// Equihash nonce (32 bytes). +pub type EquihashNonce = [u8; 32]; + +/// Transaction index within a block (0-based). +pub type TxIndex = u32; diff --git a/packages/zaino-primitives/src/types/block.rs b/packages/zaino-primitives/src/types/block.rs new file mode 100644 index 000000000..2e4280927 --- /dev/null +++ b/packages/zaino-primitives/src/types/block.rs @@ -0,0 +1,52 @@ +//! Block and block header. + +use super::transaction::Transaction; +use super::{ + BlockCommitments, BlockHash, BlockTime, CompactDifficulty, EquihashNonce, Height, MerkleRoot, +}; + +/// Block header — the fields indexes need. No equihash solution +/// (indexer doesn't validate PoW). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlockHeader { + /// Block hash (double-SHA256 of the serialized header). + pub hash: BlockHash, + /// Previous block's hash. + pub prev_hash: BlockHash, + /// Block height. + pub height: Height, + /// Timestamp when mined. + pub time: BlockTime, + /// Merkle root of transaction tree. + pub merkle_root: MerkleRoot, + /// Block commitments field (hashFinalSaplingRoot / hashBlockCommitments). + pub block_commitments: BlockCommitments, + /// Compact difficulty target (nBits). + pub bits: CompactDifficulty, + /// Equihash nonce. + pub nonce: EquihashNonce, +} + +/// A complete block: header + transactions + chain metadata. +/// +/// This is the domain-level block — not a wire format. Adapters +/// parse from their wire format (hex RPC, ReadState, etc.) into +/// this type. Indexes extract from it via `ProvideContext`. +#[derive(Debug, Clone)] +pub struct Block { + /// Block header. + pub header: BlockHeader, + /// Transactions in block order. + pub transactions: Vec, + /// Commitment tree metadata after this block. + pub chain_metadata: ChainMetadata, +} + +/// Chain-level metadata attached to a block. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ChainMetadata { + /// Cumulative Sapling note commitment tree size after this block. + pub sapling_tree_size: u32, + /// Cumulative Orchard note commitment tree size after this block. + pub orchard_tree_size: u32, +} diff --git a/packages/zaino-primitives/src/types/block_commitments.rs b/packages/zaino-primitives/src/types/block_commitments.rs new file mode 100644 index 000000000..40f545816 --- /dev/null +++ b/packages/zaino-primitives/src/types/block_commitments.rs @@ -0,0 +1,20 @@ +//! Block commitments field (hashBlockCommitments / hashFinalSaplingRoot). + +/// Block commitments hash (32 bytes). +/// +/// Pre-Sapling: `hashFinalSaplingRoot`. Post-Sapling: `hashBlockCommitments` +/// digest covering multiple commitment tree roots. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BlockCommitments([u8; 32]); + +impl From<[u8; 32]> for BlockCommitments { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(bc: BlockCommitments) -> Self { + bc.0 + } +} diff --git a/packages/zaino-primitives/src/types/block_hash.rs b/packages/zaino-primitives/src/types/block_hash.rs new file mode 100644 index 000000000..347e6548f --- /dev/null +++ b/packages/zaino-primitives/src/types/block_hash.rs @@ -0,0 +1,97 @@ +//! SHA-256d block hash. + +use core::fmt; + +/// SHA-256d block hash (32 bytes, internal byte order). +/// +/// Internal byte order = little-endian as produced by the double-SHA256 +/// digest. Display and RPC use reversed (big-endian) order. +/// +/// The inner bytes are private. Use `From<[u8; 32]>` to construct and +/// `From for [u8; 32]` at boundaries that need raw bytes. +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BlockHash([u8; 32]); + +impl BlockHash { + /// The zero hash, used as a sentinel (e.g. genesis `prev_hash`). + pub const ZERO: Self = Self([0u8; 32]); +} + +impl From<[u8; 32]> for BlockHash { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(h: BlockHash) -> Self { + h.0 + } +} + +impl fmt::Debug for BlockHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // First 4 bytes in display (big-endian) order for log readability. + write!( + f, + "BlockHash({:02x}{:02x}{:02x}{:02x}…)", + self.0[31], self.0[30], self.0[29], self.0[28] + ) + } +} + +impl fmt::Display for BlockHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Full 64-char hex in display (big-endian) order. + for &byte in self.0.iter().rev() { + write!(f, "{byte:02x}")?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zero_is_all_zeroes() { + assert_eq!(<[u8; 32]>::from(BlockHash::ZERO), [0u8; 32]); + } + + #[test] + fn roundtrip_bytes() { + let bytes = [0xAB; 32]; + let hash = BlockHash::from(bytes); + assert_eq!(<[u8; 32]>::from(hash), bytes); + } + + #[test] + fn display_is_big_endian_hex() { + let mut bytes = [0u8; 32]; + bytes[31] = 0xAB; + let hash = BlockHash::from(bytes); + let display = format!("{hash}"); + assert!(display.starts_with("ab"), "got: {display}"); + assert_eq!(display.len(), 64); + } + + #[test] + fn debug_shows_truncated_prefix() { + let mut bytes = [0u8; 32]; + bytes[31] = 0xDE; + bytes[30] = 0xAD; + let hash = BlockHash::from(bytes); + let debug = format!("{hash:?}"); + assert!(debug.contains("dead"), "got: {debug}"); + assert!(debug.contains('…'), "got: {debug}"); + } + + #[test] + fn equality_and_ordering() { + let a = BlockHash::from([0u8; 32]); + let b = BlockHash::from([1u8; 32]); + assert_ne!(a, b); + assert!(a < b); + } +} diff --git a/packages/zaino-primitives/src/types/block_verbose.rs b/packages/zaino-primitives/src/types/block_verbose.rs new file mode 100644 index 000000000..75d0e3645 --- /dev/null +++ b/packages/zaino-primitives/src/types/block_verbose.rs @@ -0,0 +1,18 @@ +//! Verbose block metadata (cumulative chain state). + +use super::{ChainWork, Confirmations, Difficulty}; + +/// Verbose block metadata not present in the raw block bytes. +/// +/// Fields that require cumulative chain state (chainwork, +/// confirmations) live here; fields derivable from the raw block +/// (header, transactions) do not. +#[derive(Debug, Clone)] +pub struct BlockVerbose { + /// Cumulative chainwork at this block. + pub chainwork: ChainWork, + /// Current difficulty target. + pub difficulty: Difficulty, + /// Number of confirmations (depth from tip). + pub confirmations: Confirmations, +} diff --git a/packages/zaino-primitives/src/types/chain_work.rs b/packages/zaino-primitives/src/types/chain_work.rs new file mode 100644 index 000000000..03ae79c7a --- /dev/null +++ b/packages/zaino-primitives/src/types/chain_work.rs @@ -0,0 +1,24 @@ +//! Cumulative proof-of-work. + +/// Cumulative chainwork at a block (256-bit big-endian). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ChainWork([u8; 32]); + +impl ChainWork { + /// Wrap raw chainwork bytes (256-bit big-endian). + pub fn new(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(cw: ChainWork) -> Self { + cw.0 + } +} + +impl From<[u8; 32]> for ChainWork { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} diff --git a/packages/zaino-primitives/src/types/compact_block.rs b/packages/zaino-primitives/src/types/compact_block.rs new file mode 100644 index 000000000..783b9fc2b --- /dev/null +++ b/packages/zaino-primitives/src/types/compact_block.rs @@ -0,0 +1,117 @@ +//! Pre-index and serving compact block types. +//! +//! A [`PreIndexCompactBlock`] is what the source adapter provides: block data +//! with proofs/sigs stripped, suitable for feeding the sync engine. It does NOT +//! contain indexed state like commitment tree sizes. +//! +//! A [`CompactBlock`] is the serving-ready format (proto-adherent) that includes +//! [`ChainMetadata`]. It's the OUTPUT of indexing — constructed by reading from +//! the indexed DB, not from the source directly. +//! +//! Transaction sub-types (`TransparentInput`, `SaplingOutput`, `OrchardAction`, +//! etc.) are reused from [`super::transaction`] — no duplication. + +use super::{ + BlockHash, BlockTime, ChainMetadata, CompactDifficulty, Nullifier, OrchardAction, + SaplingOutput, TransactionHash, TransparentInput, TransparentOutput, +}; + +// --------------------------------------------------------------------------- +// Pre-index compact block — source adapter output +// --------------------------------------------------------------------------- + +/// A compact block as received from the source, before indexing. +/// +/// Contains all per-block data needed to build indexes, but no cumulative +/// indexed state (tree sizes, etc.). The sync engine consumes this type. +/// +/// Becomes a [`CompactBlock`] after indexing adds [`ChainMetadata`]. +#[derive(Debug, Clone)] +pub struct PreIndexCompactBlock { + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Block height (raw u32). + pub height: u32, + /// Block timestamp. + pub time: BlockTime, + /// Compact difficulty target (nBits). + pub bits: CompactDifficulty, + /// Compact transactions. + pub transactions: Vec, +} + +/// A compact transaction in the pre-index representation. +/// +/// Reuses existing primitives types for sub-components. +#[derive(Debug, Clone)] +pub struct PreIndexCompactTx { + /// Transaction hash. + pub txid: TransactionHash, + /// Transparent inputs — outpoint only (no script/sequence). + pub transparent_inputs: Vec, + /// Transparent outputs — value + lock script. + pub transparent_outputs: Vec, + /// Sapling nullifiers (one per spend). + pub sapling_nullifiers: Vec, + /// Sapling outputs — cmu + epk + enc_ciphertext head. + pub sapling_outputs: Vec, + /// Orchard actions — nullifier + cmx + epk + enc_ciphertext head. + pub orchard_actions: Vec, +} + +impl From<&super::Block> for PreIndexCompactBlock { + fn from(block: &super::Block) -> Self { + Self { + hash: block.header.hash, + prev_hash: block.header.prev_hash, + height: u32::from(block.header.height), + time: block.header.time, + bits: block.header.bits, + transactions: block.transactions.iter().map(PreIndexCompactTx::from).collect(), + } + } +} + +impl From<&super::transaction::Transaction> for PreIndexCompactTx { + fn from(tx: &super::transaction::Transaction) -> Self { + Self { + txid: tx.txid, + transparent_inputs: tx.transparent.inputs.clone(), + transparent_outputs: tx.transparent.outputs.clone(), + sapling_nullifiers: tx.sapling.spends.iter().map(|s| s.nullifier).collect(), + sapling_outputs: tx.sapling.outputs.clone(), + orchard_actions: tx.orchard.actions.clone(), + } + } +} + +// --------------------------------------------------------------------------- +// CompactBlock — serving format (proto-adherent, post-indexing) +// --------------------------------------------------------------------------- + +/// A complete compact block ready for serving to light wallets. +/// +/// This is the proto-adherent `CompactBlock` format. It includes +/// [`ChainMetadata`] (commitment tree sizes) which is cumulative indexed +/// state — not available from the raw block data. +/// +/// Constructed by reading from the indexed DB after sync completes. +#[derive(Debug, Clone)] +pub struct CompactBlock { + /// Block hash. + pub hash: BlockHash, + /// Previous block hash. + pub prev_hash: BlockHash, + /// Block height (raw u32). + pub height: u32, + /// Block timestamp. + pub time: BlockTime, + /// Compact difficulty target (nBits). + pub bits: CompactDifficulty, + /// Compact transactions (same shape as pre-index). + pub transactions: Vec, + /// Indexed chain metadata — commitment tree sizes at this block. + pub chain_metadata: ChainMetadata, +} diff --git a/packages/zaino-primitives/src/types/encrypted_ciphertext.rs b/packages/zaino-primitives/src/types/encrypted_ciphertext.rs new file mode 100644 index 000000000..7d5b464a9 --- /dev/null +++ b/packages/zaino-primitives/src/types/encrypted_ciphertext.rs @@ -0,0 +1,27 @@ +//! Encrypted ciphertext — partial note encryption for wallet scanning. + +/// First 52 bytes of an encrypted note ciphertext. +/// +/// Enough for a wallet to attempt trial decryption and detect +/// payments. The full 580-byte ciphertext is not needed for scanning. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EncryptedCiphertext(Vec); + +impl EncryptedCiphertext { + /// Wrap ciphertext bytes. + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl From for Vec { + fn from(c: EncryptedCiphertext) -> Self { + c.0 + } +} + +impl From> for EncryptedCiphertext { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} diff --git a/packages/zaino-primitives/src/types/ephemeral_key.rs b/packages/zaino-primitives/src/types/ephemeral_key.rs new file mode 100644 index 000000000..8e88e861b --- /dev/null +++ b/packages/zaino-primitives/src/types/ephemeral_key.rs @@ -0,0 +1,17 @@ +//! Ephemeral key — used by recipients to detect and decrypt shielded notes. + +/// An ephemeral public key (32 bytes). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EphemeralKey([u8; 32]); + +impl From<[u8; 32]> for EphemeralKey { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(k: EphemeralKey) -> Self { + k.0 + } +} diff --git a/packages/zaino-primitives/src/types/height.rs b/packages/zaino-primitives/src/types/height.rs new file mode 100644 index 000000000..ccc3ea9ca --- /dev/null +++ b/packages/zaino-primitives/src/types/height.rs @@ -0,0 +1,158 @@ +//! Block height on the Zcash chain. + +use core::fmt; + +/// Maximum valid block height (Zcash protocol limit, matches Zebra). +/// +/// `2^31 - 1`. Heights above this are rejected at construction. +const MAX_HEIGHT: u32 = (1 << 31) - 1; + +/// Block height. +/// +/// Invariant: the inner value is `≤ MAX_HEIGHT` (`2^31 - 1`). +/// Enforced at construction; all arithmetic is checked. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Height(u32); + +/// Error returned when a `u32` exceeds the protocol height limit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +#[error("height {got} exceeds protocol maximum {MAX_HEIGHT}")] +pub struct HeightOverflow { + /// The value that was rejected. + pub got: u32, +} + +impl Height { + /// The genesis block. + pub const GENESIS: Self = Self(0); + + /// Add a delta, returning `None` on overflow or protocol-limit violation. + pub fn checked_add(self, delta: u32) -> Option { + let sum = self.0.checked_add(delta)?; + if sum > MAX_HEIGHT { + return None; + } + Some(Self(sum)) + } + + /// Subtract a delta, returning `None` on underflow. + pub fn checked_sub(self, delta: u32) -> Option { + self.0.checked_sub(delta).map(Self) + } + + /// Subtract, saturating at zero. + pub fn saturating_sub(self, delta: u32) -> Self { + Self(self.0.saturating_sub(delta)) + } + + /// Distance between two heights (absolute value). + pub fn abs_diff(self, other: Self) -> u32 { + self.0.abs_diff(other.0) + } +} + +impl TryFrom for Height { + type Error = HeightOverflow; + + fn try_from(h: u32) -> Result { + if h > MAX_HEIGHT { + Err(HeightOverflow { got: h }) + } else { + Ok(Self(h)) + } + } +} + +impl From for u32 { + fn from(h: Height) -> Self { + h.0 + } +} + +impl From for u64 { + fn from(h: Height) -> Self { + u64::from(h.0) + } +} + +impl fmt::Debug for Height { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Height({})", self.0) + } +} + +impl fmt::Display for Height { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn genesis() { + assert_eq!(u32::from(Height::GENESIS), 0); + } + + #[test] + fn valid_construction() { + let h = Height::try_from(100).expect("valid height"); + assert_eq!(u32::from(h), 100); + } + + #[test] + fn max_is_valid() { + assert!(Height::try_from(MAX_HEIGHT).is_ok()); + } + + #[test] + fn above_max_rejected() { + let err = Height::try_from(MAX_HEIGHT + 1).unwrap_err(); + assert_eq!(err.got, MAX_HEIGHT + 1); + } + + #[test] + fn checked_add_within_limit() { + let h = Height::try_from(10).expect("valid"); + assert_eq!(u32::from(h.checked_add(5).expect("ok")), 15); + } + + #[test] + fn checked_add_overflow_returns_none() { + let h = Height::try_from(MAX_HEIGHT).expect("valid"); + assert!(h.checked_add(1).is_none()); + } + + #[test] + fn checked_sub_underflow_returns_none() { + assert!(Height::GENESIS.checked_sub(1).is_none()); + } + + #[test] + fn saturating_sub_floors_at_zero() { + assert_eq!(Height::GENESIS.saturating_sub(100), Height::GENESIS); + } + + #[test] + fn abs_diff_commutative() { + let a = Height::try_from(10).expect("valid"); + let b = Height::try_from(25).expect("valid"); + assert_eq!(a.abs_diff(b), 15); + assert_eq!(b.abs_diff(a), 15); + } + + #[test] + fn ordering() { + let a = Height::try_from(1).expect("valid"); + let b = Height::try_from(2).expect("valid"); + assert!(a < b); + } + + #[test] + fn into_u64() { + let h = Height::try_from(42).expect("valid"); + assert_eq!(u64::from(h), 42u64); + } +} diff --git a/packages/zaino-primitives/src/types/index_id.rs b/packages/zaino-primitives/src/types/index_id.rs new file mode 100644 index 000000000..6e5852492 --- /dev/null +++ b/packages/zaino-primitives/src/types/index_id.rs @@ -0,0 +1,26 @@ +//! Unique identifier for a registered index. + +/// Unique identifier for a registered index. +/// +/// Newtype over `&'static str` — prevents accidental use of arbitrary +/// strings where an index name is expected. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct IndexId(&'static str); + +impl IndexId { + /// Create an index identifier from a static string. + pub const fn new(name: &'static str) -> Self { + Self(name) + } + + /// The string value. + pub const fn as_str(&self) -> &'static str { + self.0 + } +} + +impl core::fmt::Display for IndexId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.0) + } +} diff --git a/packages/zaino-primitives/src/types/merkle_root.rs b/packages/zaino-primitives/src/types/merkle_root.rs new file mode 100644 index 000000000..f85e51d76 --- /dev/null +++ b/packages/zaino-primitives/src/types/merkle_root.rs @@ -0,0 +1,17 @@ +//! Merkle root of the transaction tree. + +/// Transaction merkle root (32 bytes). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MerkleRoot([u8; 32]); + +impl From<[u8; 32]> for MerkleRoot { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(m: MerkleRoot) -> Self { + m.0 + } +} diff --git a/packages/zaino-primitives/src/types/note_commitment.rs b/packages/zaino-primitives/src/types/note_commitment.rs new file mode 100644 index 000000000..1fa422292 --- /dev/null +++ b/packages/zaino-primitives/src/types/note_commitment.rs @@ -0,0 +1,17 @@ +//! Note commitment — binds a shielded note to the commitment tree. + +/// A note commitment (32 bytes). Sapling `cmu` or Orchard `cmx`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct NoteCommitment([u8; 32]); + +impl From<[u8; 32]> for NoteCommitment { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(n: NoteCommitment) -> Self { + n.0 + } +} diff --git a/packages/zaino-primitives/src/types/nullifier.rs b/packages/zaino-primitives/src/types/nullifier.rs new file mode 100644 index 000000000..1793e4103 --- /dev/null +++ b/packages/zaino-primitives/src/types/nullifier.rs @@ -0,0 +1,17 @@ +//! Nullifier — marks a shielded note as spent. + +/// A nullifier (32 bytes). Used by both Sapling and Orchard pools. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Nullifier([u8; 32]); + +impl From<[u8; 32]> for Nullifier { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(n: Nullifier) -> Self { + n.0 + } +} diff --git a/packages/zaino-primitives/src/types/script.rs b/packages/zaino-primitives/src/types/script.rs new file mode 100644 index 000000000..f9dffda70 --- /dev/null +++ b/packages/zaino-primitives/src/types/script.rs @@ -0,0 +1,24 @@ +//! Transaction output script. + +/// A transparent output script (raw bytes). +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Script(Vec); + +impl Script { + /// Wrap raw script bytes. + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl From