diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..71aeacb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0-alpha.1] - 2026-03-26 + +### Added + +- **Smart Client Sync Module** (`phaser-client`): + - `PhaserSyncer` orchestrator with parallel worker pool + - `BatchWriter` trait for pluggable storage backends + - `WriterFactory` trait for creating writers per data type + - Exponential backoff retry with `RetryPolicy` configuration + - Progress streaming via `subscribe_progress()` method + - Error categorization (transient vs permanent) for intelligent retry decisions + - Position-agnostic `Range` and `SegmentWork` types for any ordered data source + +- **Generic Position/Range Types**: + - Replaced EVM-specific `BlockRange` with generic `Range` type + - `SegmentWork` uses `HashMap>` for flexible data type tracking + - Supports any ordered data source (EVM blocks, Solana slots, Canton offsets) + +- **Proto Updates** (`phaser-query`): + - Added generic `Range` message (replaces `BlockRange`) + - Added `DataTypeRanges` for flexible missing range tracking + - Added `by_type` maps in `DataProgress` and `FileStatistics` + - Renamed `from_block`/`to_block` to `from_position`/`to_position` + +### Changed + +- `phaser-query` now uses `phaser-client::sync::SegmentWork` instead of local definition +- Proto `IncompleteSegment` uses generic `missing_ranges` field (old fields deprecated) + +### Removed + +- **Dead code from `phaser-types`**: + - `BlockRange` struct (had unused `source: DataSource` field) + - `DataSource` enum (never instantiated) + - `DataAvailability` struct (never used) + +### Deprecated + +- Proto fields `missing_blocks_ranges`, `missing_transactions_ranges`, `missing_logs_ranges` + in `IncompleteSegment` (use `missing_ranges` instead) +- Proto fields `blocks`, `transactions`, `logs` in `DataProgress` (use `by_type` instead) diff --git a/Cargo.lock b/Cargo.lock index 4736db2..85f8b4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -25,7 +16,7 @@ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -33,9 +24,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -63,9 +54,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff28fb7c2a2cf287fd2fa6291a33bcb70596230aa4b757e212ed63206733abe" +checksum = "f4f7f312b60857fb4f8e0c723a17ae9c687c0e83a3c6b7940a30d4d5a26af091" dependencies = [ "alloy-consensus", "alloy-contract", @@ -89,9 +80,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.12" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3008b4f680adca5a81fad5f6cdbb561cca0cee7e97050756c2c1f3e41d2103c" +checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" dependencies = [ "alloy-primitives", "num_enum", @@ -100,9 +91,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645b546d63ffd10bb90ec85bbd1365e99cf613273dd10968dbaf0c26264eca4f" +checksum = "1e56cae3909bcb2347f77c0f318ef5eb7e131ea6d0a94d31f8bfb6e4c5e3c7c7" dependencies = [ "alloy-eips", "alloy-primitives", @@ -111,23 +102,25 @@ dependencies = [ "alloy-trie", "alloy-tx-macros", "auto_impl", + "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more 2.1.1", "either", "k256", "once_cell", "rand 0.8.5", "secp256k1", "serde", + "serde_json", "serde_with", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-consensus-any" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b549704e83c09f66a199508b9d34ee7d0f964e6d49e7913e5e8a25a64de341" +checksum = "f4394690a8a64757316c57c57f2c663bf8395febb4c4baa049a058ebd122b668" dependencies = [ "alloy-consensus", "alloy-eips", @@ -139,9 +132,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f7ab0f7ea0b4844dd2039cfa0f0b26232c51b31aa74a1c444361e1ca043404" +checksum = "efa68d4b6fbbf44b9597684f27509573c5de3ef897907122376157f25b1f378e" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -157,14 +150,15 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", + "tracing", ] [[package]] name = "alloy-core" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe6c56d58fbfa9f0f6299376e8ce33091fc6494239466814c3f54b55743cb09" +checksum = "23e8604b0c092fabc80d075ede181c9b9e596249c70b99253082d7e689836529" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -175,9 +169,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f56873f3cac7a2c63d8e98a4314b8311aa96adb1a0f82ae923eb2119809d2c" +checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -186,7 +180,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -199,74 +193,90 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-eip2930" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "serde", ] [[package]] name = "alloy-eip7702" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "k256", "serde", - "thiserror 2.0.16", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", ] [[package]] name = "alloy-eips" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26a4df894e5665f0c5c9beeedd6db6c2aa3642686a8c37c350df50d1271b611" +checksum = "7b97433ffdb356d11b6c89b08c69a787b9f55d787cdeee733c12fdf85d465ef1" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", "alloy-serde", "auto_impl", + "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more 2.1.1", "either", "serde", "serde_with", "sha2", - "thiserror 2.0.16", ] [[package]] name = "alloy-genesis" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678a61059c150bb94139ba726f86f6f7b31d53c6b5e251060f94dba3d17d8eb" +checksum = "6173ced325833e40ccb781bb372c720df638a966bad6ed6733682b6bff63d855" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-serde", "alloy-trie", + "borsh", "serde", "serde_with", ] [[package]] name = "alloy-json-abi" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125a1c373261b252e53e04d6e92c37d881833afc1315fceab53fd46045695640" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -276,24 +286,24 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be436893c0d1f7a57d1d8f1b6b9af9db04174468410b7e6e1d1893e78110a3bc" +checksum = "ae95062f48461967424eecb1e1ab703e493b6a7aca7f9c327cc1c06758eb6ec2" dependencies = [ "alloy-primitives", "alloy-sol-types", "http", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-network" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "785b8736204e6a8dcde9b491aa7eac333b5e14f1e57bd5f81888b8a251cfbff8" +checksum = "464d9c2c5bca3ff3f020f2ab502629043486a1b3645c5c11613c1dade4eb6f5e" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -308,18 +318,18 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.0.1", + "derive_more 2.1.1", "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-network-primitives" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e17985b9e55fcd27d751b5824ac2bfebf64a4823b43e02db953b5c57229f282" +checksum = "2158d382ef9743ae7185c9fcd33cd9a99967419fdcd5eebc83ff7c0e79cad2bc" dependencies = [ "alloy-consensus", "alloy-eips", @@ -330,36 +340,36 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9485c56de23438127a731a6b4c87803d49faf1a7068dcd1d8768aca3a9edb9" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more 2.0.1", - "foldhash", - "hashbrown 0.15.5", - "indexmap 2.11.4", + "derive_more 2.1.1", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", "itoa", "k256", "keccak-asm", "paste", "proptest", "rand 0.9.2", + "rapidhash", "ruint", "rustc-hash 2.1.1", "serde", "sha3", - "tiny-keccak", ] [[package]] name = "alloy-provider" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c041912a8ccafeb36d685569ebfa852b2bb07d8576d14804a31cb117a02338" +checksum = "09b08405e82effb4985f7cbe83d5067730e79893c8699793c1e113043ffeac9d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -394,7 +404,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -403,9 +413,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7bb37096e97de25133cf904e08df2aa72168af64f429e3c43a112649e131930" +checksum = "9f39b713fb8fd7abb6ee7601d467f71d2c2541b7799dd02d3a8f9f650d1d3f6d" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -418,16 +428,16 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower 0.5.2", + "tower 0.5.3", "tracing", "wasmtimer", ] [[package]] name = "alloy-rlp" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -436,20 +446,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "alloy-rpc-client" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbeeeffa0bb7e95cb79f2b4b46b591763afeccfa9a797183c1b192377ffb6fac" +checksum = "96846729dd095dd5a87bf5bd8efea2345ca41ceab6dda212c49f5c8ca6e2a536" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -465,7 +475,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower 0.5.2", + "tower 0.5.3", "tracing", "url", "wasmtimer", @@ -473,9 +483,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c94b05986216575532c618a05d9fb590e1802f224003df8018f65420929ec08" +checksum = "4f20059ff046049aae59f6e19d96462214ad3290cf9920394309dd7ffc284390" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -490,9 +500,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c265bdbd7477d24e41cd594dd7a2022a14c9a4c58785af4bf15020ef51da075" +checksum = "e9aca954ee74bb38b24399da5bd861a2511fb76d40cca7ae700d2289ff96fb90" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -502,9 +512,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96414c5385381b4b9d19ed1ee8f3a9c24a9a084c509ef66a235b5a45221fa6a9" +checksum = "89b71da7e5cdd0d5ffebc459f5987888fd3380969c2f94f8cb652ad91ae51bba" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -513,28 +523,28 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27eaa6c63f551e35f835638397ce5c66d2ba14d0b17ce3bb286842e815b0fc94" +checksum = "30d9d9a32059dff4809fe4db016db13ba1a20babf767c4cd26e97c93ee9efe4c" dependencies = [ "alloy-primitives", - "derive_more 2.0.1", + "derive_more 2.1.1", "serde", "serde_with", ] [[package]] name = "alloy-rpc-types-engine" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301962bdc2f084bf25e86abe64d41c8a3ca1398d41d6f3416b6fffe2fe1620fc" +checksum = "f083328dbf7aa5abfe5c0a7758ec9acc86a45bfbcfb45c54186c6caa78036f2a" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", - "derive_more 2.0.1", + "derive_more 2.1.1", "rand 0.8.5", "serde", "strum", @@ -542,9 +552,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d6b2bfc7e6b29f4ebc2e86cfa520c77d4cd0d08ed54332d2f5116df8357fd7" +checksum = "1641df7ef4d15cd43843d399252c90a95abf465a67e2a2b2fd76f4e93cc60e15" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -558,28 +568,28 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-trace" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5c7e2f70d1ed7e117e5a4d6b13d547ef31c238162e46f2147ff5c45dd4326" +checksum = "b00e2d5ab1f7310c7ecd69962af8c99d2557335ff8cf1baf39c8ff6eec9c171d" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ca69c1bb9cb4cb6b80cfbdec98813acaa50101d6298a4604fb24b9176b3ad2" +checksum = "154695f4a24dd749368fd46a4bdac1ec806b4376abc40112e3028018fee5b593" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -589,9 +599,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b253eb23896e22d0cf8117fc915383d4ecf8efdedd57f590a13c8716a7347f2" +checksum = "8f1c2c0b5f024814f1c04ae76ff71862d06c836e7d67102daf8a557e5056be68" dependencies = [ "alloy-primitives", "serde", @@ -600,9 +610,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42084a7b455ef0b94ed201b7494392a759c3e20faac2d00ded5d5762fcf71dee" +checksum = "e0f59de30d9d3b13ce6a1b90e85270dc086e4e4733aaa12b250fb30e1eb35849" dependencies = [ "alloy-primitives", "async-trait", @@ -610,14 +620,14 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-signer-local" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621eafdbf1b1646c70d3b55959635c59e66ed7ad83a8b495fd9b948db09fe6c2" +checksum = "d3e64733ea58cfb393c901047638e9ff6890c0a45dc5855bfa077f8bace0f9f9" dependencies = [ "alloy-consensus", "alloy-network", @@ -626,47 +636,47 @@ dependencies = [ "async-trait", "k256", "rand 0.8.5", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "alloy-sol-macro" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20d867dcf42019d4779519a1ceb55eba8d7f3d0e4f0a89bcba82b8f9eb01e48" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74e91b0b553c115d14bd0ed41898309356dc85d0e3d4b9014c4e7715e48c8ad" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.11.4", + "indexmap 2.13.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "sha3", + "syn 2.0.117", "syn-solidity", - "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84194d31220803f5f62d0a00f583fd3a062b36382e2bea446f1af96727754565" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" dependencies = [ "alloy-json-abi", "const-hex", @@ -676,25 +686,25 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.106", + "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe8c27b3cf6b2bb8361904732f955bc7c05e00be5f469cec7e2280b6167f3ff0" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] name = "alloy-sol-types" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5383d34ea00079e6dd89c652bcbdb764db160cef84e6250926961a0b2295d04" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -704,23 +714,22 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f77fa71f6dad3aa9b97ab6f6e90f257089fb9eaa959892d153a1011618e2d6" +checksum = "ec9c680a7ee0879aa27ea2e347f14e3a973a262e4325964fa1ab062b8a796064" dependencies = [ "alloy-json-rpc", - "alloy-primitives", "auto_impl", "base64", - "derive_more 2.0.1", + "derive_more 2.1.1", "futures", "futures-utils-wasm", "parking_lot", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", - "tower 0.5.2", + "tower 0.5.3", "tracing", "url", "wasmtimer", @@ -728,24 +737,25 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1a5d0f5dd5e07187a4170bdcb7ceaff18b1133cd6b8585bc316ab442cd78a" +checksum = "ae9abaedb9123ebe4b1527bc88a0409d65b2727c3de89a289284ba91ad70ae99" dependencies = [ "alloy-json-rpc", "alloy-transport", + "itertools 0.14.0", "reqwest", "serde_json", - "tower 0.5.2", + "tower 0.5.3", "tracing", "url", ] [[package]] name = "alloy-transport-ipc" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62764e672967d7f8a890c3d28c9c9a9fc781fba59e5d869898b08073c9deae3a" +checksum = "1c74911eda4fd7ef12f446b1661f11787f9fb31bde4ab4a0a68efcac9b98cfdf" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -763,9 +773,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.36" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a21442472bad4494cfb1f11d975ae83059882a11cdda6a3aa8c0d2eb444beb6" +checksum = "130762ef3e6c0fb7dc1b855d4f359eb8ea6f2df2f800f40aa52d27737bf640f1" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -776,36 +786,36 @@ dependencies = [ "tokio", "tokio-tungstenite", "tracing", + "url", "ws_stream_wasm", ] [[package]] name = "alloy-trie" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", - "derive_more 2.0.1", + "derive_more 2.1.1", "nybbles", "serde", "smallvec", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-tx-macros" -version = "1.0.34" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d998c2f0e95079fdc8798cb48b9ea985dab225ed02005f724e66788aaf614" +checksum = "1a3684226a2220bb6e84a5ab74877e66628882336ecfba97482c9d473aa2b1cc" dependencies = [ - "alloy-primitives", "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -825,9 +835,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -840,44 +850,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ark-ff" @@ -917,6 +927,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -937,6 +967,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -962,6 +1002,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -983,6 +1036,18 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -1003,14 +1068,21 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", -] [[package]] name = "arrow" @@ -1059,7 +1131,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "num-complex", "num-integer", "num-traits", @@ -1173,7 +1245,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "indexmap 2.11.4", + "indexmap 2.13.0", "itoa", "lexical-core", "memchr", @@ -1272,9 +1344,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -1286,9 +1358,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -1323,7 +1395,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -1340,7 +1412,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -1377,7 +1449,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -1395,6 +1467,28 @@ dependencies = [ "cc", ] +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.7.9" @@ -1423,7 +1517,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", @@ -1431,11 +1525,11 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "axum-core 0.5.2", + "axum-core 0.5.6", "bytes", "futures-util", "http", @@ -1447,10 +1541,9 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "sync_wrapper", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", ] @@ -1478,9 +1571,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -1489,27 +1582,11 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -1524,9 +1601,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bimap" @@ -1561,7 +1638,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -1570,7 +1647,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1579,7 +1656,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -1599,15 +1676,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -1621,9 +1698,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -1658,6 +1735,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "bridge-test" version = "0.1.0" @@ -1696,9 +1797,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-slice-cast" @@ -1714,9 +1815,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -1733,9 +1834,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.1" +version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" dependencies = [ "blst", "cc", @@ -1754,9 +1855,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.37" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -1764,6 +1865,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -1775,9 +1882,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -1787,16 +1894,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -1839,9 +1946,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -1849,9 +1956,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -1861,36 +1968,46 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "concurrent-queue" @@ -1903,9 +2020,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.16.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", "cpufeatures", @@ -1934,16 +2051,16 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -1959,6 +2076,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-executor" version = "0.1.0" @@ -1978,7 +2104,7 @@ dependencies = [ [[package]] name = "core-executor" version = "0.1.0" -source = "git+https://github.com/dwerner/core-executor#376f2c62abbaceafef639fa24ab82199a0ce8cfe" +source = "git+https://github.com/dwerner/core-executor#dd69571382354c0a482177512afff6083b050734" dependencies = [ "async-channel 1.9.0", "async-executor", @@ -2029,9 +2155,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -2134,9 +2260,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -2144,30 +2270,30 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -2175,28 +2301,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "serde", "strsim", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -2215,9 +2340,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" @@ -2231,12 +2356,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -2261,11 +2386,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl 2.0.1", + "derive_more-impl 2.1.1", ] [[package]] @@ -2276,19 +2401,21 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", "unicode-xid", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.106", + "rustc_version 0.4.1", + "syn 2.0.117", "unicode-xid", ] @@ -2321,14 +2448,14 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "doctest-file" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" [[package]] name = "dunce" @@ -2357,6 +2484,18 @@ dependencies = [ "spki", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "either" version = "1.15.0" @@ -2386,6 +2525,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2431,7 +2590,7 @@ dependencies = [ "tonic", "tonic-prost", "tonic-prost-build", - "tower 0.5.2", + "tower 0.5.3", "tracing", "tracing-subscriber", "typed-arrow", @@ -2445,7 +2604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2551,21 +2710,20 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed-hash" @@ -2587,23 +2745,23 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flatbuffers" -version = "25.2.10" +version = "25.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1045398c1bfd89168b5fd3f1fc11f6e70b34f6f66300c87d44d3de849463abf1" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "rustc_version 0.4.1", ] [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -2618,6 +2776,12 @@ 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" @@ -2627,6 +2791,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -2648,10 +2818,10 @@ dependencies = [ "futures-core", "futures-executor", "futures-util", - "getrandom 0.2.16", + "getrandom 0.2.17", "itertools 0.13.0", "percent-encoding", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "url", "web-time", @@ -2665,7 +2835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fbefd51de0865685dc028694d492496a7dd941f10bdae49b7f942b7499594c" dependencies = [ "bytes", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] @@ -2684,9 +2854,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -2699,9 +2869,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -2709,15 +2879,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -2726,9 +2896,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -2745,13 +2915,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -2765,21 +2935,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -2789,7 +2959,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2812,36 +2981,43 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "r-efi 5.3.0", + "wasip2", "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] [[package]] name = "glob" @@ -2862,9 +3038,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -2872,7 +3048,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -2881,13 +3057,14 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "num-traits", + "zerocopy", ] [[package]] @@ -2908,17 +3085,21 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", - "serde", + "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "heck" @@ -2940,9 +3121,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -2958,12 +3139,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -3009,21 +3189,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f50d4312588681f6d07e6009728bf5c777e1f674d43a3ad91d15f6795a0db965" dependencies = [ "arrayvec", - "bitflags 2.9.4", - "derive_more 2.0.1", + "bitflags 2.11.0", + "derive_more 2.1.1", "errno", "hwlocality-sys", "libc", "strum", - "thiserror 2.0.16", - "windows-sys 0.61.1", + "thiserror 2.0.18", + "windows-sys 0.61.2", ] [[package]] name = "hwlocality-sys" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98f0a5f1ab804ba087ef715ce5cd4feaab6222a2ef6b3e9d5ae1536e90393728" +checksum = "6d55ff554bde432473a6d17dc219a2d7fedc1be12d1e150418526f666dc9d096" dependencies = [ "autotools", "cmake", @@ -3033,14 +3213,14 @@ dependencies = [ "sha3", "tar", "ureq", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -3073,7 +3253,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.2", ] [[package]] @@ -3091,14 +3270,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -3115,9 +3293,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3139,9 +3317,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -3152,9 +3330,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -3165,11 +3343,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -3180,42 +3357,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -3223,6 +3396,12 @@ dependencies = [ "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" @@ -3267,7 +3446,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -3283,12 +3462,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -3301,9 +3480,9 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "interprocess" -version = "2.2.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" dependencies = [ "doctest-file", "futures-core", @@ -3314,28 +3493,17 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" dependencies = [ "memchr", "serde", @@ -3343,20 +3511,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -3387,9 +3555,53 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] name = "jobserver" @@ -3397,15 +3609,15 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -3426,6 +3638,7 @@ dependencies = [ "arrow", "arrow-array", "arrow-flight", + "arrow-ipc", "arrow-schema", "async-stream", "async-trait", @@ -3446,7 +3659,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tower 0.5.2", + "tower 0.5.3", "tracing", "tracing-subscriber", "typed-arrow", @@ -3455,9 +3668,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.24.9" +version = "0.24.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b26c20e2178756451cfeb0661fb74c47dd5988cb7e3939de7e9241fd604d42" +checksum = "e281ae70cc3b98dac15fced3366a880949e65fc66e345ce857a5682d152f3e62" dependencies = [ "jsonrpsee-core", "jsonrpsee-server", @@ -3467,9 +3680,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.9" +version = "0.24.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456196007ca3a14db478346f58c7238028d55ee15c1df15115596e411ff27925" +checksum = "348ee569eaed52926b5e740aae20863762b16596476e943c9e415a6479021622" dependencies = [ "async-trait", "bytes", @@ -3490,9 +3703,9 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.24.9" +version = "0.24.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e363146da18e50ad2b51a0a7925fc423137a0b1371af8235b1c231a0647328" +checksum = "21429bcdda37dcf2d43b68621b994adede0e28061f816b038b0f18c70c143d51" dependencies = [ "futures-util", "http", @@ -3517,9 +3730,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.9" +version = "0.24.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a8e70baf945b6b5752fc8eb38c918a48f1234daf11355e07106d963f860089" +checksum = "b0f05e0028e55b15dbd2107163b3c744cd3bb4474f193f95d9708acbf5677e44" dependencies = [ "http", "serde", @@ -3543,18 +3756,18 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -3572,11 +3785,17 @@ 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 = "lexical-core" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b765c31809609075565a70b4b71402281283aeda7ecaf4818ac14a7b2ade8958" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" dependencies = [ "lexical-parse-float", "lexical-parse-integer", @@ -3587,86 +3806,80 @@ dependencies = [ [[package]] name = "lexical-parse-float" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" dependencies = [ "lexical-parse-integer", "lexical-util", - "static_assertions", ] [[package]] name = "lexical-parse-integer" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" dependencies = [ "lexical-util", - "static_assertions", ] [[package]] name = "lexical-util" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" -dependencies = [ - "static_assertions", -] +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" [[package]] name = "lexical-write-float" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5afc668a27f460fb45a81a757b6bf2f43c2d7e30cb5a2dcd3abf294c78d62bd" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" dependencies = [ "lexical-util", "lexical-write-integer", - "static_assertions", ] [[package]] name = "lexical-write-integer" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629ddff1a914a836fb245616a7888b62903aae58fa771e1d83943035efa0f978" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" dependencies = [ "lexical-util", - "static_assertions", ] [[package]] name = "libc" -version = "0.2.175" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "libc", - "redox_syscall", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -3685,20 +3898,11 @@ dependencies = [ "zstd-sys", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" -dependencies = [ - "zlib-rs", -] - [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" dependencies = [ "cc", "pkg-config", @@ -3707,39 +3911,38 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.13.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -3760,9 +3963,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6473172471198271ff72e9379150e9dfd70d8e533e0752a27e515b48dd375e" +checksum = "98c23545df7ecf1b16c303910a69b079e8e251d60f7dd2cc9b4177f2afaf1746" dependencies = [ "twox-hash", ] @@ -3775,7 +3978,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -3801,9 +4004,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -3824,17 +4027,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -3855,11 +4059,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3883,9 +4087,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -3918,9 +4122,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -3928,20 +4132,20 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "nybbles" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa11e84403164a9f12982ab728f3c67c6fd4ab5b5f0254ffc217bdbd3b28ab0" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" dependencies = [ "alloy-rlp", "cfg-if", @@ -3951,26 +4155,17 @@ dependencies = [ "smallvec", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -4018,7 +4213,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -4029,9 +4224,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -4039,15 +4234,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -4071,7 +4266,7 @@ dependencies = [ "flate2", "futures", "half", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "lz4_flex", "num-bigint", "num-integer", @@ -4163,12 +4358,11 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.2" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", - "thiserror 2.0.16", "ucd-trie", ] @@ -4180,7 +4374,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap 2.13.0", ] [[package]] @@ -4195,14 +4389,16 @@ dependencies = [ [[package]] name = "phaser-client" -version = "0.1.0" +version = "0.2.0-alpha.1" dependencies = [ + "anyhow", "arrow", "arrow-array", "arrow-flight", "arrow-ipc", "arrow-schema", "async-stream", + "async-trait", "futures", "http", "hyper-util", @@ -4213,7 +4409,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tonic", - "tower 0.5.2", + "tower 0.5.3", "tracing", ] @@ -4263,7 +4459,7 @@ dependencies = [ [[package]] name = "phaser-query" -version = "0.1.0" +version = "0.2.0-alpha.1" dependencies = [ "alloy-consensus", "alloy-rlp", @@ -4329,7 +4525,7 @@ dependencies = [ [[package]] name = "phaser-types" -version = "0.1.0" +version = "0.2.0-alpha.1" dependencies = [ "anyhow", "arrow-array", @@ -4343,29 +4539,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -4389,6 +4585,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plotters" version = "0.3.7" @@ -4419,9 +4621,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -4448,7 +4650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -4464,9 +4666,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -4490,14 +4692,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -4519,14 +4721,13 @@ dependencies = [ [[package]] name = "proptest" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.4", - "lazy_static", + "bitflags 2.11.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -4564,7 +4765,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.106", + "syn 2.0.117", "tempfile", ] @@ -4578,7 +4779,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -4598,11 +4799,11 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "pulldown-cmark" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "memchr", "unicase", ] @@ -4636,7 +4837,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.16", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -4644,12 +4845,13 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", @@ -4657,7 +4859,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.16", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4679,9 +4881,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -4692,6 +4894,12 @@ 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" @@ -4717,7 +4925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", "serde", ] @@ -4738,7 +4946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -4747,16 +4955,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "serde", ] @@ -4766,7 +4974,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", ] [[package]] @@ -4797,38 +5014,47 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -4838,9 +5064,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -4849,15 +5075,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", @@ -4875,20 +5101,19 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", - "tower 0.5.2", + "tower 0.5.3", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.2", ] [[package]] @@ -4909,7 +5134,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -4943,13 +5168,14 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "ruint" -version = "1.16.0" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -4963,7 +5189,7 @@ dependencies = [ "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -4974,12 +5200,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustc-hash" version = "1.1.0" @@ -5018,23 +5238,24 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -5057,30 +5278,49 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "rustls-pki-types", + "web-time", + "zeroize", ] [[package]] -name = "rustls-pki-types" -version = "1.12.0" +name = "rustls-platform-verifier" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "web-time", - "zeroize", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5094,9 +5334,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", "quick-error", @@ -5106,9 +5346,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -5121,11 +5361,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -5142,9 +5382,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -5196,11 +5436,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "core-foundation", "core-foundation-sys", "libc", @@ -5255,9 +5495,9 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -5265,35 +5505,35 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -5321,19 +5561,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", + "schemars 1.2.1", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -5341,14 +5580,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -5357,7 +5596,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -5408,9 +5647,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" dependencies = [ "cc", "cfg-if", @@ -5433,10 +5672,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -5450,6 +5690,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simdutf8" version = "0.1.5" @@ -5458,9 +5704,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -5479,12 +5725,12 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5515,9 +5761,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -5549,7 +5795,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -5571,9 +5817,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -5582,14 +5828,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.3.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b198d366dbec045acfcd97295eb653a7a2b40e4dc764ef1e79aafcad439d3c" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -5609,7 +5855,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -5620,9 +5866,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" dependencies = [ "filetime", "libc", @@ -5631,15 +5877,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -5653,11 +5899,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.18", ] [[package]] @@ -5668,18 +5914,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -5713,30 +5959,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -5753,9 +5999,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -5773,9 +6019,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -5788,40 +6034,37 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "tokio-rustls" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -5829,9 +6072,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -5841,9 +6084,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", @@ -5857,9 +6100,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -5871,32 +6114,32 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.25.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.13.0", "toml_datetime", "toml_parser", - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] @@ -5906,7 +6149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", - "axum 0.8.4", + "axum 0.8.8", "base64", "bytes", "flate2", @@ -5925,7 +6168,7 @@ dependencies = [ "tokio", "tokio-rustls", "tokio-stream", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", @@ -5941,7 +6184,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -5966,7 +6209,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.106", + "syn 2.0.117", "tempfile", "tonic-build", ] @@ -5988,13 +6231,13 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.11.4", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -6007,18 +6250,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytes", "futures-util", "http", "http-body", "iri-string", "pin-project-lite", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", ] @@ -6037,9 +6280,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -6049,20 +6292,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -6081,9 +6324,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -6105,9 +6348,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", @@ -6118,7 +6361,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.16", + "thiserror 2.0.18", "utf-8", ] @@ -6138,7 +6381,7 @@ dependencies = [ "arrow-data", "arrow-schema", "half", - "thiserror 2.0.16", + "thiserror 2.0.18", "typed-arrow-derive", ] @@ -6149,14 +6392,14 @@ source = "git+https://github.com/tonbo-io/typed-arrow?rev=bb351a14915641d96d1888 dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -6190,9 +6433,15 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -6214,27 +6463,26 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.1.2" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64", "flate2", "log", "percent-encoding", "rustls", - "rustls-pemfile", "rustls-pki-types", "ureq-proto", - "utf-8", - "webpki-roots 1.0.2", + "utf8-zero", + "webpki-roots 1.0.6", ] [[package]] name = "ureq-proto" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" dependencies = [ "base64", "http", @@ -6244,14 +6492,15 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -6260,6 +6509,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6274,11 +6529,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -6356,28 +6611,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wasip2", + "wit-bindgen", ] [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +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 = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -6386,27 +6641,14 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.53" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -6415,9 +6657,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6425,26 +6667,60 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" 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.13.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.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver 1.0.27", +] + [[package]] name = "wasmtimer" version = "0.4.3" @@ -6471,7 +6747,7 @@ dependencies = [ [[package]] name = "wat-cpu" version = "0.1.0" -source = "git+https://github.com/dwerner/core-executor#376f2c62abbaceafef639fa24ab82199a0ce8cfe" +source = "git+https://github.com/dwerner/core-executor#dd69571382354c0a482177512afff6083b050734" dependencies = [ "libc", "winapi", @@ -6479,9 +6755,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.80" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -6497,29 +6773,38 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -6543,7 +6828,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -6554,67 +6839,70 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-link" -version = "0.2.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] [[package]] -name = "windows-result" -version = "0.4.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] -name = "windows-strings" -version = "0.5.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-link 0.2.0", + "windows-targets 0.42.2", ] [[package]] @@ -6641,16 +6929,31 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -6671,21 +6974,27 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6694,9 +7003,15 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -6706,9 +7021,15 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -6718,9 +7039,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -6730,9 +7051,15 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -6742,9 +7069,15 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -6754,9 +7087,15 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -6766,9 +7105,15 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -6778,30 +7123,121 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.7.13" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[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.13.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.0", + "indexmap 2.13.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.13.0", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "ws_stream_wasm" @@ -6816,7 +7252,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper", - "thiserror 2.0.16", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6843,11 +7279,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -6855,34 +7290,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] @@ -6902,35 +7337,35 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -6939,9 +7374,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -6950,20 +7385,26 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.117", ] [[package]] name = "zlib-rs" -version = "0.5.2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 2a46928..92ac397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,12 @@ members = [ "crates/tools/bridge-test", ] +[workspace.package] +version = "0.2.0-alpha.1" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/edgeandnode/phaser" + [workspace.dependencies] # Internal crates erigon-bridge = { path = "crates/bridges/evm/erigon-bridge" } diff --git a/README.md b/README.md index 6558fc0..5f0dd12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Phaser 🔫 +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE) + A protocol abstraction layer for blockchain data using Apache Arrow Flight. ## Overview @@ -308,4 +310,9 @@ See `erigon-bridge` (uses EVM schema) and `jsonrpc-bridge` for reference impleme ## License -TODO: Add license information +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/crates/bridges/evm/erigon-bridge/src/bridge.rs b/crates/bridges/evm/erigon-bridge/src/bridge.rs index 969999d..dfd935a 100644 --- a/crates/bridges/evm/erigon-bridge/src/bridge.rs +++ b/crates/bridges/evm/erigon-bridge/src/bridge.rs @@ -829,35 +829,22 @@ impl FlightBridge for ErigonFlightBridge { &self, request: Request, ) -> Result, Status> { + use arrow_flight::SchemaAsIpc; + let descriptor = request.into_inner(); let query = Self::parse_descriptor(&descriptor)?; let stream_type = Self::table_to_stream_type(&query.table)?; let schema = Self::get_schema_for_type(stream_type); - // Convert Arrow schema to IPC format for Flight - // SchemaResult expects raw bytes - we need to encode the schema properly - let ipc_message = { - use arrow::ipc::writer::IpcWriteOptions; - let options = IpcWriteOptions::default(); - - // Use FlightDataEncoderBuilder to get the schema as bytes - let encoder = FlightDataEncoderBuilder::new() - .with_schema(schema.clone()) - .with_options(options) - .build(stream::empty()); - - // Get just the schema message (first item from encoder) - let mut encoded_schema = vec![]; - futures::pin_mut!(encoder); - if let Some(Ok(flight_data)) = encoder.next().await { - encoded_schema = flight_data.data_header.to_vec(); - } - encoded_schema - }; + // Convert Arrow schema to IPC format for Flight SchemaResult + let options = IpcWriteOptions::default(); + let schema_result: SchemaResult = SchemaAsIpc::new(&schema, &options).try_into().map_err( + |e: arrow::error::ArrowError| { + Status::internal(format!("Failed to encode schema: {}", e)) + }, + )?; - Ok(Response::new(SchemaResult { - schema: ipc_message.into(), - })) + Ok(Response::new(schema_result)) } async fn do_get( diff --git a/crates/bridges/evm/jsonrpc-bridge/Cargo.toml b/crates/bridges/evm/jsonrpc-bridge/Cargo.toml index df2f5ac..0e04611 100644 --- a/crates/bridges/evm/jsonrpc-bridge/Cargo.toml +++ b/crates/bridges/evm/jsonrpc-bridge/Cargo.toml @@ -32,6 +32,7 @@ arrow = { workspace = true } arrow-flight = { workspace = true } arrow-array = { workspace = true } arrow-schema = { workspace = true } +arrow-ipc = { workspace = true } typed-arrow = { workspace = true } tokio = { workspace = true } async-trait = { workspace = true } diff --git a/crates/bridges/evm/jsonrpc-bridge/src/bridge.rs b/crates/bridges/evm/jsonrpc-bridge/src/bridge.rs index ab32b43..7f0c804 100644 --- a/crates/bridges/evm/jsonrpc-bridge/src/bridge.rs +++ b/crates/bridges/evm/jsonrpc-bridge/src/bridge.rs @@ -1129,34 +1129,23 @@ impl FlightBridge for JsonRpcFlightBridge { &self, request: Request, ) -> std::result::Result, Status> { + use arrow_flight::SchemaAsIpc; + use arrow_ipc::writer::IpcWriteOptions; + let descriptor = request.into_inner(); let query = Self::parse_descriptor(&descriptor)?; let stream_type = Self::table_to_stream_type(&query.table)?; let schema = Self::get_schema_for_type(stream_type).map_err(|e| *e)?; - // Convert Arrow schema to IPC format for Flight - let ipc_message = { - use arrow::ipc::writer::IpcWriteOptions; - let options = IpcWriteOptions::default(); - - // Use FlightDataEncoderBuilder to get the schema as bytes - let encoder = FlightDataEncoderBuilder::new() - .with_schema(schema.clone()) - .with_options(options) - .build(stream::empty()); - - // Get just the schema message (first item from encoder) - let mut encoded_schema = vec![]; - futures::pin_mut!(encoder); - if let Some(Ok(flight_data)) = encoder.next().await { - encoded_schema = flight_data.data_header.to_vec(); - } - encoded_schema - }; + // Convert Arrow schema to IPC format for Flight SchemaResult + let options = IpcWriteOptions::default(); + let schema_result: SchemaResult = SchemaAsIpc::new(&schema, &options).try_into().map_err( + |e: arrow::error::ArrowError| { + Status::internal(format!("Failed to encode schema: {}", e)) + }, + )?; - Ok(Response::new(SchemaResult { - schema: ipc_message.into(), - })) + Ok(Response::new(schema_result)) } async fn do_get( diff --git a/crates/phaser-client/Cargo.toml b/crates/phaser-client/Cargo.toml index d5eb413..22521c7 100644 --- a/crates/phaser-client/Cargo.toml +++ b/crates/phaser-client/Cargo.toml @@ -1,8 +1,10 @@ [package] name = "phaser-client" -version = "0.1.0" -edition = "2021" -description = "Flight client for connecting to phaser bridges" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Flight client for connecting to phaser bridges with smart sync orchestration" [dependencies] # Core types @@ -22,6 +24,7 @@ futures = "0.3" tower = "0.5" hyper-util = "0.1" async-stream = "0.3" +async-trait = "0.1" # Serialization serde = { workspace = true } @@ -30,9 +33,13 @@ prost = { workspace = true } # Error handling thiserror = { workspace = true } +anyhow = { workspace = true } # Logging tracing = { workspace = true } # HTTP types (for InvalidUri) http = "1.0" + +[dev-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/phaser-client/src/lib.rs b/crates/phaser-client/src/lib.rs index 0ba393a..4e12b3e 100644 --- a/crates/phaser-client/src/lib.rs +++ b/crates/phaser-client/src/lib.rs @@ -1,9 +1,11 @@ //! Flight client for connecting to phaser bridges //! -//! This crate provides a minimal client for connecting to bridges without -//! pulling in server dependencies. Use this in consumers like flight-streamer. +//! This crate provides a client for connecting to Phaser bridges, with both +//! low-level Flight protocol access and high-level sync orchestration. //! -//! # Example +//! # Low-Level API +//! +//! The `PhaserClient` provides direct access to bridge streams: //! //! ```ignore //! use phaser_client::{PhaserClient, BridgeError, GenericQuery}; @@ -22,9 +24,36 @@ //! Ok(()) //! } //! ``` +//! +//! # High-Level Sync API +//! +//! The `sync` module provides orchestration for syncing large amounts of data +//! with automatic retry, work queues, and progress tracking: +//! +//! ```ignore +//! use phaser_client::sync::{PhaserSyncer, SyncConfig, BatchWriter, DataType}; +//! +//! // Implement BatchWriter for your storage backend +//! struct MyWriter { /* ... */ } +//! +//! impl BatchWriter for MyWriter { +//! fn data_type(&self) -> DataType { DataType::Blocks } +//! async fn write_batch(&mut self, batch: RecordBatch) -> Result { +//! // Write to your storage +//! Ok(bytes_written) +//! } +//! // ... +//! } +//! +//! // Use the syncer +//! let config = SyncConfig::default(); +//! let syncer = PhaserSyncer::new(config, writer_factory); +//! let result = syncer.sync_range("http://bridge:50051", 0, 1_000_000, work).await?; +//! ``` mod client; mod error; +pub mod sync; pub use client::PhaserClient; pub use error::BridgeError; @@ -36,14 +65,11 @@ pub use phaser_types::{ // Batch metadata BatchMetadata, BatchWithRange, - BlockRange, // Descriptors BlockchainDescriptor, BridgeInfo, Compression, ControlAction, - DataAvailability, - DataSource, // Discovery DiscoveryCapabilities, FilterDescriptor, diff --git a/crates/phaser-client/src/sync/config.rs b/crates/phaser-client/src/sync/config.rs new file mode 100644 index 0000000..b23bbbd --- /dev/null +++ b/crates/phaser-client/src/sync/config.rs @@ -0,0 +1,172 @@ +//! Configuration for sync operations + +use std::time::Duration; + +/// Retry policy configuration for sync operations +#[derive(Debug, Clone)] +pub struct RetryPolicy { + /// Initial backoff duration for retries + pub initial_backoff: Duration, + /// Maximum backoff duration + pub max_backoff: Duration, + /// Maximum number of retries without making progress before giving up + pub max_retries_without_progress: u32, +} + +impl Default for RetryPolicy { + fn default() -> Self { + Self { + initial_backoff: Duration::from_secs(1), + max_backoff: Duration::from_secs(60), + max_retries_without_progress: 5, + } + } +} + +impl RetryPolicy { + /// Calculate backoff duration for a given retry count + pub fn backoff_for_retry(&self, retry_count: u32) -> Duration { + let backoff_secs = self + .initial_backoff + .as_secs() + .saturating_mul(2u64.pow(retry_count.saturating_sub(1))); + Duration::from_secs(backoff_secs.min(self.max_backoff.as_secs())) + } +} + +/// Configuration for the PhaserSyncer +#[derive(Debug, Clone)] +pub struct SyncConfig { + /// Size of each segment in blocks (e.g., 500_000) + pub segment_size: u64, + /// Maximum number of concurrent workers + pub max_workers: u32, + /// Maximum number of segments that can sync logs concurrently + /// (logs are typically memory-intensive) + pub max_concurrent_log_segments: u32, + /// Retry policy for transient errors + pub retry_policy: RetryPolicy, + /// Batch size for queries (number of blocks per request) + pub batch_size: u32, + /// Whether to enable trace data in log queries + pub enable_traces: bool, +} + +impl Default for SyncConfig { + fn default() -> Self { + Self { + segment_size: 500_000, + max_workers: 4, + max_concurrent_log_segments: 1, + retry_policy: RetryPolicy::default(), + batch_size: 1000, + enable_traces: true, + } + } +} + +impl SyncConfig { + /// Create a new SyncConfig with the given segment size + pub fn with_segment_size(segment_size: u64) -> Self { + Self { + segment_size, + ..Default::default() + } + } + + /// Set the maximum number of workers + pub fn with_max_workers(mut self, max_workers: u32) -> Self { + self.max_workers = max_workers; + self + } + + /// Set the maximum concurrent log segments + pub fn with_max_concurrent_log_segments(mut self, max: u32) -> Self { + self.max_concurrent_log_segments = max; + self + } + + /// Set the retry policy + pub fn with_retry_policy(mut self, policy: RetryPolicy) -> Self { + self.retry_policy = policy; + self + } + + /// Set whether to enable traces + pub fn with_traces(mut self, enable: bool) -> Self { + self.enable_traces = enable; + self + } + + /// Calculate the segment number for a given block + pub fn segment_for_block(&self, block: u64) -> u64 { + block / self.segment_size + } + + /// Calculate the start block for a segment + pub fn segment_start(&self, segment_num: u64) -> u64 { + segment_num * self.segment_size + } + + /// Calculate the end block for a segment + pub fn segment_end(&self, segment_num: u64) -> u64 { + (segment_num + 1) * self.segment_size - 1 + } + + /// Get the first and last segment numbers for a block range + pub fn segment_range(&self, from_block: u64, to_block: u64) -> (u64, u64) { + let first_segment = self.segment_for_block(from_block); + let last_segment = self.segment_for_block(to_block); + (first_segment, last_segment) + } + + /// Calculate total segments in a block range + pub fn total_segments(&self, from_block: u64, to_block: u64) -> u64 { + let (first, last) = self.segment_range(from_block, to_block); + last - first + 1 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_segment_calculations() { + let config = SyncConfig::with_segment_size(500_000); + + assert_eq!(config.segment_for_block(0), 0); + assert_eq!(config.segment_for_block(499_999), 0); + assert_eq!(config.segment_for_block(500_000), 1); + assert_eq!(config.segment_for_block(1_000_000), 2); + + assert_eq!(config.segment_start(0), 0); + assert_eq!(config.segment_start(1), 500_000); + + assert_eq!(config.segment_end(0), 499_999); + assert_eq!(config.segment_end(1), 999_999); + } + + #[test] + fn test_segment_range() { + let config = SyncConfig::with_segment_size(500_000); + + assert_eq!(config.segment_range(0, 499_999), (0, 0)); + assert_eq!(config.segment_range(0, 1_000_000), (0, 2)); + assert_eq!(config.segment_range(500_000, 999_999), (1, 1)); + } + + #[test] + fn test_backoff_calculation() { + let policy = RetryPolicy::default(); + + // First retry: 1s + assert_eq!(policy.backoff_for_retry(1), Duration::from_secs(1)); + // Second retry: 2s + assert_eq!(policy.backoff_for_retry(2), Duration::from_secs(2)); + // Third retry: 4s + assert_eq!(policy.backoff_for_retry(3), Duration::from_secs(4)); + // Should cap at max_backoff + assert_eq!(policy.backoff_for_retry(10), Duration::from_secs(60)); + } +} diff --git a/crates/phaser-client/src/sync/error.rs b/crates/phaser-client/src/sync/error.rs new file mode 100644 index 0000000..c1ddfa0 --- /dev/null +++ b/crates/phaser-client/src/sync/error.rs @@ -0,0 +1,623 @@ +//! Error types for sync operations +//! +//! This module provides rich error types with categorization for intelligent +//! retry decisions. Errors are categorized based on their message content +//! to determine if they are transient (should retry) or permanent (should fail). + +use std::fmt; + +/// Categorize an error message into an ErrorCategory +/// This is the single source of truth for error categorization based on message content +pub fn categorize_error_message(err_lower: &str) -> ErrorCategory { + // Connection errors + if err_lower.contains("connection") || err_lower.contains("connect") { + return ErrorCategory::Connection; + } + + // Timeout errors + if err_lower.contains("timeout") || err_lower.contains("timed out") { + return ErrorCategory::Timeout; + } + + // Cancelled errors + if err_lower.contains("cancelled") || err_lower.contains("canceled") { + return ErrorCategory::Cancelled; + } + + // Block/data not found - non-transient for historical sync + if err_lower.contains("header not found") || err_lower.contains("block not found") { + return ErrorCategory::NoData; + } + + // No data / empty responses + if err_lower.contains("no data") || err_lower.contains("empty") { + return ErrorCategory::NoData; + } + + // Stuck worker errors + if err_lower.contains("failed to make progress") { + return ErrorCategory::StuckWorker; + } + + // Protocol errors (bridge returned zero batches, etc.) + if err_lower.contains("zero batches") || err_lower.contains("protocol error") { + return ErrorCategory::ProtocolError; + } + + // Disk I/O errors - check BEFORE validation since some validation errors might mention "file" + // Include parquet/arrow write errors which are typically disk or serialization issues + if err_lower.contains("io error") + || err_lower.contains("disk") + || err_lower.contains("parquet") + || err_lower.contains("arrow") + || err_lower.contains("write error") + || err_lower.contains("failed to write") + || (err_lower.contains("file") && !err_lower.contains("validation")) + { + return ErrorCategory::DiskIo; + } + + // Validation errors + if err_lower.contains("validation") || err_lower.contains("invalid") { + return ErrorCategory::Validation; + } + + // Unknown - catch-all for unrecognized errors + ErrorCategory::Unknown +} + +/// Type of data being synced when error occurred +/// +/// This is a simple string wrapper - no assumptions about specific table names. +/// Use whatever table names your bridge exposes. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DataType(String); + +impl DataType { + /// Create a new DataType from a string + pub fn new(name: impl Into) -> Self { + Self(name.into()) + } + + /// Get the string representation + pub fn as_str(&self) -> &str { + &self.0 + } + + /// Unknown type (for errors where type cannot be determined) + pub fn unknown() -> Self { + Self("unknown".to_string()) + } +} + +impl From<&str> for DataType { + fn from(s: &str) -> Self { + Self::new(s) + } +} + +impl From for DataType { + fn from(s: String) -> Self { + Self(s) + } +} + +impl fmt::Display for DataType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +/// Category of sync error for metrics and monitoring +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ErrorCategory { + /// Connection or networking issues + Connection, + /// Request timeout + Timeout, + /// Bridge returned no data for a range that should have data + NoData, + /// Worker stuck making no progress + StuckWorker, + /// Data validation failure + Validation, + /// Disk I/O error + DiskIo, + /// Operation was cancelled + Cancelled, + /// Bridge protocol error (e.g., zero batches returned) + ProtocolError, + /// Other/uncategorized error + Unknown, +} + +impl ErrorCategory { + pub fn as_str(&self) -> &'static str { + match self { + ErrorCategory::Connection => "connection", + ErrorCategory::Timeout => "timeout", + ErrorCategory::NoData => "no_data", + ErrorCategory::StuckWorker => "stuck_worker", + ErrorCategory::Validation => "validation", + ErrorCategory::DiskIo => "disk_io", + ErrorCategory::Cancelled => "cancelled", + ErrorCategory::ProtocolError => "protocol_error", + ErrorCategory::Unknown => "unknown", + } + } + + /// Returns true if this error category is transient and should be retried + pub fn is_transient(&self) -> bool { + matches!( + self, + ErrorCategory::Connection | ErrorCategory::Timeout | ErrorCategory::Cancelled + ) + } + + /// Returns true if this error category is permanent and should not be retried + pub fn is_permanent(&self) -> bool { + matches!( + self, + ErrorCategory::Validation | ErrorCategory::StuckWorker | ErrorCategory::NoData + ) + } +} + +impl fmt::Display for ErrorCategory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +/// Structured sync error that preserves error context while providing categorization +#[derive(Debug)] +pub struct SyncError { + /// What type of data was being synced + pub data_type: DataType, + /// Category of error for metrics + pub category: ErrorCategory, + /// Block range being synced when error occurred + pub from_block: u64, + pub to_block: u64, + /// Human-readable error message + pub message: String, + /// The underlying error source, if any + pub source: Option>, +} + +/// Error when multiple data types fail during parallel sync +#[derive(Debug)] +pub struct MultipleDataTypeErrors { + pub from_block: u64, + pub to_block: u64, + pub errors: Vec<(DataType, SyncError)>, +} + +impl fmt::Display for MultipleDataTypeErrors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Multiple data types failed syncing blocks {}-{}: ", + self.from_block, self.to_block + )?; + for (i, (data_type, err)) in self.errors.iter().enumerate() { + if i > 0 { + write!(f, "; ")?; + } + write!(f, "{data_type}: {err}")?; + } + Ok(()) + } +} + +impl std::error::Error for MultipleDataTypeErrors { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + // Return first error as source + self.errors.first().and_then(|(_, e)| e.source()) + } +} + +impl From for SyncError { + fn from(multi_err: MultipleDataTypeErrors) -> Self { + // Aggregate into a single SyncError with Unknown data type + let message = format!("{multi_err}"); + let category = multi_err + .errors + .first() + .map(|(_, e)| e.category) + .unwrap_or(ErrorCategory::Unknown); + + SyncError { + data_type: DataType::unknown(), + category, + from_block: multi_err.from_block, + to_block: multi_err.to_block, + message, + source: Some(Box::new(multi_err)), + } + } +} + +impl SyncError { + pub fn new( + data_type: DataType, + category: ErrorCategory, + from_block: u64, + to_block: u64, + message: String, + ) -> Self { + Self { + data_type, + category, + from_block, + to_block, + message, + source: None, + } + } + + pub fn with_source(mut self, source: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + self.source = Some(Box::new(source)); + self + } + + /// Create a protocol error (bridge returned zero batches) + pub fn protocol_error(data_type: DataType, from_block: u64, to_block: u64) -> Self { + let msg = format!( + "Bridge returned zero batches for {data_type} {from_block}-{to_block}. This indicates a protocol error." + ); + Self { + data_type, + category: ErrorCategory::ProtocolError, + from_block, + to_block, + message: msg, + source: None, + } + } + + /// Create a validation error (unexpected block range) + pub fn validation_error( + data_type: DataType, + from_block: u64, + to_block: u64, + message: String, + ) -> Self { + Self { + data_type, + category: ErrorCategory::Validation, + from_block, + to_block, + message, + source: None, + } + } + + /// Create a disk I/O error + pub fn disk_io_error( + data_type: DataType, + from_block: u64, + to_block: u64, + message: String, + ) -> Self { + Self { + data_type, + category: ErrorCategory::DiskIo, + from_block, + to_block, + message, + source: None, + } + } + + /// Create a stuck worker error + pub fn stuck_worker( + data_type: DataType, + from_block: u64, + to_block: u64, + message: String, + ) -> Self { + Self { + data_type, + category: ErrorCategory::StuckWorker, + from_block, + to_block, + message, + source: None, + } + } + + /// Wrap an arbitrary error with context + pub fn from_error(data_type: DataType, from_block: u64, to_block: u64, error: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + let message = error.to_string(); + let err_lower = message.to_lowercase(); + + // Categorize based on error message + let category = categorize_error_message(&err_lower); + + Self { + data_type, + category, + from_block, + to_block, + message, + source: Some(Box::new(error)), + } + } + + /// Wrap an arbitrary error with custom context message + pub fn from_error_with_context( + data_type: DataType, + from_block: u64, + to_block: u64, + context: impl Into, + error: E, + ) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + let err_str = error.to_string(); + let err_lower = err_str.to_lowercase(); + + // Categorize based on error message + let category = categorize_error_message(&err_lower); + + let context_str = context.into(); + let message = format!("{context_str}: {err_str}"); + + Self { + data_type, + category, + from_block, + to_block, + message, + source: Some(Box::new(error)), + } + } + + /// Create from a string message (used when the original error is not available) + pub fn from_message( + data_type: DataType, + from_block: u64, + to_block: u64, + message: impl Into, + ) -> Self { + let message = message.into(); + let err_lower = message.to_lowercase(); + let category = categorize_error_message(&err_lower); + + Self { + data_type, + category, + from_block, + to_block, + message, + source: None, + } + } + + /// Wrap an anyhow error with custom context message + pub fn from_anyhow_with_context( + data_type: DataType, + from_block: u64, + to_block: u64, + context: impl Into, + error: anyhow::Error, + ) -> Self { + let err_str = error.to_string(); + let err_lower = err_str.to_lowercase(); + let category = categorize_error_message(&err_lower); + let context_str = context.into(); + let message = format!("{context_str}: {err_str}"); + + Self { + data_type, + category, + from_block, + to_block, + message, + source: None, // anyhow doesn't expose its source easily + } + } + + /// Check if this error is transient and should trigger a retry + pub fn is_transient(&self) -> bool { + self.category.is_transient() || self.message.contains("Timeout expired") + } + + /// Check if this error is permanent and should not be retried + pub fn is_permanent(&self) -> bool { + self.category.is_permanent() + } +} + +impl fmt::Display for SyncError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "[{}/{}] blocks {}-{}: {}", + self.category, self.data_type, self.from_block, self.to_block, self.message + ) + } +} + +impl std::error::Error for SyncError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source + .as_ref() + .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) + } +} + +// Convert from anyhow::Error for backwards compatibility during migration +impl From for SyncError { + fn from(err: anyhow::Error) -> Self { + let message = err.to_string(); + let err_lower = message.to_lowercase(); + + // Detect data type from error message + let data_type = if err_lower.contains("blocks") { + DataType::new("blocks") + } else if err_lower.contains("transactions") { + DataType::new("transactions") + } else if err_lower.contains("logs") { + DataType::new("logs") + } else { + DataType::unknown() + }; + + // Categorize error + let category = categorize_error_message(&err_lower); + + Self { + data_type, + category, + from_block: 0, + to_block: 0, + message, + source: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_categorize_connection_errors() { + assert_eq!( + categorize_error_message("connection refused"), + ErrorCategory::Connection + ); + assert_eq!( + categorize_error_message("failed to connect to server"), + ErrorCategory::Connection + ); + } + + #[test] + fn test_categorize_timeout_errors() { + assert_eq!( + categorize_error_message("request timeout"), + ErrorCategory::Timeout + ); + assert_eq!( + categorize_error_message("operation timed out"), + ErrorCategory::Timeout + ); + } + + #[test] + fn test_categorize_cancelled_errors() { + assert_eq!( + categorize_error_message("operation cancelled"), + ErrorCategory::Cancelled + ); + assert_eq!( + categorize_error_message("request was canceled"), + ErrorCategory::Cancelled + ); + } + + #[test] + fn test_categorize_nodata_errors() { + assert_eq!( + categorize_error_message("header not found"), + ErrorCategory::NoData + ); + assert_eq!( + categorize_error_message("block not found"), + ErrorCategory::NoData + ); + } + + #[test] + fn test_categorize_stuck_worker() { + assert_eq!( + categorize_error_message("worker failed to make progress"), + ErrorCategory::StuckWorker + ); + } + + #[test] + fn test_categorize_protocol_errors() { + assert_eq!( + categorize_error_message("bridge returned zero batches"), + ErrorCategory::ProtocolError + ); + } + + #[test] + fn test_categorize_disk_io_errors() { + assert_eq!( + categorize_error_message("io error: disk full"), + ErrorCategory::DiskIo + ); + assert_eq!( + categorize_error_message("parquet write failed"), + ErrorCategory::DiskIo + ); + assert_eq!( + categorize_error_message("arrow serialization error"), + ErrorCategory::DiskIo + ); + } + + #[test] + fn test_categorize_validation_errors() { + assert_eq!( + categorize_error_message("validation failed"), + ErrorCategory::Validation + ); + assert_eq!( + categorize_error_message("invalid block range"), + ErrorCategory::Validation + ); + } + + #[test] + fn test_transient_check() { + assert!(ErrorCategory::Connection.is_transient()); + assert!(ErrorCategory::Timeout.is_transient()); + assert!(ErrorCategory::Cancelled.is_transient()); + assert!(!ErrorCategory::Validation.is_transient()); + assert!(!ErrorCategory::StuckWorker.is_transient()); + } + + #[test] + fn test_permanent_check() { + assert!(ErrorCategory::Validation.is_permanent()); + assert!(ErrorCategory::StuckWorker.is_permanent()); + assert!(ErrorCategory::NoData.is_permanent()); + assert!(!ErrorCategory::Connection.is_permanent()); + assert!(!ErrorCategory::Timeout.is_permanent()); + } + + #[test] + fn test_sync_error_display() { + let err = SyncError::new( + DataType::new("blocks"), + ErrorCategory::Connection, + 0, + 1000, + "connection refused".to_string(), + ); + let display = format!("{err}"); + assert!(display.contains("connection")); + assert!(display.contains("blocks")); + assert!(display.contains("0-1000")); + } + + #[test] + fn test_data_type_from_str() { + let dt: DataType = "my_custom_table".into(); + assert_eq!(dt.as_str(), "my_custom_table"); + } +} diff --git a/crates/phaser-client/src/sync/mod.rs b/crates/phaser-client/src/sync/mod.rs new file mode 100644 index 0000000..a6f2e99 --- /dev/null +++ b/crates/phaser-client/src/sync/mod.rs @@ -0,0 +1,78 @@ +//! Smart client for synchronizing blockchain data from Phaser bridges +//! +//! This module provides high-level orchestration for syncing data from bridges, +//! including: +//! - Work queue management with parallel workers +//! - Automatic retry with exponential backoff +//! - Error categorization for intelligent retry decisions +//! - Progress tracking and gap detection +//! +//! # Architecture +//! +//! The sync module is designed around the `BatchWriter` trait, which abstracts +//! the storage layer. Consumers implement `BatchWriter` to handle Arrow RecordBatches, +//! while the `PhaserSyncer` handles orchestration, retries, and error recovery. +//! +//! ```text +//! ┌──────────────────────────────────────────────────────────────┐ +//! │ PhaserSyncer │ +//! │ - Work queue management │ +//! │ - Worker pool coordination │ +//! │ - Retry logic with backoff │ +//! │ - Progress tracking │ +//! └──────────────────────────────────────────────────────────────┘ +//! │ +//! │ calls +//! ▼ +//! ┌──────────────────────────────────────────────────────────────┐ +//! │ BatchWriter │ +//! │ - write_batch(RecordBatch) -> Result │ +//! │ - finalize() -> Result<()> │ +//! │ - last_written_block() -> Option │ +//! └──────────────────────────────────────────────────────────────┘ +//! │ +//! │ implemented by +//! ▼ +//! ┌─────────────────────┐ ┌─────────────────────┐ +//! │ ParquetWriter │ │ ArrowFlightSink │ +//! │ (phaser-query) │ │ (AMP) │ +//! └─────────────────────┘ └─────────────────────┘ +//! ``` +//! +//! # Example +//! +//! ```ignore +//! use phaser_client::sync::{PhaserSyncer, SyncConfig, BatchWriter}; +//! +//! // Implement BatchWriter for your storage backend +//! struct MyWriter { /* ... */ } +//! +//! impl BatchWriter for MyWriter { +//! async fn write_batch(&mut self, batch: RecordBatch) -> Result { +//! // Write to your storage +//! Ok(bytes_written) +//! } +//! // ... +//! } +//! +//! // Create syncer with config +//! let config = SyncConfig::default(); +//! let syncer = PhaserSyncer::new(config); +//! +//! // Sync a range with your writer +//! syncer.sync_range("http://bridge:50051", 0, 1_000_000, writer).await?; +//! ``` + +mod config; +mod error; +mod progress; +mod syncer; +mod writer; + +pub use config::{RetryPolicy, SyncConfig}; +pub use error::{ + categorize_error_message, DataType, ErrorCategory, MultipleDataTypeErrors, SyncError, +}; +pub use progress::{Range, SegmentWork, SyncProgress, WorkerProgress}; +pub use syncer::{PhaserSyncer, ProgressReceiver, ProgressSender, ProgressUpdate, SyncResult}; +pub use writer::{BatchWriter, WriterFactory}; diff --git a/crates/phaser-client/src/sync/progress.rs b/crates/phaser-client/src/sync/progress.rs new file mode 100644 index 0000000..b0adb24 --- /dev/null +++ b/crates/phaser-client/src/sync/progress.rs @@ -0,0 +1,425 @@ +//! Progress tracking for sync operations +//! +//! This module provides position-agnostic progress tracking. All types use +//! generic "position" terminology rather than blockchain-specific terms like +//! "block" to support any ordered data source. + +use std::collections::{HashMap, HashSet}; +use std::time::SystemTime; + +/// A contiguous range of positions (e.g., blocks, slots, offsets, sequence numbers) +/// +/// Generic range type for sync operations. The unit depends on context - +/// could be block numbers, slot numbers, ledger offsets, or any ordered position. +/// +/// TODO: Move to phaser-types for reuse across crates +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Range { + pub start: u64, + pub end: u64, +} + +impl Range { + pub fn new(start: u64, end: u64) -> Self { + Self { start, end } + } + + /// Number of positions in this range (inclusive) + pub fn len(&self) -> u64 { + if self.start > self.end { + 0 + } else { + self.end - self.start + 1 + } + } + + /// Check if range is empty + pub fn is_empty(&self) -> bool { + self.start > self.end + } + + /// Check if this range contains a position + pub fn contains(&self, pos: u64) -> bool { + pos >= self.start && pos <= self.end + } + + /// Check if this range overlaps with another + pub fn overlaps(&self, other: &Range) -> bool { + self.start <= other.end && other.start <= self.end + } + + /// Convert to std::ops::Range (exclusive end) + pub fn to_std_range(&self) -> std::ops::Range { + self.start..self.end.saturating_add(1) + } +} + +impl From> for Range { + fn from(r: std::ops::Range) -> Self { + Self { + start: r.start, + end: r.end.saturating_sub(1), + } + } +} + +/// Progress information for a single worker +#[derive(Debug, Clone)] +pub struct WorkerProgress { + /// Worker identifier + pub worker_id: u32, + /// Start of the position range being synced + pub from_position: u64, + /// End of the position range being synced + pub to_position: u64, + /// Current phase of work (e.g., "syncing transactions", "syncing events") + pub current_phase: String, + /// Data types that have been completed (string names for flexibility) + pub completed_types: HashSet, + /// Data types expected to complete (for is_complete check) + pub expected_types: HashSet, + /// When the worker started + pub started_at: SystemTime, + /// Current position being processed + pub current_position: u64, + /// Total positions processed so far + pub positions_processed: u64, + /// Total bytes written so far + pub bytes_written: u64, + /// Number of files created + pub files_created: u32, +} + +impl WorkerProgress { + /// Create a new WorkerProgress for a worker starting a segment + pub fn new(worker_id: u32, from_position: u64, to_position: u64) -> Self { + Self { + worker_id, + from_position, + to_position, + current_phase: "initializing".to_string(), + completed_types: HashSet::new(), + expected_types: HashSet::new(), + started_at: SystemTime::now(), + current_position: from_position, + positions_processed: 0, + bytes_written: 0, + files_created: 0, + } + } + + /// Create with expected data types to track + pub fn with_expected_types( + mut self, + types: impl IntoIterator>, + ) -> Self { + self.expected_types = types.into_iter().map(|t| t.into()).collect(); + self + } + + /// Update the current phase + pub fn set_phase(&mut self, phase: impl Into) { + self.current_phase = phase.into(); + } + + /// Mark a data type as completed (by string name) + pub fn mark_completed(&mut self, data_type: impl AsRef) { + self.completed_types.insert(data_type.as_ref().to_string()); + } + + /// Check if all expected data types are completed + pub fn is_complete(&self) -> bool { + if self.expected_types.is_empty() { + // If no expected types specified, we can't determine completion + false + } else { + self.expected_types + .iter() + .all(|t| self.completed_types.contains(t)) + } + } + + /// Get elapsed time since worker started + pub fn elapsed(&self) -> std::time::Duration { + self.started_at + .elapsed() + .unwrap_or(std::time::Duration::ZERO) + } + + /// Calculate positions per second + pub fn positions_per_second(&self) -> f64 { + let elapsed = self.elapsed().as_secs_f64(); + if elapsed > 0.0 { + self.positions_processed as f64 / elapsed + } else { + 0.0 + } + } +} + +/// Overall sync progress across all workers +#[derive(Debug, Clone, Default)] +pub struct SyncProgress { + /// Total segments to sync + pub total_segments: u64, + /// Segments completed + pub completed_segments: u64, + /// Segments currently being processed + pub in_progress_segments: u64, + /// Total positions to sync + pub total_positions: u64, + /// Positions synced so far + pub positions_synced: u64, + /// Total bytes written + pub bytes_written: u64, + /// Number of retries that have occurred + pub total_retries: u64, + /// Number of permanent errors encountered + pub permanent_errors: u64, +} + +impl SyncProgress { + /// Create a new SyncProgress for a sync range + pub fn new(total_segments: u64, total_positions: u64) -> Self { + Self { + total_segments, + total_positions, + ..Default::default() + } + } + + /// Calculate completion percentage + pub fn completion_percentage(&self) -> f64 { + if self.total_segments == 0 { + return 100.0; + } + (self.completed_segments as f64 / self.total_segments as f64) * 100.0 + } + + /// Check if sync is complete + pub fn is_complete(&self) -> bool { + self.completed_segments >= self.total_segments + } + + /// Mark a segment as completed + pub fn mark_segment_completed(&mut self, positions: u64, bytes: u64) { + self.completed_segments += 1; + self.positions_synced += positions; + self.bytes_written += bytes; + if self.in_progress_segments > 0 { + self.in_progress_segments -= 1; + } + } + + /// Mark a segment as started + pub fn mark_segment_started(&mut self) { + self.in_progress_segments += 1; + } + + /// Record a retry + pub fn record_retry(&mut self) { + self.total_retries += 1; + } + + /// Record a permanent error + pub fn record_permanent_error(&mut self) { + self.permanent_errors += 1; + } +} + +/// Work item for a segment that needs syncing +/// +/// Uses a map of data type name -> missing ranges for flexibility. +/// Callers define their own data type names (e.g., "blocks", "events", "account_updates"). +#[derive(Debug, Clone)] +pub struct SegmentWork { + /// Segment number + pub segment_num: u64, + /// Start of the segment (position) + pub segment_start: u64, + /// End of the segment (position) + pub segment_end: u64, + /// Missing ranges by data type name + pub missing_ranges: HashMap>, + /// Number of times this has been retried + pub retry_count: Option, + /// Last attempt time (for backoff) + pub last_attempt: std::time::Instant, +} + +impl SegmentWork { + /// Create an empty segment work item + pub fn new(segment_num: u64, segment_start: u64, segment_end: u64) -> Self { + Self { + segment_num, + segment_start, + segment_end, + missing_ranges: HashMap::new(), + retry_count: None, + last_attempt: std::time::Instant::now(), + } + } + + /// Create work for a completely missing segment with specified data types + pub fn new_full_segment( + segment_num: u64, + segment_start: u64, + segment_end: u64, + data_types: &[&str], + ) -> Self { + let full_range = Range::new(segment_start, segment_end); + let mut missing_ranges = HashMap::new(); + for dt in data_types { + missing_ranges.insert(dt.to_string(), vec![full_range.clone()]); + } + Self { + segment_num, + segment_start, + segment_end, + missing_ranges, + retry_count: None, + last_attempt: std::time::Instant::now(), + } + } + + /// Add missing ranges for a data type + pub fn add_missing(&mut self, data_type: impl Into, ranges: Vec) { + self.missing_ranges.insert(data_type.into(), ranges); + } + + /// Check if this segment is complete (no missing data for any type) + pub fn is_complete(&self) -> bool { + self.missing_ranges.values().all(|ranges| ranges.is_empty()) + } + + /// Get list of data types that have missing ranges + pub fn missing_types(&self) -> Vec { + self.missing_ranges + .iter() + .filter(|(_, ranges)| !ranges.is_empty()) + .map(|(name, _)| name.clone()) + .collect() + } + + /// Check if a specific data type needs syncing + pub fn needs_sync(&self, data_type: &str) -> bool { + self.missing_ranges + .get(data_type) + .map(|ranges| !ranges.is_empty()) + .unwrap_or(false) + } + + /// Get the missing ranges for a data type + pub fn get_missing_ranges(&self, data_type: &str) -> &[Range] { + self.missing_ranges + .get(data_type) + .map(|v| v.as_slice()) + .unwrap_or(&[]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_range_basics() { + let range = Range::new(0, 999); + assert_eq!(range.len(), 1000); + assert!(range.contains(0)); + assert!(range.contains(999)); + assert!(!range.contains(1000)); + } + + #[test] + fn test_range_empty() { + let range = Range::new(100, 50); // start > end + assert!(range.is_empty()); + assert_eq!(range.len(), 0); + } + + #[test] + fn test_range_overlap() { + let r1 = Range::new(0, 100); + let r2 = Range::new(50, 150); + let r3 = Range::new(200, 300); + + assert!(r1.overlaps(&r2)); + assert!(r2.overlaps(&r1)); + assert!(!r1.overlaps(&r3)); + } + + #[test] + fn test_range_conversion() { + // std::ops::Range to our Range + let std_range = 0u64..1000u64; + let our_range: Range = std_range.into(); + assert_eq!(our_range.start, 0); + assert_eq!(our_range.end, 999); // exclusive -> inclusive + + // Our Range to std::ops::Range + let back = our_range.to_std_range(); + assert_eq!(back, 0..1000); + } + + #[test] + fn test_segment_work_with_custom_types() { + // Test with Ethereum-style types + let work = + SegmentWork::new_full_segment(0, 0, 499_999, &["blocks", "transactions", "logs"]); + assert!(!work.is_complete()); + assert!(work.needs_sync("blocks")); + assert!(work.needs_sync("transactions")); + assert!(work.needs_sync("logs")); + assert!(!work.needs_sync("proofs")); // Not configured + + // Test with Canton-style types + let canton_work = SegmentWork::new_full_segment(0, 0, 999, &["events", "domain_updates"]); + assert!(canton_work.needs_sync("events")); + assert!(canton_work.needs_sync("domain_updates")); + assert!(!canton_work.needs_sync("blocks")); // Different chain, different types + } + + #[test] + fn test_sync_progress() { + let mut progress = SyncProgress::new(10, 5_000_000); + assert_eq!(progress.completion_percentage(), 0.0); + + progress.mark_segment_started(); + progress.mark_segment_completed(500_000, 1_000_000); + assert_eq!(progress.completion_percentage(), 10.0); + assert_eq!(progress.completed_segments, 1); + } + + #[test] + fn test_worker_progress_with_string_types() { + let mut wp = WorkerProgress::new(0, 0, 499_999).with_expected_types([ + "blocks", + "transactions", + "logs", + ]); + assert!(!wp.is_complete()); + + wp.mark_completed("blocks"); + assert!(!wp.is_complete()); + + wp.mark_completed("transactions"); + assert!(!wp.is_complete()); + + wp.mark_completed("logs"); + assert!(wp.is_complete()); + } + + #[test] + fn test_worker_progress_custom_chain() { + // Solana-style with account_updates + let mut wp = + WorkerProgress::new(0, 0, 999).with_expected_types(["slot_data", "account_updates"]); + + wp.mark_completed("slot_data"); + assert!(!wp.is_complete()); + + wp.mark_completed("account_updates"); + assert!(wp.is_complete()); + } +} diff --git a/crates/phaser-client/src/sync/syncer.rs b/crates/phaser-client/src/sync/syncer.rs new file mode 100644 index 0000000..9d4d8d1 --- /dev/null +++ b/crates/phaser-client/src/sync/syncer.rs @@ -0,0 +1,770 @@ +//! PhaserSyncer - High-level orchestration for syncing data from bridges +//! +//! The syncer manages: +//! - Work queue of segments to sync +//! - Parallel worker coordination +//! - Retry logic with backoff +//! - Progress tracking +//! +//! This module is source-agnostic - it works with any data types defined +//! by the caller (e.g., "blocks", "events", "account_updates"). + +use std::collections::VecDeque; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use futures::StreamExt; +use tokio::sync::{mpsc, Mutex, RwLock, Semaphore}; +use tracing::{debug, error, info, warn}; + +use crate::{GenericQuery, PhaserClient}; + +use super::config::SyncConfig; +use super::error::{DataType, MultipleDataTypeErrors, SyncError}; +use super::progress::{Range, SegmentWork, SyncProgress}; +use super::writer::{BatchWriter, WriterFactory}; + +/// Progress update sent to the caller +#[derive(Debug, Clone)] +pub struct ProgressUpdate { + /// Current progress snapshot + pub progress: SyncProgress, + /// Timestamp of this update + pub timestamp: std::time::Instant, +} + +/// Type alias for the progress sender +pub type ProgressSender = mpsc::Sender; + +/// Type alias for the progress receiver +pub type ProgressReceiver = mpsc::Receiver; + +/// Result of a sync operation +#[derive(Debug)] +pub struct SyncResult { + /// Number of segments synced + pub segments_synced: u64, + /// Total positions synced + pub positions_synced: u64, + /// Total bytes written + pub bytes_written: u64, + /// Number of retries that occurred + pub retries: u64, + /// Number of permanent errors (segments that couldn't be synced) + pub permanent_errors: u64, +} + +/// High-level syncer for data from Phaser bridges +/// +/// Coordinates parallel workers to sync data from a Phaser bridge. +/// Source-agnostic - works with any data types defined by the caller. +pub struct PhaserSyncer { + config: SyncConfig, + /// Optional progress sender for streaming updates to the caller + progress_sender: Option, + /// Interval for sending progress updates (default: 1 second) + progress_interval: Duration, +} + +impl PhaserSyncer { + /// Create a new syncer with the given configuration + pub fn new(config: SyncConfig) -> Self { + Self { + config, + progress_sender: None, + progress_interval: Duration::from_secs(1), + } + } + + /// Create a progress channel and return the receiver + /// + /// Call this before `sync_range()` to receive progress updates. + /// The syncer will send `ProgressUpdate` messages at the configured interval. + /// + /// # Example + /// + /// ```ignore + /// let syncer = PhaserSyncer::new(config); + /// let mut progress_rx = syncer.subscribe_progress(100); + /// + /// // Spawn a task to handle progress + /// tokio::spawn(async move { + /// while let Some(update) = progress_rx.recv().await { + /// println!("Progress: {:.1}%", update.progress.completion_percentage()); + /// } + /// }); + /// + /// // Start sync + /// syncer.sync_range(...).await?; + /// ``` + pub fn subscribe_progress(&mut self, buffer_size: usize) -> ProgressReceiver { + let (tx, rx) = mpsc::channel(buffer_size); + self.progress_sender = Some(tx); + rx + } + + /// Set the interval for progress updates (default: 1 second) + pub fn set_progress_interval(&mut self, interval: Duration) { + self.progress_interval = interval; + } + + /// Sync a position range from a bridge endpoint using the provided writer factory + /// + /// This is the main entry point for syncing. It: + /// 1. Takes pre-computed work (which segments need which data types) + /// 2. Spawns parallel workers + /// 3. Uses the WriterFactory to create writers for each data type + /// 4. Manages retries for failed segments + /// 5. Returns when all segments are synced or have permanently failed + /// + /// The `work` parameter defines what needs to be synced. Each `SegmentWork` + /// contains a map of data type names to missing ranges. The syncer doesn't + /// know what "blocks" or "events" mean - it just syncs whatever data types + /// are specified. + pub async fn sync_range( + &self, + bridge_endpoint: &str, + from_position: u64, + to_position: u64, + work: Vec, + writer_factory: Arc, + ) -> Result + where + F: WriterFactory + 'static, + { + let total_segments = work.len() as u64; + + if total_segments == 0 { + info!( + "No segments need syncing in range {}-{}", + from_position, to_position + ); + return Ok(SyncResult { + segments_synced: 0, + positions_synced: 0, + bytes_written: 0, + retries: 0, + permanent_errors: 0, + }); + } + + info!( + "Starting sync of {} segments from {} ({} workers, positions {}-{})", + total_segments, bridge_endpoint, self.config.max_workers, from_position, to_position + ); + + // Create work queue + let segment_queue = Arc::new(Mutex::new(VecDeque::from(work))); + let job_complete = Arc::new(AtomicBool::new(false)); + + // Create semaphore for limiting concurrent "heavy" data type syncs + let heavy_semaphore = Arc::new(Semaphore::new( + self.config.max_concurrent_log_segments as usize, + )); + + // Create shared progress tracker + let progress = Arc::new(RwLock::new(SyncProgress::new( + total_segments, + to_position - from_position + 1, + ))); + + // Spawn workers + let num_workers = std::cmp::min(self.config.max_workers as u64, total_segments) as u32; + let mut worker_handles = Vec::with_capacity(num_workers as usize); + + for worker_id in 0..num_workers { + let bridge_endpoint = bridge_endpoint.to_string(); + let segment_queue = segment_queue.clone(); + let job_complete = job_complete.clone(); + let heavy_semaphore = heavy_semaphore.clone(); + let progress = progress.clone(); + let config = self.config.clone(); + let writer_factory = writer_factory.clone(); + + let handle = tokio::spawn(async move { + Self::worker_loop( + worker_id, + &bridge_endpoint, + segment_queue, + job_complete, + heavy_semaphore, + progress, + &config, + writer_factory, + ) + .await + }); + + worker_handles.push(handle); + } + + // Spawn progress reporter if a subscriber exists + let progress_handle = if let Some(sender) = &self.progress_sender { + let sender = sender.clone(); + let progress = progress.clone(); + let job_complete = job_complete.clone(); + let interval = self.progress_interval; + + Some(tokio::spawn(async move { + loop { + // Check if job is complete + if job_complete.load(Ordering::Relaxed) { + // Send final update + let snapshot = progress.read().await.clone(); + let _ = sender + .send(ProgressUpdate { + progress: snapshot, + timestamp: std::time::Instant::now(), + }) + .await; + break; + } + + // Send progress update + let snapshot = progress.read().await.clone(); + if sender + .send(ProgressUpdate { + progress: snapshot, + timestamp: std::time::Instant::now(), + }) + .await + .is_err() + { + // Receiver dropped, stop reporting + break; + } + + tokio::time::sleep(interval).await; + } + })) + } else { + None + }; + + // Wait for all workers to complete + for (idx, handle) in worker_handles.into_iter().enumerate() { + match handle.await { + Ok(Ok(())) => { + debug!("Worker {} finished successfully", idx); + } + Ok(Err(e)) => { + error!("Worker {} failed: {}", idx, e); + } + Err(e) => { + error!("Worker {} panicked: {}", idx, e); + } + } + } + + // Mark job complete + job_complete.store(true, Ordering::Relaxed); + + // Wait for progress reporter to finish + if let Some(handle) = progress_handle { + let _ = handle.await; + } + + // Collect final progress + let final_progress = progress.read().await; + Ok(SyncResult { + segments_synced: final_progress.completed_segments, + positions_synced: final_progress.positions_synced, + bytes_written: final_progress.bytes_written, + retries: final_progress.total_retries, + permanent_errors: final_progress.permanent_errors, + }) + } + + /// Worker loop - pulls segments from queue and syncs them + #[allow(clippy::too_many_arguments)] + async fn worker_loop( + worker_id: u32, + bridge_endpoint: &str, + segment_queue: Arc>>, + job_complete: Arc, + heavy_semaphore: Arc, + progress: Arc>, + config: &SyncConfig, + writer_factory: Arc, + ) -> Result<(), SyncError> + where + F: WriterFactory, + F::Writer: 'static, + { + loop { + // Check if job is complete + if job_complete.load(Ordering::Relaxed) { + info!(worker_id, "Worker exiting - job complete"); + break; + } + + // Get next segment + let work = { + let mut queue = segment_queue.lock().await; + queue.pop_front() + }; + + let work = match work { + Some(w) => w, + None => { + // No work available, check if we should wait or exit + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + continue; + } + }; + + let segment_num = work.segment_num; + let retry_count = work.retry_count.unwrap_or(0); + + info!( + worker_id, + segment_num, + from_position = work.segment_start, + to_position = work.segment_end, + retry_count, + missing_types = ?work.missing_types(), + "Starting segment processing" + ); + + // Mark segment as started + { + let mut p = progress.write().await; + p.mark_segment_started(); + } + + // Process the segment + match Self::sync_segment( + worker_id, + bridge_endpoint, + &work, + heavy_semaphore.clone(), + config, + &*writer_factory, + ) + .await + { + Ok((positions, bytes)) => { + info!( + worker_id, + segment_num, positions, bytes, "Segment completed successfully" + ); + let mut p = progress.write().await; + p.mark_segment_completed(positions, bytes); + } + Err(sync_err) => { + // Check if error is retryable + if sync_err.is_permanent() { + error!( + worker_id, + segment_num, + error_type = sync_err.category.as_str(), + data_type = sync_err.data_type.as_str(), + error = %sync_err, + "Segment failed with permanent error" + ); + let mut p = progress.write().await; + p.record_permanent_error(); + continue; + } + + // Retryable error - check if we've exceeded max retries without progress + if retry_count >= config.retry_policy.max_retries_without_progress { + error!( + worker_id, + segment_num, + retry_count, + error = %sync_err, + "Segment exceeded max retries without progress" + ); + let mut p = progress.write().await; + p.record_permanent_error(); + continue; + } + + // Calculate backoff + let backoff = config.retry_policy.backoff_for_retry(retry_count + 1); + + warn!( + worker_id, + segment_num, + retry_count, + backoff_secs = backoff.as_secs(), + error = %sync_err, + "Segment failed, will retry" + ); + + // Record retry + { + let mut p = progress.write().await; + p.record_retry(); + } + + // Sleep for backoff + tokio::time::sleep(backoff).await; + + // Re-queue with incremented retry count + let mut retry_work = work; + retry_work.retry_count = Some(retry_count + 1); + retry_work.last_attempt = std::time::Instant::now(); + + let mut queue = segment_queue.lock().await; + queue.push_back(retry_work); + } + } + } + + Ok(()) + } + + /// Sync a single segment (all data types in parallel) + async fn sync_segment( + worker_id: u32, + bridge_endpoint: &str, + work: &SegmentWork, + _heavy_semaphore: Arc, + config: &SyncConfig, + writer_factory: &F, + ) -> Result<(u64, u64), SyncError> + where + F: WriterFactory, + F::Writer: 'static, + { + // Get all data types that need syncing + let data_types: Vec = work.missing_types(); + + if data_types.is_empty() { + return Ok((0, 0)); + } + + // Spawn a task for each data type + let mut handles = Vec::new(); + + for data_type in data_types { + let ranges = work.get_missing_ranges(&data_type).to_vec(); + let segment_start = work.segment_start; + let segment_end = work.segment_end; + let config = config.clone(); + let bridge_endpoint = bridge_endpoint.to_string(); + let dt_clone = data_type.clone(); + + // Create writer for this data type + let writer = match writer_factory.create_writer( + DataType::new(&data_type), + segment_start, + segment_end, + ranges.first().map(|r| r.start).unwrap_or(segment_start), + ranges.last().map(|r| r.end).unwrap_or(segment_end), + ) { + Ok(w) => w, + Err(e) => { + error!( + worker_id, + data_type = &data_type, + segment_num = work.segment_num, + error = %e, + "Failed to create writer" + ); + return Err(e); + } + }; + + let handle = tokio::spawn(async move { + Self::sync_data_type_ranges( + worker_id, + &bridge_endpoint, + &dt_clone, + &ranges, + segment_start, + segment_end, + &config, + writer, + ) + .await + .map(|result| (dt_clone, result)) + }); + + handles.push((data_type, handle)); + } + + // Collect results + let mut errors = Vec::new(); + let mut total_positions = 0u64; + let mut total_bytes = 0u64; + + for (data_type, handle) in handles { + match handle.await { + Ok(Ok((_, (positions, bytes)))) => { + total_positions += positions; + total_bytes += bytes; + } + Ok(Err(e)) => { + errors.push((DataType::new(&data_type), e)); + } + Err(join_err) => { + errors.push(( + DataType::new(&data_type), + SyncError::from_message( + DataType::new(&data_type), + work.segment_start, + work.segment_end, + format!("Task panicked: {join_err}"), + ), + )); + } + } + } + + if !errors.is_empty() { + return Err(MultipleDataTypeErrors { + from_block: work.segment_start, + to_block: work.segment_end, + errors, + } + .into()); + } + + Ok((total_positions, total_bytes)) + } + + /// Sync ranges for a specific data type using the provided writer + #[allow(clippy::too_many_arguments)] + async fn sync_data_type_ranges( + worker_id: u32, + bridge_endpoint: &str, + data_type: &str, + ranges: &[Range], + segment_start: u64, + segment_end: u64, + config: &SyncConfig, + mut writer: W, + ) -> Result<(u64, u64), SyncError> + where + W: BatchWriter, + { + if ranges.is_empty() { + return Ok((0, 0)); + } + + let dt = DataType::new(data_type); + + // Connect to bridge + let mut client = PhaserClient::connect(bridge_endpoint.to_string()) + .await + .map_err(|e| { + SyncError::from_message( + dt.clone(), + segment_start, + segment_end, + format!("Failed to connect to bridge: {e}"), + ) + })?; + + let mut total_positions = 0u64; + let mut total_bytes = 0u64; + + for range in ranges { + debug!( + worker_id, + data_type, + from = range.start, + to = range.end, + "Syncing range" + ); + + let (positions, bytes) = Self::sync_single_range( + worker_id, + &mut client, + data_type, + range.start, + range.end, + config, + &mut writer, + ) + .await?; + + total_positions += positions; + total_bytes += bytes; + } + + // Finalize the writer + writer.finalize().map_err(|e| { + SyncError::from_message( + dt.clone(), + segment_start, + segment_end, + format!("Failed to finalize writer: {e}"), + ) + })?; + + Ok((total_positions, total_bytes)) + } + + /// Sync a single range with retry/resume logic + async fn sync_single_range( + worker_id: u32, + client: &mut PhaserClient, + data_type: &str, + from_position: u64, + to_position: u64, + config: &SyncConfig, + writer: &mut W, + ) -> Result<(u64, u64), SyncError> + where + W: BatchWriter, + { + let dt = DataType::new(data_type); + let mut resume_from = from_position; + let mut last_resume_from = resume_from; + let mut retries_without_progress = 0u32; + let mut retry_count = 0u32; + let mut total_bytes = 0u64; + + loop { + match Self::try_sync_stream(client, data_type, resume_from, to_position, config, writer) + .await + { + Ok(bytes) => { + total_bytes += bytes; + let positions = to_position - from_position + 1; + return Ok((positions, total_bytes)); + } + Err(e) if e.is_transient() => { + // Get last written position from writer for resume + let new_resume_from = writer + .last_written_block() + .map(|b| b + 1) + .unwrap_or(resume_from); + + // Detect stuck loop + if new_resume_from == last_resume_from { + retries_without_progress += 1; + if retries_without_progress + >= config.retry_policy.max_retries_without_progress + { + return Err(SyncError::stuck_worker( + dt.clone(), + resume_from, + to_position, + format!( + "Worker {} failed to make progress on {} {}-{} after {} retries. Last error: {}", + worker_id, data_type, resume_from, to_position, + config.retry_policy.max_retries_without_progress, e + ), + )); + } + } else { + retries_without_progress = 0; + } + + last_resume_from = resume_from; + resume_from = new_resume_from; + + if resume_from > to_position { + // Completed before error + break; + } + + retry_count += 1; + let backoff = config.retry_policy.backoff_for_retry(retry_count); + + warn!( + worker_id, + data_type, + from = resume_from, + to = to_position, + retry_count, + backoff_secs = backoff.as_secs(), + error = %e, + "Stream failed, will retry" + ); + + tokio::time::sleep(backoff).await; + continue; + } + Err(e) => return Err(e), + } + } + + let positions = to_position - from_position + 1; + Ok((positions, total_bytes)) + } + + /// Try to sync a stream (single attempt), writing batches through the writer + async fn try_sync_stream( + client: &mut PhaserClient, + data_type: &str, + from_position: u64, + to_position: u64, + config: &SyncConfig, + writer: &mut W, + ) -> Result + where + W: BatchWriter, + { + let dt = DataType::new(data_type); + + // Build query using the data type as the table name + let mut query = GenericQuery::historical(data_type, from_position, to_position); + + // Add trace filter for logs if enabled (EVM-specific, but harmless for others) + if data_type == "logs" && config.enable_traces { + query = query.with_filter("enable_traces", serde_json::json!(true)); + } + + // Get stream with metadata + let stream = client.query_with_metadata(query).await.map_err(|e| { + SyncError::from_message( + dt.clone(), + from_position, + to_position, + format!("Failed to start stream: {e}"), + ) + })?; + let mut stream = Box::pin(stream); + + let mut batches_processed = 0u64; + let mut total_bytes = 0u64; + + while let Some(batch_result) = stream.next().await { + let (batch, metadata) = match batch_result { + Ok(data) => data, + Err(e) => { + return Err(SyncError::from_message( + dt.clone(), + from_position, + to_position, + format!("Stream error: {e}"), + )); + } + }; + + // Update writer with responsibility range from metadata + writer.update_responsibility_end(metadata.responsibility_range.end_block); + + // Write the batch through the writer + let bytes = writer.write_batch(batch).await.map_err(|e| { + SyncError::from_message( + dt.clone(), + from_position, + to_position, + format!("Write error: {e}"), + ) + })?; + + total_bytes += bytes; + batches_processed += 1; + } + + debug!( + data_type, + from = from_position, + to = to_position, + batches = batches_processed, + bytes = total_bytes, + "Stream completed" + ); + + Ok(total_bytes) + } +} diff --git a/crates/phaser-client/src/sync/writer.rs b/crates/phaser-client/src/sync/writer.rs new file mode 100644 index 0000000..a559990 --- /dev/null +++ b/crates/phaser-client/src/sync/writer.rs @@ -0,0 +1,130 @@ +//! BatchWriter trait for abstracting storage backends +//! +//! The BatchWriter trait is the primary abstraction point between the sync +//! orchestration (PhaserSyncer) and the storage layer. Consumers implement +//! this trait to handle Arrow RecordBatches however they need. + +use arrow_array::RecordBatch; + +use super::error::{DataType, SyncError}; + +/// Trait for writing batches of blockchain data to a storage backend +/// +/// Implementors receive Arrow RecordBatches and are responsible for: +/// - Writing data to their storage backend (Parquet files, Arrow Flight, etc.) +/// - Tracking progress (last written block) +/// - Handling rotation/finalization when segments complete +/// +/// # Example Implementation +/// +/// ```ignore +/// use phaser_client::sync::{BatchWriter, SyncError, DataType}; +/// use arrow_array::RecordBatch; +/// +/// struct ParquetWriter { +/// // ... +/// } +/// +/// #[async_trait::async_trait] +/// impl BatchWriter for ParquetWriter { +/// fn data_type(&self) -> DataType { +/// DataType::Blocks +/// } +/// +/// async fn write_batch(&mut self, batch: RecordBatch) -> Result { +/// // Write to parquet file +/// let bytes = self.inner_write(batch)?; +/// Ok(bytes) +/// } +/// +/// fn last_written_block(&self) -> Option { +/// self.last_block +/// } +/// +/// fn update_responsibility_end(&mut self, block: u64) { +/// self.responsibility_end = self.responsibility_end.max(block); +/// } +/// +/// fn finalize(&mut self) -> Result<(), SyncError> { +/// // Flush and close the file +/// self.flush()?; +/// Ok(()) +/// } +/// } +/// ``` +#[async_trait::async_trait] +pub trait BatchWriter: Send { + /// The type of data this writer handles (blocks, transactions, logs) + fn data_type(&self) -> DataType; + + /// Write a batch of records + /// + /// Returns the number of bytes written (for progress tracking). + /// The batch contains Arrow data from the bridge. + async fn write_batch(&mut self, batch: RecordBatch) -> Result; + + /// Get the last block number that was successfully written + /// + /// Used for resume logic - if a stream fails, we can resume from + /// the next block after the last successfully written one. + fn last_written_block(&self) -> Option; + + /// Update the responsibility end block from batch metadata + /// + /// The bridge sends metadata indicating the range it processed. + /// This is important for empty batches - we need to know the range + /// was covered even if no rows were returned. + fn update_responsibility_end(&mut self, block: u64); + + /// Finalize the current output (flush, close file, etc.) + /// + /// Called when a segment is complete. Implementors should ensure + /// all data is persisted and the file is properly closed. + fn finalize(&mut self) -> Result<(), SyncError>; + + /// Reset the writer for a new range + /// + /// Called when starting a new segment or resuming from a different point. + /// Implementors should prepare for a fresh write. + fn reset(&mut self) -> Result<(), SyncError> { + Ok(()) // Default no-op + } + + /// Set the block range this writer is responsible for + /// + /// segment_start/segment_end: The logical segment boundaries (for filenames) + /// responsibility_start/responsibility_end: The actual blocks we're syncing + fn set_ranges( + &mut self, + segment_start: u64, + segment_end: u64, + responsibility_start: u64, + responsibility_end: u64, + ); +} + +/// Factory for creating writers for different data types +/// +/// Implementors provide this to create appropriately configured writers +/// for each data type (blocks, transactions, logs). +pub trait WriterFactory: Send + Sync { + /// The writer type produced by this factory + type Writer: BatchWriter; + + /// Create a writer for the given data type and segment + /// + /// Parameters: + /// - data_type: The type of data to write + /// - segment_start: Start of the segment (for filename) + /// - segment_end: End of the segment (for filename) + /// - responsibility_start: First block we're responsible for + /// - responsibility_end: Last block we're responsible for + fn create_writer( + &self, + data_type: DataType, + segment_start: u64, + segment_end: u64, + responsibility_start: u64, + responsibility_end: u64, + ) -> Result; +} diff --git a/crates/phaser-query/Cargo.toml b/crates/phaser-query/Cargo.toml index 0b9aa82..c3dbf06 100644 --- a/crates/phaser-query/Cargo.toml +++ b/crates/phaser-query/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "phaser-query" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true [[bin]] name = "phaser-query" diff --git a/crates/phaser-query/proto/admin/sync.proto b/crates/phaser-query/proto/admin/sync.proto index 55f8330..f772c68 100644 --- a/crates/phaser-query/proto/admin/sync.proto +++ b/crates/phaser-query/proto/admin/sync.proto @@ -74,29 +74,44 @@ message GapAnalysis { repeated IncompleteSegment incomplete_details = 7; } -message BlockRange { - // Start block (inclusive) +// A contiguous range of positions (blocks, slots, offsets, etc.) +// Generic - works for any ordered data source +message Range { + // Start position (inclusive) uint64 start = 1; - // End block (inclusive) + // End position (inclusive) uint64 end = 2; } +// Missing ranges for a specific data type +message DataTypeRanges { + // Data type name (e.g., "blocks", "transactions", "logs", "events") + string data_type = 1; + + // Missing ranges for this data type + repeated Range ranges = 2; +} + message IncompleteSegment { // Segment number uint64 segment_num = 1; - // Block range for this segment - uint64 from_block = 2; - uint64 to_block = 3; + // Position range for this segment + uint64 from_position = 2; + uint64 to_position = 3; // Missing data types (e.g., "blocks", "txs", "logs") repeated string missing_data_types = 4; - // Detailed missing ranges for each data type - repeated BlockRange missing_blocks_ranges = 5; - repeated BlockRange missing_transactions_ranges = 6; - repeated BlockRange missing_logs_ranges = 7; + // Generic missing ranges by data type (replaces separate fields) + repeated DataTypeRanges missing_ranges = 8; + + // DEPRECATED: Use missing_ranges instead + // Kept for backwards compatibility during transition + repeated Range missing_blocks_ranges = 5 [deprecated = true]; + repeated Range missing_transactions_ranges = 6 [deprecated = true]; + repeated Range missing_logs_ranges = 7 [deprecated = true]; } message SyncStatusRequest { @@ -233,23 +248,22 @@ message AnalyzeGapsResponse { // Detailed progress tracking for each data type message DataProgress { - // Blocks progress - DataTypeProgress blocks = 1; - - // Transactions progress - DataTypeProgress transactions = 2; - - // Logs progress - DataTypeProgress logs = 3; + // Generic progress by data type name (replaces separate fields) + map by_type = 5; // File statistics FileStatistics file_stats = 4; + + // DEPRECATED: Use by_type["blocks"] instead + DataTypeProgress blocks = 1 [deprecated = true]; + DataTypeProgress transactions = 2 [deprecated = true]; + DataTypeProgress logs = 3 [deprecated = true]; } -// Progress for a specific data type (blocks, transactions, or logs) +// Progress for a specific data type message DataTypeProgress { - // Number of blocks covered by files on disk (may have gaps) - uint64 blocks_on_disk = 1; + // Number of positions covered by files on disk (may have gaps) + uint64 positions_on_disk = 1; // Number of gaps (missing ranges) uint32 gap_count = 2; @@ -257,29 +271,36 @@ message DataTypeProgress { // Percentage of target range covered (0-100) double coverage_percentage = 3; - // Highest continuous block number (no gaps before this) + // Highest continuous position (no gaps before this) uint64 highest_continuous = 4; } +// File statistics for a specific data type +message DataTypeFileStats { + uint32 file_count = 1; + uint64 disk_bytes = 2; +} + // File and disk statistics message FileStatistics { // Total number of parquet files uint32 total_files = 1; - // Files by type - uint32 blocks_files = 2; - uint32 transactions_files = 3; - uint32 logs_files = 4; - uint32 proofs_files = 5; - // Total disk usage in bytes uint64 total_disk_bytes = 6; - // Disk usage by type - uint64 blocks_disk_bytes = 7; - uint64 transactions_disk_bytes = 8; - uint64 logs_disk_bytes = 9; - uint64 proofs_disk_bytes = 10; + // Generic file stats by data type name + map by_type = 11; + + // DEPRECATED: Use by_type instead + uint32 blocks_files = 2 [deprecated = true]; + uint32 transactions_files = 3 [deprecated = true]; + uint32 logs_files = 4 [deprecated = true]; + uint32 proofs_files = 5 [deprecated = true]; + uint64 blocks_disk_bytes = 7 [deprecated = true]; + uint64 transactions_disk_bytes = 8 [deprecated = true]; + uint64 logs_disk_bytes = 9 [deprecated = true]; + uint64 proofs_disk_bytes = 10 [deprecated = true]; } enum SyncStatus { diff --git a/crates/phaser-query/src/bin/phaser-cli.rs b/crates/phaser-query/src/bin/phaser-cli.rs index 21dc5c9..5b7039c 100644 --- a/crates/phaser-query/src/bin/phaser-cli.rs +++ b/crates/phaser-query/src/bin/phaser-cli.rs @@ -117,8 +117,8 @@ async fn main() -> Result<()> { println!( " Segment {} (blocks {}-{}): missing {}", detail.segment_num, - detail.from_block, - detail.to_block, + detail.from_position, + detail.to_position, detail.missing_data_types.join(", ") ); } @@ -131,8 +131,8 @@ async fn main() -> Result<()> { println!( " Segment {} (blocks {}-{}): missing {}", detail.segment_num, - detail.from_block, - detail.to_block, + detail.from_position, + detail.to_position, detail.missing_data_types.join(", ") ); } @@ -189,96 +189,52 @@ async fn main() -> Result<()> { }; if let Some(ref gap) = job.gap_analysis { - println!("\nData Progress (by segment):"); let total_segments = gap.total_segments; - let mut blocks_incomplete = 0; - let mut txs_incomplete = 0; - let mut logs_incomplete = 0; + let mut incomplete_by_type: std::collections::HashMap = + std::collections::HashMap::new(); for detail in &gap.incomplete_details { - if !detail.missing_blocks_ranges.is_empty() { - blocks_incomplete += 1; - } - if !detail.missing_transactions_ranges.is_empty() { - txs_incomplete += 1; - } - if !detail.missing_logs_ranges.is_empty() { - logs_incomplete += 1; - } - } - - if let Some(ref blocks) = progress.blocks { - let blocks_complete = total_segments - blocks_incomplete; - println!( - " Blocks: {}/{} segments - {} files, {}{}", - blocks_complete, - total_segments, - progress - .file_stats - .as_ref() - .map(|s| s.blocks_files) - .unwrap_or(0), - format_size( - progress - .file_stats - .as_ref() - .map(|s| s.blocks_disk_bytes) - .unwrap_or(0) - ), - if blocks.gap_count > 0 { - format!(" ({} gaps)", blocks.gap_count) - } else { - String::new() + // Count incomplete segments by data type + for dt_ranges in &detail.missing_ranges { + if !dt_ranges.ranges.is_empty() { + *incomplete_by_type + .entry(dt_ranges.data_type.clone()) + .or_insert(0) += 1; } - ); + } } - if let Some(ref txs) = progress.transactions { - let txs_complete = total_segments - txs_incomplete; - println!( - " Transactions: {}/{} segments - {} files, {}{}", - txs_complete, - total_segments, - progress - .file_stats - .as_ref() - .map(|s| s.transactions_files) - .unwrap_or(0), - format_size( - progress - .file_stats - .as_ref() - .map(|s| s.transactions_disk_bytes) - .unwrap_or(0) - ), - if txs.gap_count > 0 { - format!(" ({} gaps)", txs.gap_count) - } else { - String::new() + // Display progress for each data type using generic by_type + println!("\nData Progress (by segment):"); + for (data_type, type_progress) in &progress.by_type { + let incomplete = + incomplete_by_type.get(data_type).copied().unwrap_or(0); + let complete = total_segments - incomplete; + let file_stats = progress + .file_stats + .as_ref() + .and_then(|s| s.by_type.get(data_type)); + let file_count = file_stats.map(|s| s.file_count).unwrap_or(0); + let disk_bytes = file_stats.map(|s| s.disk_bytes).unwrap_or(0); + + // Capitalize first letter for display + let display_name = { + let mut chars = data_type.chars(); + match chars.next() { + None => String::new(), + Some(c) => c.to_uppercase().chain(chars).collect(), } - ); - } + }; - if let Some(ref logs) = progress.logs { - let logs_complete = total_segments - logs_incomplete; println!( - " Logs: {}/{} segments - {} files, {}{}", - logs_complete, + " {:14}{}/{} segments - {} files, {}{}", + format!("{}:", display_name), + complete, total_segments, - progress - .file_stats - .as_ref() - .map(|s| s.logs_files) - .unwrap_or(0), - format_size( - progress - .file_stats - .as_ref() - .map(|s| s.logs_disk_bytes) - .unwrap_or(0) - ), - if logs.gap_count > 0 { - format!(" ({} gaps)", logs.gap_count) + file_count, + format_size(disk_bytes), + if type_progress.gap_count > 0 { + format!(" ({} gaps)", type_progress.gap_count) } else { String::new() } @@ -304,31 +260,22 @@ async fn main() -> Result<()> { ); if gap.missing_segments > 0 { - let mut missing_blocks_count = 0; - let mut missing_txs_count = 0; - let mut missing_logs_count = 0; + let mut missing_by_type: std::collections::HashMap = + std::collections::HashMap::new(); for detail in &gap.incomplete_details { - if !detail.missing_blocks_ranges.is_empty() { - missing_blocks_count += 1; - } - if !detail.missing_transactions_ranges.is_empty() { - missing_txs_count += 1; - } - if !detail.missing_logs_ranges.is_empty() { - missing_logs_count += 1; + for dt_ranges in &detail.missing_ranges { + if !dt_ranges.ranges.is_empty() { + *missing_by_type + .entry(dt_ranges.data_type.clone()) + .or_insert(0) += 1; + } } } println!("Incomplete Segments: {}", gap.missing_segments); - if missing_blocks_count > 0 { - println!(" - {missing_blocks_count} segments missing blocks"); - } - if missing_txs_count > 0 { - println!(" - {missing_txs_count} segments missing transactions"); - } - if missing_logs_count > 0 { - println!(" - {missing_logs_count} segments missing logs"); + for (data_type, count) in &missing_by_type { + println!(" - {count} segments missing {data_type}"); } } @@ -421,97 +368,49 @@ async fn main() -> Result<()> { println!("\nData Progress (by segment):"); let total_segments = gap.total_segments; - let mut blocks_incomplete = 0; - let mut txs_incomplete = 0; - let mut logs_incomplete = 0; + let mut incomplete_by_type: std::collections::HashMap = + std::collections::HashMap::new(); for detail in &gap.incomplete_details { - if !detail.missing_blocks_ranges.is_empty() { - blocks_incomplete += 1; - } - if !detail.missing_transactions_ranges.is_empty() { - txs_incomplete += 1; - } - if !detail.missing_logs_ranges.is_empty() { - logs_incomplete += 1; - } - } - - // Blocks - if let Some(ref blocks) = progress.blocks { - let blocks_complete = total_segments - blocks_incomplete; - println!( - " Blocks: {}/{} segments - {} files, {}{}", - blocks_complete, - total_segments, - progress - .file_stats - .as_ref() - .map(|s| s.blocks_files) - .unwrap_or(0), - format_size( - progress - .file_stats - .as_ref() - .map(|s| s.blocks_disk_bytes) - .unwrap_or(0) - ), - if blocks.gap_count > 0 { - format!(" ({} gaps)", blocks.gap_count) - } else { - String::new() + for dt_ranges in &detail.missing_ranges { + if !dt_ranges.ranges.is_empty() { + *incomplete_by_type + .entry(dt_ranges.data_type.clone()) + .or_insert(0) += 1; } - ); + } } - // Transactions - if let Some(ref txs) = progress.transactions { - let txs_complete = total_segments - txs_incomplete; - println!( - " Transactions: {}/{} segments - {} files, {}{}", - txs_complete, - total_segments, - progress - .file_stats - .as_ref() - .map(|s| s.transactions_files) - .unwrap_or(0), - format_size( - progress - .file_stats - .as_ref() - .map(|s| s.transactions_disk_bytes) - .unwrap_or(0) - ), - if txs.gap_count > 0 { - format!(" ({} gaps)", txs.gap_count) - } else { - String::new() + // Display progress for each data type using generic by_type + for (data_type, type_progress) in &progress.by_type { + let incomplete = + incomplete_by_type.get(data_type).copied().unwrap_or(0); + let complete = total_segments - incomplete; + let file_stats = progress + .file_stats + .as_ref() + .and_then(|s| s.by_type.get(data_type)); + let file_count = file_stats.map(|s| s.file_count).unwrap_or(0); + let disk_bytes = file_stats.map(|s| s.disk_bytes).unwrap_or(0); + + // Capitalize first letter for display + let display_name = { + let mut chars = data_type.chars(); + match chars.next() { + None => String::new(), + Some(c) => c.to_uppercase().chain(chars).collect(), } - ); - } + }; - // Logs - if let Some(ref logs) = progress.logs { - let logs_complete = total_segments - logs_incomplete; println!( - " Logs: {}/{} segments - {} files, {}{}", - logs_complete, + " {:14}{}/{} segments - {} files, {}{}", + format!("{}:", display_name), + complete, total_segments, - progress - .file_stats - .as_ref() - .map(|s| s.logs_files) - .unwrap_or(0), - format_size( - progress - .file_stats - .as_ref() - .map(|s| s.logs_disk_bytes) - .unwrap_or(0) - ), - if logs.gap_count > 0 { - format!(" ({} gaps)", logs.gap_count) + file_count, + format_size(disk_bytes), + if type_progress.gap_count > 0 { + format!(" ({} gaps)", type_progress.gap_count) } else { String::new() } @@ -541,33 +440,22 @@ async fn main() -> Result<()> { // Show breakdown of incomplete segments by data type if gap.missing_segments > 0 { - let mut missing_blocks_count = 0; - let mut missing_txs_count = 0; - let mut missing_logs_count = 0; + let mut missing_by_type: std::collections::HashMap = + std::collections::HashMap::new(); for detail in &gap.incomplete_details { - if !detail.missing_blocks_ranges.is_empty() { - missing_blocks_count += 1; - } - if !detail.missing_transactions_ranges.is_empty() { - missing_txs_count += 1; - } - if !detail.missing_logs_ranges.is_empty() { - missing_logs_count += 1; + for dt_ranges in &detail.missing_ranges { + if !dt_ranges.ranges.is_empty() { + *missing_by_type + .entry(dt_ranges.data_type.clone()) + .or_insert(0) += 1; + } } } println!("Incomplete Segments: {}", gap.missing_segments); - if missing_blocks_count > 0 { - println!(" - {missing_blocks_count} segments missing blocks"); - } - if missing_txs_count > 0 { - println!( - " - {missing_txs_count} segments missing transactions" - ); - } - if missing_logs_count > 0 { - println!(" - {missing_logs_count} segments missing logs"); + for (data_type, count) in &missing_by_type { + println!(" - {count} segments missing {data_type}"); } } @@ -645,40 +533,20 @@ async fn main() -> Result<()> { for detail in &gap.incomplete_details { println!( " Segment {} (blocks {}-{}):", - detail.segment_num, detail.from_block, detail.to_block + detail.segment_num, detail.from_position, detail.to_position ); - // Show detailed ranges for each data type - if !detail.missing_blocks_ranges.is_empty() { - println!(" - blocks:"); - for range in &detail.missing_blocks_ranges { - let count = range.end - range.start + 1; - println!( - " {}-{} ({} blocks)", - range.start, range.end, count - ); - } - } - - if !detail.missing_transactions_ranges.is_empty() { - println!(" - transactions:"); - for range in &detail.missing_transactions_ranges { - let count = range.end - range.start + 1; - println!( - " {}-{} ({} blocks)", - range.start, range.end, count - ); - } - } - - if !detail.missing_logs_ranges.is_empty() { - println!(" - logs:"); - for range in &detail.missing_logs_ranges { - let count = range.end - range.start + 1; - println!( - " {}-{} ({} blocks)", - range.start, range.end, count - ); + // Show detailed ranges for each data type (using generic missing_ranges) + for data_type_ranges in &detail.missing_ranges { + if !data_type_ranges.ranges.is_empty() { + println!(" - {}:", data_type_ranges.data_type); + for range in &data_type_ranges.ranges { + let count = range.end - range.start + 1; + println!( + " {}-{} ({} positions)", + range.start, range.end, count + ); + } } } } diff --git a/crates/phaser-query/src/generated/phaser.admin.rs b/crates/phaser-query/src/generated/phaser.admin.rs index c455c68..3740bd1 100644 --- a/crates/phaser-query/src/generated/phaser.admin.rs +++ b/crates/phaser-query/src/generated/phaser.admin.rs @@ -53,35 +53,54 @@ pub struct GapAnalysis { #[prost(message, repeated, tag = "7")] pub incomplete_details: ::prost::alloc::vec::Vec, } +/// A contiguous range of positions (blocks, slots, offsets, etc.) +/// Generic - works for any ordered data source #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] -pub struct BlockRange { - /// Start block (inclusive) +pub struct Range { + /// Start position (inclusive) #[prost(uint64, tag = "1")] pub start: u64, - /// End block (inclusive) + /// End position (inclusive) #[prost(uint64, tag = "2")] pub end: u64, } +/// Missing ranges for a specific data type +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataTypeRanges { + /// Data type name (e.g., "blocks", "transactions", "logs", "events") + #[prost(string, tag = "1")] + pub data_type: ::prost::alloc::string::String, + /// Missing ranges for this data type + #[prost(message, repeated, tag = "2")] + pub ranges: ::prost::alloc::vec::Vec, +} #[derive(Clone, PartialEq, ::prost::Message)] pub struct IncompleteSegment { /// Segment number #[prost(uint64, tag = "1")] pub segment_num: u64, - /// Block range for this segment + /// Position range for this segment #[prost(uint64, tag = "2")] - pub from_block: u64, + pub from_position: u64, #[prost(uint64, tag = "3")] - pub to_block: u64, + pub to_position: u64, /// Missing data types (e.g., "blocks", "txs", "logs") #[prost(string, repeated, tag = "4")] pub missing_data_types: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - /// Detailed missing ranges for each data type + /// Generic missing ranges by data type (replaces separate fields) + #[prost(message, repeated, tag = "8")] + pub missing_ranges: ::prost::alloc::vec::Vec, + /// DEPRECATED: Use missing_ranges instead + /// Kept for backwards compatibility during transition + #[deprecated] #[prost(message, repeated, tag = "5")] - pub missing_blocks_ranges: ::prost::alloc::vec::Vec, + pub missing_blocks_ranges: ::prost::alloc::vec::Vec, + #[deprecated] #[prost(message, repeated, tag = "6")] - pub missing_transactions_ranges: ::prost::alloc::vec::Vec, + pub missing_transactions_ranges: ::prost::alloc::vec::Vec, + #[deprecated] #[prost(message, repeated, tag = "7")] - pub missing_logs_ranges: ::prost::alloc::vec::Vec, + pub missing_logs_ranges: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SyncStatusRequest { @@ -233,62 +252,90 @@ pub struct AnalyzeGapsResponse { pub message: ::prost::alloc::string::String, } /// Detailed progress tracking for each data type -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct DataProgress { - /// Blocks progress + /// Generic progress by data type name (replaces separate fields) + #[prost(map = "string, message", tag = "5")] + pub by_type: ::std::collections::HashMap< + ::prost::alloc::string::String, + DataTypeProgress, + >, + /// File statistics + #[prost(message, optional, tag = "4")] + pub file_stats: ::core::option::Option, + /// DEPRECATED: Use by_type\["blocks"\] instead + #[deprecated] #[prost(message, optional, tag = "1")] pub blocks: ::core::option::Option, - /// Transactions progress + #[deprecated] #[prost(message, optional, tag = "2")] pub transactions: ::core::option::Option, - /// Logs progress + #[deprecated] #[prost(message, optional, tag = "3")] pub logs: ::core::option::Option, - /// File statistics - #[prost(message, optional, tag = "4")] - pub file_stats: ::core::option::Option, } -/// Progress for a specific data type (blocks, transactions, or logs) +/// Progress for a specific data type #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct DataTypeProgress { - /// Number of blocks covered by files on disk (may have gaps) + /// Number of positions covered by files on disk (may have gaps) #[prost(uint64, tag = "1")] - pub blocks_on_disk: u64, + pub positions_on_disk: u64, /// Number of gaps (missing ranges) #[prost(uint32, tag = "2")] pub gap_count: u32, /// Percentage of target range covered (0-100) #[prost(double, tag = "3")] pub coverage_percentage: f64, - /// Highest continuous block number (no gaps before this) + /// Highest continuous position (no gaps before this) #[prost(uint64, tag = "4")] pub highest_continuous: u64, } -/// File and disk statistics +/// File statistics for a specific data type #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DataTypeFileStats { + #[prost(uint32, tag = "1")] + pub file_count: u32, + #[prost(uint64, tag = "2")] + pub disk_bytes: u64, +} +/// File and disk statistics +#[derive(Clone, PartialEq, ::prost::Message)] pub struct FileStatistics { /// Total number of parquet files #[prost(uint32, tag = "1")] pub total_files: u32, - /// Files by type + /// Total disk usage in bytes + #[prost(uint64, tag = "6")] + pub total_disk_bytes: u64, + /// Generic file stats by data type name + #[prost(map = "string, message", tag = "11")] + pub by_type: ::std::collections::HashMap< + ::prost::alloc::string::String, + DataTypeFileStats, + >, + /// DEPRECATED: Use by_type instead + #[deprecated] #[prost(uint32, tag = "2")] pub blocks_files: u32, + #[deprecated] #[prost(uint32, tag = "3")] pub transactions_files: u32, + #[deprecated] #[prost(uint32, tag = "4")] pub logs_files: u32, + #[deprecated] #[prost(uint32, tag = "5")] pub proofs_files: u32, - /// Total disk usage in bytes - #[prost(uint64, tag = "6")] - pub total_disk_bytes: u64, - /// Disk usage by type + #[deprecated] #[prost(uint64, tag = "7")] pub blocks_disk_bytes: u64, + #[deprecated] #[prost(uint64, tag = "8")] pub transactions_disk_bytes: u64, + #[deprecated] #[prost(uint64, tag = "9")] pub logs_disk_bytes: u64, + #[deprecated] #[prost(uint64, tag = "10")] pub proofs_disk_bytes: u64, } diff --git a/crates/phaser-query/src/sync/data_scanner.rs b/crates/phaser-query/src/sync/data_scanner.rs index d31db09..2bc46fc 100644 --- a/crates/phaser-query/src/sync/data_scanner.rs +++ b/crates/phaser-query/src/sync/data_scanner.rs @@ -2,55 +2,16 @@ use anyhow::{Context, Result}; use core_executor::ThreadPoolExecutor; use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder; use parquet::file::statistics::Statistics; +use phaser_client::sync::SegmentWork; use phaser_parquet_metadata::PhaserMetadata; use std::fs::{self, File}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, RwLock}; use tracing::{debug, info, warn}; -/// Represents a block range that has been synced -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BlockRange { - pub start: u64, - pub end: u64, -} - -/// Work needed for a specific segment -#[derive(Debug, Clone)] -pub struct SegmentWork { - pub segment_num: u64, - pub segment_from: u64, - pub segment_to: u64, - pub missing_blocks: Vec, - pub missing_transactions: Vec, - pub missing_logs: Vec, - - // Retry tracking - pub retry_count: Option, - pub last_attempt: std::time::Instant, -} - -impl SegmentWork { - pub fn is_complete(&self) -> bool { - self.missing_blocks.is_empty() - && self.missing_transactions.is_empty() - && self.missing_logs.is_empty() - } - - pub fn missing_types(&self) -> Vec { - let mut types = Vec::new(); - if !self.missing_blocks.is_empty() { - types.push("blocks".to_string()); - } - if !self.missing_transactions.is_empty() { - types.push("transactions".to_string()); - } - if !self.missing_logs.is_empty() { - types.push("logs".to_string()); - } - types - } -} +// Re-export Range as BlockRange for backwards compatibility within this module +// TODO: Eventually update all callers to use Range directly +pub use phaser_client::sync::Range as BlockRange; /// Analysis of what segments need syncing #[derive(Debug, Clone)] @@ -857,25 +818,13 @@ impl DataScanner { for segment_num in first_segment..=last_segment { let segment_from = segment_num * segment_size; let segment_to = (segment_num + 1) * segment_size - 1; - segments_needing_work.push(SegmentWork { + // Use phaser-client's generic SegmentWork with EVM data types + segments_needing_work.push(SegmentWork::new_full_segment( segment_num, segment_from, segment_to, - missing_blocks: vec![BlockRange { - start: segment_from, - end: segment_to, - }], - missing_transactions: vec![BlockRange { - start: segment_from, - end: segment_to, - }], - missing_logs: vec![BlockRange { - start: segment_from, - end: segment_to, - }], - retry_count: None, - last_attempt: std::time::Instant::now(), - }); + &["blocks", "transactions", "logs"], + )); } return Ok(GapAnalysis { total_segments, @@ -939,16 +888,18 @@ impl DataScanner { // Clean any temp files for this segment self.clean_temp_files_for_segment(segment_start, segment_end)?; - segments_needing_work.push(SegmentWork { - segment_num, - segment_from: segment_start, - segment_to: segment_end, - missing_blocks, - missing_transactions, - missing_logs, - retry_count: None, - last_attempt: std::time::Instant::now(), - }); + // Build generic SegmentWork with missing ranges per data type + let mut work = SegmentWork::new(segment_num, segment_start, segment_end); + if !missing_blocks.is_empty() { + work.add_missing("blocks", missing_blocks); + } + if !missing_transactions.is_empty() { + work.add_missing("transactions", missing_transactions); + } + if !missing_logs.is_empty() { + work.add_missing("logs", missing_logs); + } + segments_needing_work.push(work); } } diff --git a/crates/phaser-query/src/sync/error.rs b/crates/phaser-query/src/sync/error.rs index 7170f31..c91c063 100644 --- a/crates/phaser-query/src/sync/error.rs +++ b/crates/phaser-query/src/sync/error.rs @@ -1,404 +1,6 @@ -use std::fmt; +//! Re-exports of sync error types from phaser-client +//! +//! phaser-query uses phaser-client's error types for consistency. +//! All types are re-exported here for backwards compatibility. -/// Categorize an error message into an ErrorCategory -/// This is the single source of truth for error categorization based on message content -fn categorize_error_message(err_lower: &str) -> ErrorCategory { - // Connection errors - if err_lower.contains("connection") || err_lower.contains("connect") { - return ErrorCategory::Connection; - } - - // Timeout errors - if err_lower.contains("timeout") || err_lower.contains("timed out") { - return ErrorCategory::Timeout; - } - - // Cancelled errors - if err_lower.contains("cancelled") || err_lower.contains("canceled") { - return ErrorCategory::Cancelled; - } - - // Block/data not found - non-transient for historical sync - if err_lower.contains("header not found") || err_lower.contains("block not found") { - return ErrorCategory::NoData; - } - - // No data / empty responses - if err_lower.contains("no data") || err_lower.contains("empty") { - return ErrorCategory::NoData; - } - - // Stuck worker errors - if err_lower.contains("failed to make progress") { - return ErrorCategory::StuckWorker; - } - - // Protocol errors (bridge returned zero batches, etc.) - if err_lower.contains("zero batches") || err_lower.contains("protocol error") { - return ErrorCategory::ProtocolError; - } - - // Disk I/O errors - check BEFORE validation since some validation errors might mention "file" - // Include parquet/arrow write errors which are typically disk or serialization issues - if err_lower.contains("io error") - || err_lower.contains("disk") - || err_lower.contains("parquet") - || err_lower.contains("arrow") - || err_lower.contains("write error") - || err_lower.contains("failed to write") - || (err_lower.contains("file") && !err_lower.contains("validation")) - { - return ErrorCategory::DiskIo; - } - - // Validation errors - if err_lower.contains("validation") || err_lower.contains("invalid") { - return ErrorCategory::Validation; - } - - // Unknown - catch-all for unrecognized errors - ErrorCategory::Unknown -} - -/// Type of data being synced when error occurred -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DataType { - Blocks, - Transactions, - Logs, - Unknown, -} - -impl DataType { - pub fn as_str(&self) -> &'static str { - match self { - DataType::Blocks => "blocks", - DataType::Transactions => "transactions", - DataType::Logs => "logs", - DataType::Unknown => "unknown", - } - } -} - -impl fmt::Display for DataType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -/// Category of sync error for metrics and monitoring -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ErrorCategory { - /// Connection or networking issues - Connection, - /// Request timeout - Timeout, - /// Bridge returned no data for a range that should have data - NoData, - /// Worker stuck making no progress - StuckWorker, - /// Data validation failure - Validation, - /// Disk I/O error - DiskIo, - /// Operation was cancelled - Cancelled, - /// Bridge protocol error (e.g., zero batches returned) - ProtocolError, - /// Other/uncategorized error - Unknown, -} - -impl ErrorCategory { - pub fn as_str(&self) -> &'static str { - match self { - ErrorCategory::Connection => "connection", - ErrorCategory::Timeout => "timeout", - ErrorCategory::NoData => "no_data", - ErrorCategory::StuckWorker => "stuck_worker", - ErrorCategory::Validation => "validation", - ErrorCategory::DiskIo => "disk_io", - ErrorCategory::Cancelled => "cancelled", - ErrorCategory::ProtocolError => "protocol_error", - ErrorCategory::Unknown => "unknown", - } - } -} - -impl fmt::Display for ErrorCategory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -/// Structured sync error that preserves error context while providing categorization -#[derive(Debug)] -pub struct SyncError { - /// What type of data was being synced - pub data_type: DataType, - /// Category of error for metrics - pub category: ErrorCategory, - /// Block range being synced when error occurred - pub from_block: u64, - pub to_block: u64, - /// Human-readable error message - pub message: String, - /// The underlying error source, if any - pub source: Option>, -} - -/// Error when multiple data types fail during parallel sync -#[derive(Debug)] -pub struct MultipleDataTypeErrors { - pub from_block: u64, - pub to_block: u64, - pub errors: Vec<(DataType, SyncError)>, -} - -impl fmt::Display for MultipleDataTypeErrors { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Multiple data types failed syncing blocks {}-{}: ", - self.from_block, self.to_block - )?; - for (i, (data_type, err)) in self.errors.iter().enumerate() { - if i > 0 { - write!(f, "; ")?; - } - write!(f, "{data_type}: {err}")?; - } - Ok(()) - } -} - -impl std::error::Error for MultipleDataTypeErrors { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - // Return first error as source - self.errors.first().and_then(|(_, e)| e.source()) - } -} - -impl From for SyncError { - fn from(multi_err: MultipleDataTypeErrors) -> Self { - // Aggregate into a single SyncError with Unknown data type - let message = format!("{multi_err}"); - let category = multi_err - .errors - .first() - .map(|(_, e)| e.category) - .unwrap_or(ErrorCategory::Unknown); - - SyncError { - data_type: DataType::Unknown, - category, - from_block: multi_err.from_block, - to_block: multi_err.to_block, - message, - source: Some(Box::new(multi_err)), - } - } -} - -impl SyncError { - pub fn new( - data_type: DataType, - category: ErrorCategory, - from_block: u64, - to_block: u64, - message: String, - ) -> Self { - Self { - data_type, - category, - from_block, - to_block, - message, - source: None, - } - } - - pub fn with_source(mut self, source: E) -> Self - where - E: std::error::Error + Send + Sync + 'static, - { - self.source = Some(Box::new(source)); - self - } - - /// Create a protocol error (bridge returned zero batches) - pub fn protocol_error(data_type: DataType, from_block: u64, to_block: u64) -> Self { - let msg = format!( - "Bridge returned zero batches for {data_type} {from_block}-{to_block}. This indicates a protocol error." - ); - Self { - data_type, - category: ErrorCategory::ProtocolError, - from_block, - to_block, - message: msg, - source: None, - } - } - - /// Create a validation error (unexpected block range) - pub fn validation_error( - data_type: DataType, - from_block: u64, - to_block: u64, - message: String, - ) -> Self { - Self { - data_type, - category: ErrorCategory::Validation, - from_block, - to_block, - message, - source: None, - } - } - - /// Create a disk I/O error - pub fn disk_io_error( - data_type: DataType, - from_block: u64, - to_block: u64, - message: String, - ) -> Self { - Self { - data_type, - category: ErrorCategory::DiskIo, - from_block, - to_block, - message, - source: None, - } - } - - /// Wrap an arbitrary error with context - pub fn from_error(data_type: DataType, from_block: u64, to_block: u64, error: E) -> Self - where - E: std::error::Error + Send + Sync + 'static, - { - let message = error.to_string(); - let err_lower = message.to_lowercase(); - - // Categorize based on error message - let category = categorize_error_message(&err_lower); - - Self { - data_type, - category, - from_block, - to_block, - message, - source: Some(Box::new(error)), - } - } - - /// Wrap an arbitrary error with custom context message - pub fn from_error_with_context( - data_type: DataType, - from_block: u64, - to_block: u64, - context: impl Into, - error: E, - ) -> Self - where - E: std::error::Error + Send + Sync + 'static, - { - let err_str = error.to_string(); - let err_lower = err_str.to_lowercase(); - - // Categorize based on error message - let category = categorize_error_message(&err_lower); - - let context_str = context.into(); - let message = format!("{context_str}: {err_str}"); - - Self { - data_type, - category, - from_block, - to_block, - message, - source: Some(Box::new(error)), - } - } - - /// Wrap an anyhow error with custom context message - pub fn from_anyhow_with_context( - data_type: DataType, - from_block: u64, - to_block: u64, - context: impl Into, - error: anyhow::Error, - ) -> Self { - let err_str = error.to_string(); - let err_lower = err_str.to_lowercase(); - - // Categorize based on error message - let category = categorize_error_message(&err_lower); - - let context_str = context.into(); - let message = format!("{context_str}: {err_str}"); - - Self { - data_type, - category, - from_block, - to_block, - message, - source: None, // anyhow doesn't expose its source easily - } - } -} - -// Convert from anyhow::Error for backwards compatibility during migration -impl From for SyncError { - fn from(err: anyhow::Error) -> Self { - let message = err.to_string(); - let err_lower = message.to_lowercase(); - - // Detect data type - let data_type = if err_lower.contains("blocks") { - DataType::Blocks - } else if err_lower.contains("transactions") { - DataType::Transactions - } else if err_lower.contains("logs") { - DataType::Logs - } else { - DataType::Unknown - }; - - // Categorize error using common function - let category = categorize_error_message(&err_lower); - - Self { - data_type, - category, - from_block: 0, - to_block: 0, - message, - source: None, - } - } -} - -impl fmt::Display for SyncError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "[{}/{}] blocks {}-{}: {}", - self.category, self.data_type, self.from_block, self.to_block, self.message - ) - } -} - -impl std::error::Error for SyncError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.source - .as_ref() - .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) - } -} +pub use phaser_client::sync::{DataType, ErrorCategory, MultipleDataTypeErrors, SyncError}; diff --git a/crates/phaser-query/src/sync/service.rs b/crates/phaser-query/src/sync/service.rs index 44d1d9b..db14379 100644 --- a/crates/phaser-query/src/sync/service.rs +++ b/crates/phaser-query/src/sync/service.rs @@ -18,8 +18,10 @@ use tracing::{error, info, warn}; use uuid::Uuid; use crate::proto::admin::{ - DataProgress as ProtoDataProgress, DataTypeProgress as ProtoDataTypeProgress, + DataProgress as ProtoDataProgress, DataTypeFileStats, + DataTypeProgress as ProtoDataTypeProgress, DataTypeRanges, FileStatistics as ProtoFileStatistics, GapAnalysis as ProtoGapAnalysis, IncompleteSegment, + Range as ProtoRange, }; use crate::sync::data_scanner::{ DataProgress, DataTypeProgress, FileStatistics, GapAnalysis as DataGapAnalysis, @@ -31,45 +33,34 @@ fn gap_analysis_to_proto(analysis: &DataGapAnalysis, segment_size: u64) -> Proto .segments_needing_work .iter() .map(|work| { - let from_block = work.segment_num * segment_size; - let to_block = from_block + segment_size - 1; + let from_position = work.segment_num * segment_size; + let to_position = from_position + segment_size - 1; - // Convert BlockRange vectors to proto - let missing_blocks_ranges = work - .missing_blocks + // Convert all missing ranges to proto format + let missing_ranges: Vec = work + .missing_ranges .iter() - .map(|r| crate::proto::admin::BlockRange { - start: r.start, - end: r.end, - }) - .collect(); - - let missing_transactions_ranges = work - .missing_transactions - .iter() - .map(|r| crate::proto::admin::BlockRange { - start: r.start, - end: r.end, - }) - .collect(); - - let missing_logs_ranges = work - .missing_logs - .iter() - .map(|r| crate::proto::admin::BlockRange { - start: r.start, - end: r.end, + .filter(|(_, ranges)| !ranges.is_empty()) + .map(|(data_type, ranges)| DataTypeRanges { + data_type: data_type.clone(), + ranges: ranges + .iter() + .map(|r| ProtoRange { + start: r.start, + end: r.end, + }) + .collect(), }) .collect(); IncompleteSegment { segment_num: work.segment_num, - from_block, - to_block, + from_position, + to_position, missing_data_types: work.missing_types(), - missing_blocks_ranges, - missing_transactions_ranges, - missing_logs_ranges, + missing_ranges, + // Deprecated fields removed - use missing_ranges instead + ..Default::default() } }) .collect(); @@ -91,18 +82,32 @@ fn gap_analysis_to_proto(analysis: &DataGapAnalysis, segment_size: u64) -> Proto /// Convert internal DataProgress to proto fn data_progress_to_proto(progress: &DataProgress) -> ProtoDataProgress { + let mut by_type = std::collections::HashMap::new(); + by_type.insert( + "blocks".to_string(), + data_type_progress_to_proto(&progress.blocks), + ); + by_type.insert( + "transactions".to_string(), + data_type_progress_to_proto(&progress.transactions), + ); + by_type.insert( + "logs".to_string(), + data_type_progress_to_proto(&progress.logs), + ); + ProtoDataProgress { - blocks: Some(data_type_progress_to_proto(&progress.blocks)), - transactions: Some(data_type_progress_to_proto(&progress.transactions)), - logs: Some(data_type_progress_to_proto(&progress.logs)), + by_type, file_stats: Some(file_statistics_to_proto(&progress.file_stats)), + // Deprecated fields removed - use by_type instead + ..Default::default() } } /// Convert internal DataTypeProgress to proto fn data_type_progress_to_proto(progress: &DataTypeProgress) -> ProtoDataTypeProgress { ProtoDataTypeProgress { - blocks_on_disk: progress.blocks_on_disk, + positions_on_disk: progress.blocks_on_disk, gap_count: progress.gap_count, coverage_percentage: progress.coverage_percentage, highest_continuous: progress.highest_continuous, @@ -111,17 +116,42 @@ fn data_type_progress_to_proto(progress: &DataTypeProgress) -> ProtoDataTypeProg /// Convert internal FileStatistics to proto fn file_statistics_to_proto(stats: &FileStatistics) -> ProtoFileStatistics { + let mut by_type = std::collections::HashMap::new(); + by_type.insert( + "blocks".to_string(), + DataTypeFileStats { + file_count: stats.blocks_files, + disk_bytes: stats.blocks_disk_bytes, + }, + ); + by_type.insert( + "transactions".to_string(), + DataTypeFileStats { + file_count: stats.transactions_files, + disk_bytes: stats.transactions_disk_bytes, + }, + ); + by_type.insert( + "logs".to_string(), + DataTypeFileStats { + file_count: stats.logs_files, + disk_bytes: stats.logs_disk_bytes, + }, + ); + by_type.insert( + "proofs".to_string(), + DataTypeFileStats { + file_count: stats.proofs_files, + disk_bytes: stats.proofs_disk_bytes, + }, + ); + ProtoFileStatistics { total_files: stats.total_files, - blocks_files: stats.blocks_files, - transactions_files: stats.transactions_files, - logs_files: stats.logs_files, - proofs_files: stats.proofs_files, total_disk_bytes: stats.total_disk_bytes, - blocks_disk_bytes: stats.blocks_disk_bytes, - transactions_disk_bytes: stats.transactions_disk_bytes, - logs_disk_bytes: stats.logs_disk_bytes, - proofs_disk_bytes: stats.proofs_disk_bytes, + by_type, + // Deprecated fields removed - use by_type instead + ..Default::default() } } @@ -353,10 +383,15 @@ impl SyncServer { }; let segment_num = work.segment_num; - let segment_from = work.segment_from; - let segment_to = work.segment_to; + let segment_from = work.segment_start; + let segment_to = work.segment_end; let retry_count = work.retry_count.unwrap_or(0); - let segment_start = std::time::Instant::now(); + let segment_start_time = std::time::Instant::now(); + + // Count missing ranges by data type + let missing_blocks_count = work.get_missing_ranges("blocks").len(); + let missing_txs_count = work.get_missing_ranges("transactions").len(); + let missing_logs_count = work.get_missing_ranges("logs").len(); info!( worker_id = worker_id, @@ -364,9 +399,9 @@ impl SyncServer { from_block = segment_from, to_block = segment_to, retry_count = retry_count, - missing_blocks = work.missing_blocks.len(), - missing_txs = work.missing_transactions.len(), - missing_logs = work.missing_logs.len(), + missing_blocks = missing_blocks_count, + missing_txs = missing_txs_count, + missing_logs = missing_logs_count, "Starting segment processing" ); @@ -396,7 +431,7 @@ impl SyncServer { match worker.run().await { Ok(()) => { - let duration = segment_start.elapsed(); + let duration = segment_start_time.elapsed(); // Metrics metrics.segment_attempts("success"); @@ -427,15 +462,8 @@ impl SyncServer { // Metrics metrics.sync_errors(error_category, data_type); - // Check if error is retryable - // Most errors should be retried - only validation and stuck workers are truly non-retryable - use crate::sync::error::ErrorCategory; - let is_retryable = !matches!( - sync_err.category, - ErrorCategory::Validation - | ErrorCategory::StuckWorker - | ErrorCategory::NoData - ); + // Check if error is retryable using phaser-client's method + let is_retryable = !sync_err.is_permanent(); if !is_retryable { // Non-retryable error - fail immediately diff --git a/crates/phaser-query/src/sync/worker.rs b/crates/phaser-query/src/sync/worker.rs index a909df2..d9a2b8a 100644 --- a/crates/phaser-query/src/sync/worker.rs +++ b/crates/phaser-query/src/sync/worker.rs @@ -6,6 +6,7 @@ use arrow::array as arrow_array; use evm_common::proof::{generate_transaction_proof, MerkleProofRecord}; use evm_common::transaction::TransactionRecord; use futures::StreamExt; +use phaser_client::sync::SegmentWork; use phaser_client::{GenericQuery, PhaserClient, ValidationStage}; use phaser_metrics::SegmentMetrics; use std::collections::HashMap; @@ -18,13 +19,8 @@ use typed_arrow::prelude::*; /// Check if an error is transient and should trigger a retry fn is_transient_error(err: &SyncError) -> bool { - // Check error category for transient types - matches!( - err.category, - ErrorCategory::Connection | ErrorCategory::Timeout | ErrorCategory::Cancelled - ) || - // Also check message for specific transient patterns - err.message.contains("Timeout expired") + // Use the is_transient() method from phaser-client + err.is_transient() } /// Worker progress tracking @@ -88,8 +84,8 @@ pub struct SyncWorker { parquet_config: Option, progress_tracker: Option, _validation_stage: phaser_client::ValidationStage, - segment_work: crate::sync::data_scanner::SegmentWork, // Pre-computed missing ranges - current_progress: Arc>, // Real-time progress state + segment_work: SegmentWork, // Pre-computed missing ranges + current_progress: Arc>, // Real-time progress state logs_semaphore: Arc, } @@ -97,7 +93,7 @@ impl SyncWorker { pub fn new( worker_id: u32, config: SyncWorkerConfig, - segment_work: crate::sync::data_scanner::SegmentWork, + segment_work: SegmentWork, historical_boundary: Option, ) -> Self { // Cap to_block at historical boundary if present @@ -231,9 +227,15 @@ impl SyncWorker { ); // Use pre-computed missing ranges from segment_work (no file I/O needed!) - let missing_blocks = self.segment_work.missing_blocks.clone(); - let missing_txs = self.segment_work.missing_transactions.clone(); - let missing_logs = self.segment_work.missing_logs.clone(); + // Convert from generic Range to BlockRange for backwards compat + let missing_blocks: Vec = + self.segment_work.get_missing_ranges("blocks").to_vec(); + let missing_txs: Vec = self + .segment_work + .get_missing_ranges("transactions") + .to_vec(); + let missing_logs: Vec = + self.segment_work.get_missing_ranges("logs").to_vec(); debug!( "Worker {} segment work: blocks={} ranges, txs={} ranges, logs={} ranges", @@ -254,13 +256,13 @@ impl SyncWorker { // Collect all errors let mut errors = Vec::new(); if let Err(e) = blocks_res { - errors.push((DataType::Blocks, e)); + errors.push((DataType::new("blocks"), e)); } if let Err(e) = txs_res { - errors.push((DataType::Transactions, e)); + errors.push((DataType::new("transactions"), e)); } if let Err(e) = logs_res { - errors.push((DataType::Logs, e)); + errors.push((DataType::new("logs"), e)); } // If any failed, return MultipleDataTypeErrors @@ -299,7 +301,7 @@ impl SyncWorker { .await .map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Blocks, + DataType::new("blocks"), self.from_block, self.to_block, "Failed to connect to bridge", @@ -333,7 +335,7 @@ impl SyncWorker { .await .map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Transactions, + DataType::new("transactions"), self.from_block, self.to_block, "Failed to connect to bridge", @@ -376,7 +378,7 @@ impl SyncWorker { .await .map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Logs, + DataType::new("logs"), self.from_block, self.to_block, "Failed to acquire log semaphore permit for segment", @@ -395,7 +397,7 @@ impl SyncWorker { .await .map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Logs, + DataType::new("logs"), self.from_block, self.to_block, "Failed to connect to bridge", @@ -478,7 +480,7 @@ impl SyncWorker { retries_without_progress += 1; if retries_without_progress >= MAX_RETRIES_WITHOUT_PROGRESS { return Err(SyncError::new( - DataType::Blocks, + DataType::new("blocks"), ErrorCategory::StuckWorker, resume_from, to_block, @@ -546,7 +548,7 @@ impl SyncWorker { // Subscribe to the block stream with metadata (returns RecordBatch + responsibility range) let stream = client.query_with_metadata(query).await.map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Blocks, + DataType::new("blocks"), from_block, to_block, "Failed to subscribe to block stream", @@ -566,7 +568,7 @@ impl SyncWorker { Err(e) => { // Preserve the full gRPC/Flight error chain from bridge/erigon return Err(SyncError::from_error_with_context( - DataType::Blocks, + DataType::new("blocks"), from_block, to_block, "Failed to receive block batch", @@ -611,7 +613,7 @@ impl SyncWorker { // Write Arrow RecordBatch directly to parquet and get actual bytes written let batch_bytes = writer.write_batch(batch).await.map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Blocks, + DataType::new("blocks"), from_block, to_block, "Failed to write block batch to parquet", @@ -630,7 +632,7 @@ impl SyncWorker { if let (Some(first), Some(last)) = (first_block_seen, last_block_seen) { if from_block == original_from && first != from_block { return Err(SyncError::validation_error( - DataType::Blocks, + DataType::new("blocks"), from_block, to_block, format!( @@ -640,7 +642,7 @@ impl SyncWorker { } if last != to_block { return Err(SyncError::validation_error( - DataType::Blocks, + DataType::new("blocks"), from_block, to_block, format!( @@ -652,7 +654,7 @@ impl SyncWorker { // Finalize with the requested end block to ensure proper filename writer.finalize_current_file().map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Blocks, + DataType::new("blocks"), from_block, to_block, "Failed to finalize parquet file", @@ -776,7 +778,7 @@ impl SyncWorker { retries_without_progress += 1; if retries_without_progress >= MAX_RETRIES_WITHOUT_PROGRESS { return Err(SyncError::new( - DataType::Transactions, + DataType::new("transactions"), ErrorCategory::StuckWorker, resume_from, to_block, @@ -873,7 +875,7 @@ impl SyncWorker { ); // Preserve the full gRPC/Flight error chain from bridge/erigon return Err(SyncError::from_error_with_context( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, "Failed to receive transaction batch", @@ -934,7 +936,7 @@ impl SyncWorker { if let Ok(proof_batch) = self.generate_proofs_for_batch(&batch) { proof_w.write_batch(proof_batch).await.map_err(|e| { SyncError::disk_io_error( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, format!("Failed to write proof batch: {e}"), @@ -951,7 +953,7 @@ impl SyncWorker { // Write Arrow RecordBatch directly to parquet and get actual bytes written let batch_bytes = writer.write_batch(batch).await.map_err(|e| { SyncError::disk_io_error( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, format!("Failed to write transaction batch: {e}"), @@ -980,7 +982,7 @@ impl SyncWorker { if let (Some(first), Some(last)) = (first_block_seen, last_block_seen) { if first < from_block || first > to_block { return Err(SyncError::validation_error( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, format!( @@ -990,7 +992,7 @@ impl SyncWorker { } if last < from_block || last > to_block { return Err(SyncError::validation_error( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, format!( @@ -1002,7 +1004,7 @@ impl SyncWorker { // Finalize with the requested end block to ensure proper filename writer.finalize_current_file().map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, "Failed to finalize parquet file", @@ -1012,7 +1014,7 @@ impl SyncWorker { if let Some(ref mut proof_w) = proof_writer { proof_w.finalize_current_file().map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Transactions, + DataType::new("transactions"), from_block, to_block, "Failed to finalize proof parquet file", @@ -1155,7 +1157,7 @@ impl SyncWorker { retries_without_progress += 1; if retries_without_progress >= MAX_RETRIES_WITHOUT_PROGRESS { return Err(SyncError::new( - DataType::Logs, + DataType::new("logs"), ErrorCategory::StuckWorker, resume_from, to_block, @@ -1225,7 +1227,7 @@ impl SyncWorker { // Subscribe to the log stream with metadata (returns RecordBatch + responsibility range) let stream = client.query_with_metadata(query).await.map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Logs, + DataType::new("logs"), from_block, to_block, "Failed to subscribe to log stream", @@ -1245,7 +1247,7 @@ impl SyncWorker { Err(e) => { // Preserve the full gRPC/Flight error chain from bridge/erigon return Err(SyncError::from_error_with_context( - DataType::Logs, + DataType::new("logs"), from_block, to_block, "Failed to receive log batch", @@ -1290,7 +1292,7 @@ impl SyncWorker { // Write Arrow RecordBatch directly to parquet and get actual bytes written let batch_bytes = writer.write_batch(batch).await.map_err(|e| { SyncError::disk_io_error( - DataType::Logs, + DataType::new("logs"), from_block, to_block, format!("Failed to write log batch: {e}"), @@ -1309,7 +1311,7 @@ impl SyncWorker { if let (Some(first), Some(last)) = (first_block_seen, last_block_seen) { if first < from_block || first > to_block { return Err(SyncError::validation_error( - DataType::Logs, + DataType::new("logs"), from_block, to_block, format!( @@ -1319,7 +1321,7 @@ impl SyncWorker { } if last < from_block || last > to_block { return Err(SyncError::validation_error( - DataType::Logs, + DataType::new("logs"), from_block, to_block, format!( @@ -1331,7 +1333,7 @@ impl SyncWorker { // Finalize with the requested end block to ensure proper filename writer.finalize_current_file().map_err(|e| { SyncError::from_anyhow_with_context( - DataType::Logs, + DataType::new("logs"), from_block, to_block, "Failed to finalize parquet file", diff --git a/crates/phaser-server/src/lib.rs b/crates/phaser-server/src/lib.rs index 969a337..e0600da 100644 --- a/crates/phaser-server/src/lib.rs +++ b/crates/phaser-server/src/lib.rs @@ -37,14 +37,11 @@ pub use phaser_types::{ // Batch metadata BatchMetadata, BatchWithRange, - BlockRange, // Descriptors BlockchainDescriptor, BridgeInfo, Compression, ControlAction, - DataAvailability, - DataSource, // Discovery DiscoveryCapabilities, FilterDescriptor, diff --git a/crates/phaser-types/Cargo.toml b/crates/phaser-types/Cargo.toml index 54e68bf..ddb790d 100644 --- a/crates/phaser-types/Cargo.toml +++ b/crates/phaser-types/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "phaser-types" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true description = "Shared protocol types for phaser (discovery, descriptors, subscription)" [dependencies] diff --git a/crates/phaser-types/src/lib.rs b/crates/phaser-types/src/lib.rs index 6001899..887fde2 100644 --- a/crates/phaser-types/src/lib.rs +++ b/crates/phaser-types/src/lib.rs @@ -22,8 +22,8 @@ pub use discovery::{ ACTION_DESCRIBE, }; pub use subscription::{ - BackpressureStrategy, BlockRange, ControlAction, DataAvailability, DataSource, FilterSpec, - QueryMode, SubscriptionHandle, SubscriptionInfo, SubscriptionOptions, + BackpressureStrategy, ControlAction, FilterSpec, QueryMode, SubscriptionHandle, + SubscriptionInfo, SubscriptionOptions, }; /// Responsibility range metadata for a batch diff --git a/crates/phaser-types/src/subscription.rs b/crates/phaser-types/src/subscription.rs index 3bbedae..1844bbc 100644 --- a/crates/phaser-types/src/subscription.rs +++ b/crates/phaser-types/src/subscription.rs @@ -84,35 +84,3 @@ pub enum QueryMode { /// Subscribe to live data from current head Live, } - -/// Data availability information for query planning -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DataAvailability { - /// Available block ranges - pub ranges: Vec, - /// Gaps in available data - pub gaps: Vec<(u64, u64)>, - /// Estimated size per range (bytes) - pub estimated_sizes: Vec<(BlockRange, u64)>, -} - -/// A contiguous range of blocks -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BlockRange { - pub start: u64, - pub end: u64, - pub source: DataSource, -} - -/// Where the data comes from -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum DataSource { - /// Live streaming from node - Live, - /// Cached in memory - Cache, - /// Historical parquet files - Parquet, - /// Need to query node - NodeQuery, -} diff --git a/docs/design-position-abstraction.md b/docs/design-position-abstraction.md new file mode 100644 index 0000000..b521acb --- /dev/null +++ b/docs/design-position-abstraction.md @@ -0,0 +1,322 @@ +# Design: Position-Based Data Abstraction + +## Problem Statement + +The current Phaser and AMP architecture uses `BlockRange` as its fundamental unit of progress tracking: + +```rust +pub struct BlockRange { + pub numbers: RangeInclusive, + pub network: NetworkId, + pub hash: BlockHash, // EVM-specific: 32-byte Keccak hash + pub prev_hash: BlockHash, // EVM-specific: parent block hash + pub timestamp: Option, +} +``` + +This design has several problems: + +1. **EVM-centric assumptions**: `BlockHash` is a 32-byte Keccak hash, which only makes sense for EVM chains. Canton, Solana, and other chains have different hash formats or no meaningful block hashes at all. + +2. **"Block" is misleading**: For non-blockchain data sources (Canton ledger offsets, event streams), there are no "blocks" - just positions in a sequence. + +3. **Hash-based adjacency is fragile**: The `prev_hash == hash` adjacency check assumes blockchain fork semantics. Canton has no forks. Solana has different finality semantics. + +4. **Leaky abstraction**: Code throughout the stack has to handle `BlockHash::ZERO` as a sentinel value meaning "hash not applicable." + +## Proposed Solution: Position Ranges + +Replace the EVM-specific `BlockRange` with a chain-agnostic `PositionRange`: + +```rust +/// A range of positions in a data stream. +/// +/// Positions are opaque u64 values that represent progress through a data source. +/// The interpretation depends on the source: +/// - EVM chains: block number +/// - Canton: ledger offset +/// - Solana: slot number +/// - Event streams: sequence number +pub struct PositionRange { + /// Start position (inclusive) + pub start: u64, + /// End position (inclusive) + pub end: u64, + /// Network identifier + pub network: NetworkId, + /// Optional bridge-specific cursor for precise resumption + pub cursor: Option, + /// Optional timestamp of the end position + pub timestamp: Option, +} + +/// Opaque cursor for precise position identification. +/// +/// Cursors are bridge-specific state serialized as JSON. The sync layer +/// treats them as opaque - it stores and forwards them, but never inspects +/// the contents. Bridges produce cursors when emitting data and consume +/// them when resuming. +/// +/// Using JSON provides: +/// - Human-readable debugging +/// - Easy serialization/deserialization in any language +/// - Schema flexibility - bridges can evolve their cursor format +/// - No coupling between sync layer and bridge internals +pub type Cursor = serde_json::Value; +``` + +### Example Cursors + +Bridges define their own cursor schemas. The sync layer never parses these: + +```json +// EVM bridge cursor - enables fork detection +{ + "block_hash": "0xabc123...", + "prev_hash": "0xdef456...", + "total_difficulty": "12345678901234567890" +} + +// Canton bridge cursor - participant-specific offset +{ + "participant_id": "participant1::namespace", + "offset": 12345, + "domain_id": "domain1" +} + +// Solana bridge cursor - slot with commitment +{ + "slot": 123456789, + "commitment": "finalized", + "blockhash": "ABC123..." +} + +// Kafka bridge cursor - partition offsets +{ + "partition_offsets": { + "0": 1000, + "1": 2000, + "2": 1500 + } +} + +// Generic event stream - just a sequence number +{ + "seq": 999 +} +``` + +## Key Design Principles + +### 0. Sync Layer is Source-Agnostic + +The sync layer (phaser-client) should have **zero knowledge** of specific data sources. It doesn't know about: +- Blockchains vs event streams vs databases +- EVM vs Solana vs Canton +- Blocks vs slots vs offsets +- Hashes vs sequence numbers + +It only knows about: +- **Positions**: monotonically increasing u64 values +- **Ranges**: start and end positions +- **Data types**: string names like "transactions", "events", "state_changes" +- **Cursors**: opaque JSON blobs it passes through + +This separation keeps the sync layer simple and lets bridges implement source-specific logic without polluting the core abstractions. + +### 1. Position is Always u64 + +Every data source can be mapped to a monotonically increasing u64 position: + +| Source | Position Meaning | +|--------|------------------| +| EVM chain | Block number | +| Canton | Ledger offset | +| Solana | Slot number | +| Kafka | Offset | +| Generic stream | Sequence number | + +This enables uniform progress tracking, range queries, and segment alignment regardless of the underlying source. + +### 2. Cursor is Optional and Opaque + +Cursors carry bridge-specific state needed for: +- **Fork detection** (EVM: compare hashes to detect reorgs) +- **Precise resumption** (resume from exact position after restart) +- **Consistency guarantees** (Solana commitment levels) + +But cursors are optional because: +- Not all sources have meaningful cursors (simple event streams may not need them) +- Progress tracking works fine with just positions +- Cursors are only needed for advanced features (fork handling, exact resumption) + +**Key principle**: The sync layer is cursor-agnostic. It stores cursors in metadata, passes them to bridges on resume, but never parses or interprets them. This keeps the sync layer generic while letting bridges implement source-specific semantics. + +### 3. Adjacency is Position-Based + +Two ranges are adjacent if `range1.end + 1 == range2.start`. Fork detection (if needed) is the bridge's responsibility, not the sync layer's. + +```rust +impl PositionRange { + /// Check if this range is adjacent to (immediately precedes) another + pub fn is_adjacent_to(&self, other: &PositionRange) -> bool { + self.network == other.network && self.end + 1 == other.start + } +} + +// Fork detection is bridge-specific, not in PositionRange +trait Bridge { + /// Check if resuming from this cursor would hit a fork. + /// Bridge implementations parse their own cursor format. + fn detects_fork(&self, stored_cursor: &Cursor, current_state: &BridgeState) -> bool; +} +``` + +### 4. Zero is a Valid Position + +Unlike `BlockHash::ZERO` which is a sentinel meaning "no hash," position 0 is a valid position (genesis block, first event). There's no need for sentinel values. + +## Migration Path + +### Phase 1: Add PositionRange alongside BlockRange + +```rust +// In phaser-client +pub use position::{PositionRange, Cursor}; + +// Bridge metadata can return either +pub struct BatchMetadata { + pub responsibility_range: PositionRange, + // Deprecated: for backwards compat + pub legacy_block_range: Option, +} +``` + +### Phase 2: Update bridges to emit PositionRange + +- erigon-bridge: JSON cursor with `block_hash`, `prev_hash` +- canton-bridge: JSON cursor with `participant_id`, `offset` +- New bridges: Define their own JSON cursor schema or use `None` + +### Phase 3: Update consumers + +- phaser-query: Use `PositionRange` for gap analysis, file naming +- AMP: Use `PositionRange` in parquet metadata, restore logic + +### Phase 4: Deprecate BlockRange + +Once all consumers are migrated, deprecate `BlockRange` and remove EVM-specific assumptions from the core abstractions. + +## File Naming + +Current file naming uses block numbers: +``` +000000000-{hash}.parquet # Start block in filename +``` + +This continues to work since positions are u64: +``` +000000000-{random}.parquet # Position-based, works for any source +``` + +The hash suffix becomes optional/random since it was primarily for debugging EVM data. + +## Segment Boundaries + +Segment boundaries remain position-based: +```rust +let segment_size = 500_000; +let segment_num = position / segment_size; +let segment_start = segment_num * segment_size; +let segment_end = segment_start + segment_size - 1; +``` + +This works identically for blocks, offsets, slots, or any u64 position. + +## Benefits + +1. **Chain-agnostic**: Same abstractions work for EVM, Canton, Solana, and future sources +2. **Simpler**: No sentinel values, no hash-based adjacency checks +3. **Cleaner separation**: Position for progress, cursor for chain-specific semantics +4. **Forward-compatible**: New chains just define their cursor type +5. **Testable**: Position ranges are easy to construct and compare in tests + +## Implementation in phaser-client Sync Module + +The `phaser-client` sync module introduces a simple `Range` type for internal progress tracking: + +```rust +/// A contiguous range of positions (e.g., blocks, slots, heights) +/// +/// Generic range type for sync operations. The unit depends on context - +/// could be block numbers, slot numbers, or any ordered position. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Range { + pub start: u64, + pub end: u64, +} +``` + +This is intentionally simpler than `PositionRange`: +- No `network` field (implied by context) +- No `cursor` field (sync module doesn't need fork detection) +- No `timestamp` (not needed for range tracking) + +### SegmentWork Uses Generic Ranges + +```rust +pub struct SegmentWork { + pub segment_num: u64, + pub segment_from: u64, + pub segment_to: u64, + /// Missing ranges by data type name (e.g., "blocks", "transactions", "events") + pub missing_ranges: HashMap>, + pub retry_count: Option, + pub last_attempt: Instant, +} +``` + +Key design decisions: +- **String-keyed data types**: Rather than an enum (`Blocks`, `Transactions`, `Logs`), use strings. This makes the sync module chain-agnostic - Canton can use `"events"`, Solana can use `"account_updates"`, etc. +- **No hardcoded types**: The sync module doesn't know about "blocks" or "transactions" - it just syncs named data streams. + +### WorkerProgress Uses String-Based Completion Tracking + +```rust +pub struct WorkerProgress { + pub worker_id: u32, + pub from_block: u64, // TODO: rename to from_position + pub to_block: u64, // TODO: rename to to_position + pub current_phase: String, + /// Data types that have been completed + pub completed_types: HashSet, + /// Data types expected to complete + pub expected_types: HashSet, + // ... +} +``` + +### Relationship to PositionRange + +| Type | Location | Purpose | +|------|----------|---------| +| `Range` | phaser-client/sync | Internal progress tracking, simple start/end | +| `PositionRange` | phaser-types (proposed) | Wire format with network ID, cursor, timestamp | +| `BlockRange` | phaser-types (legacy) | EVM-specific, has hash fields | + +The sync module's `Range` is intentionally minimal. When we need to communicate ranges across the wire (batch metadata, manifest files), we'd use `PositionRange`. The sync module converts between them at boundaries. + +## Open Questions + +1. **Should PositionRange be in phaser-client or a shared crate?** If AMP also uses it, maybe it belongs in a common crate. + +2. **Naming: `from_block`/`to_block` vs `from_position`/`to_position`?** The sync module still uses "block" terminology in some places. Should rename to be position-agnostic. + +3. **Should Range be `RangeInclusive` or custom struct?** Using `std::ops::RangeInclusive` loses the nice methods (overlaps, contains) and has different semantics (exclusive vs inclusive end). + +4. **Cursor validation**: Should bridges validate cursor schemas on resume? JSON is flexible but a malformed cursor could cause confusing errors deep in bridge code. Consider adding a `validate_cursor()` method to bridges. + +5. **Cursor versioning**: If a bridge's cursor schema changes, how do we handle old cursors? Options: + - Include a version field in the cursor + - Bridges handle migration internally + - Just fail and require re-sync from position (safest) diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..1a35d66 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.91"