diff --git a/.gitignore b/.gitignore index af98ef201..d0bd3df09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,19 @@ # Mac OS .DS_Store -# To do -/charts -TODO.md - # Builds -dist target -# I/O -in -out -.log -/datasets -/datasets2 -/price -*..* -/txout_* -/db - -# Sync -.stfolder - # Copies *\ copy* # Ignored -ignore - -# Scripts -/start-node.sh +/_* # Editors .vscode .zed -# Configs -config.toml - # Flamegraph flamegraph/ flamegraph.svg @@ -53,7 +28,11 @@ snapshots*/ docker/kibo # Types -website/scripts/types/paths.d.ts +paths.d.ts +vecid-to-indexes.d.ts -# Misc -OPENSATS.md +# Outputs +_outputs + +# Python +.ropeproject diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f703c65c..1714fa916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,44 @@ -# Changelog - -## v. 0.5.0 | [873199](https://mempool.space/block/0000000000000000000270925aa6a565be92e13164565a3f7994ca1966e48050) - 2024/12/04 +# v0.6.0 | WIP | A new beginning -![Image of the kibō Web App version 0.5.0](./assets/v0.5.0.jpg) +## Global + +- Completely redesign the back-end + +- Merged parser and server crates into a single project (and thus executable), so now both will run at the same time with a single `cargo run -r` [#7392982](https://github.com/kibo-money/kibo/commit/7392982824c2db94bcd57251fd41986117c29a23) +- Added `--no-server` and `--no-parser` to disable each if needed +- Improved executable parameters +- Started using `log` and `env_logger` crates instead of custom code [#7392982](https://github.com/kibo-money/kibo/commit/7392982824c2db94bcd57251fd41986117c29a23) +- Improved logs +- Fixed input being unfocused right after being focused in Brave browser [#9a9ae61](https://github.com/kibo-money/kibo/commit/9a9ae614d07b54c08b7e9c0e2aefe3b52fdb93c5) + +- Reworked server's API code [#6ab0f46]( https://github.com/kibo-money/kibo/commit/6ab0f463119a902a1b7ca9691b54f61543bb8f2f) + - New route format: `/api/date-to-realized-price` is now `/api/realized-price?kind=date` + - Added status and timing to logs +- Updated website packages +- Added API support for datasets by timestamp (by merging any dataset by height with the height to timestamp dataset and so it still uses heights as chunk ids) [#ca00f3f](https://github.com/kibo-money/kibo/commit/ca00f3f71526f0c5c16021024fec7e5c6e47221c) + - `/api/realized-price?kind=t` + - `/api/realized-price?kind=timestamp&chunk=860000` +- Created separate crate for indexing called `bindex` +- Created a crate a storage engine specialized in storing datasets that have indexes as keys and thus can be represented by an array/vec called `storable-vec` +- Removed the need for the `-txindex=1` parameter when starting your Bitcoin Core node as kibō has its own indexes now + +## Git + +Added git tags for each version though Markdown won't display formatted on Github so left the default text + +## Deprecated + +Moved Sanakirja database wrapper to its own crate (`snkrj`) and added a robust auto defragmentation to improve disk usage without the need for user's intervention. +Since it's not used anymore it will moved out of the repository relatively soon. + +# [v0.5.0](https://github.com/kibo-money/kibo/tree/eea56d394bf92c62c81da8b78b8c47ea730683f5) | [873199](https://mempool.space/block/0000000000000000000270925aa6a565be92e13164565a3f7994ca1966e48050) - 2024/12/04 + +![Image of the kibō Web App version 0.5.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.5.0.jpg) ## Datasets @@ -72,15 +103,15 @@ - Moved back to this repo -## v. 0.4.0 | [861950](https://mempool.space/block/00000000000000000000530d0e30ccf7deeace122dcc99f2668a06c6dad83629) - 2024/09/19 +# [v0.4.0](https://github.com/kibo-money/kibo/tree/a64c544815d9ef785e2fc1323582f774f16b9200) | [861950](https://mempool.space/block/00000000000000000000530d0e30ccf7deeace122dcc99f2668a06c6dad83629) - 2024/09/19 -![Image of the kibō Web App version 0.4.0](./assets/v0.4.0.jpg) +![Image of the kibō Web App version 0.4.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.4.0.jpg) -### Brand +## Brand - **Satonomics** is now **kibō** 🎉 -### Website +## Website - Complete redesign of the website - Rewrote the whole application and removed `node`/`npm`/`pnpm` dependencies in favor for pure `HTML`/`CSS`/`Javascript` @@ -88,7 +119,7 @@ - Added Trading View attribution link to the settings frame and file in the lightweight charts folder - Many other changes -### Parser +## Parser - Changed the block iterator from a custom version of [bitcoin-explorer](https://crates.io/crates/bitcoin-explorer) to the homemade [biter](https://crates.io/crates/biter) which allows the parser to run alongside `bitcoind` - Added datasets compression thanks to [zstd](https://crates.io/crates/zstd) to reduce disk usage @@ -103,17 +134,17 @@ - Various first run fixes - Added to `-h` which arguments are saved, which is all of them at the time of writing -### Server +## Server - Updated the code to support compressed binaries - Added serving of the website - Improved `Cache-Control` behavior -## v. 0.3.0 | [853930](https://mempool.space/block/00000000000000000002eb5e9a7950ca2d5d98bd1ed28fc9098aa630d417985d) - 2024/07/26 +# [v0.3.0](https://github.com/kibo-money/kibo/tree/b68b016091c45b071218fba01bac5b76e8eaf18c) | [853930](https://mempool.space/block/00000000000000000002eb5e9a7950ca2d5d98bd1ed28fc9098aa630d417985d) - 2024/07/26 -![Image of the Satonomics Web App version 0.3.0](./assets/v0.3.0.jpg) +![Image of the Satonomics Web App version 0.3.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.3.0.jpg) -### Parser +## Parser - Global - Improved self-hosting by: @@ -156,7 +187,7 @@ - Price - Improved error message when price cannot be found -### App +## App - General - Added chart scroll button for nice animations à la Wicked @@ -182,17 +213,17 @@ - Settings - Removed the horizontal scroll bar which was unintended -### Server +## Server - Run file - Only run with a watcher if `cargo watch` is available - Removed id_to_path file in favor for only `paths.d.ts` in `app/src/types` -## v. 0.2.0 | [851286](https://mempool.space/block/0000000000000000000281ca7f1bf8c50702bfca168c7af1bdc67c977c1ac8ed) - 2024/07/08 +# [v0.2.0](https://github.com/kibo-money/kibo/tree/248187889283597c5dbb806292297453c25e97b8) | [851286](https://mempool.space/block/0000000000000000000281ca7f1bf8c50702bfca168c7af1bdc67c977c1ac8ed) - 2024/07/08 -![Image of the Satonomics Web App version 0.2.0](./assets/v0.2.0.jpg) +![Image of the Satonomics Web App version 0.2.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.2.0.jpg) -### App +## App - General - Added the height version of all datasets and many optimizations to make them usable but only available on desktop and tablets for now @@ -220,24 +251,24 @@ - Hopefully made scrollbars a little more subtle on WIndows and Linux, can't test - Generale style updates -### Parser +## Parser - Fixed ulimit only being run in Mac OS instead of whenever the program is detected -## v. 0.1.1 | [849240](https://mempool.space/block/000000000000000000002b8653988655071c07bb5f7181c038f9326bc86db741) - 2024/06/24 +# [v0.1.1](https://github.com/kibo-money/kibo/tree/e55b5195a9de9aea306903c94ed63cb1720fda5f) | [849240](https://mempool.space/block/000000000000000000002b8653988655071c07bb5f7181c038f9326bc86db741) - 2024/06/24 -![Image of the Satonomics Web App version 0.1.1](./assets/v0.1.1.jpg) +![Image of the Satonomics Web App version 0.1.1](https://github.com/kibo-money/kibo/blob/main/_assets/v0.1.1.jpg) -### Parser +## Parser - Fixed overflow in `Price` struct which caused many Realized Caps and Realized Prices to have completely bogus data - Fixed Realized Cap computation which was using rounded prices instead normal ones -### Server +## Server - Added the chunk, date and time of the request to the terminal logs -### App +## App - Chart - Added double click option on a legend to toggle the visibility of all other series @@ -270,14 +301,14 @@ - Misc - Removed tracker even though it was a very privacy friendly as it appeared to not be working properly -### Price +## Price - Deleted old price datasets and their backups -## v. 0.1.0 | [848642](https://mempool.space/block/000000000000000000020be5761d70751252219a9557f55e91ecdfb86c4e026a) - 2024/06/19 +# [v0.1.0](https://github.com/kibo-money/kibo/tree/a1a576d088c8f83ed32d48753a7611f70a964574) | [848642](https://mempool.space/block/000000000000000000020be5761d70751252219a9557f55e91ecdfb86c4e026a) - 2024/06/19 -![Image of the Satonomics Web App version 0.1.0](./assets/v0.1.0.jpg) +![Image of the Satonomics Web App version 0.1.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.1.0.jpg) -## v. 0.0.X | [835444](https://mempool.space/block/000000000000000000009f93907a0dd83c080d5585cc7ec82c076d45f6d7c872) - 2024/03/20 +# v0.0.1 | [835444](https://mempool.space/block/000000000000000000009f93907a0dd83c080d5585cc7ec82c076d45f6d7c872) - 2024/03/20 -![Image of the Satonomics Web App version 0.0.X](./assets/v0.0.X.jpg) +![Image of the Satonomics Web App version 0.0.X](https://github.com/kibo-money/kibo/blob/main/_assets/v0.0.X.jpg) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a6c0043c3..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,8 +0,0 @@ -# Guidelines - -## Parser - -- Avoid floats as much as possible - - Use structs like `WAmount` and `Price` for calculations - - **Only** use `WAmount.to_btc()` when inserting or computing inside a dataset. It is **very** expensive. -- No `Arc`, `Rc`, `Mutex` even from third party libraries, they're slower diff --git a/parser/Cargo.lock b/Cargo.lock similarity index 60% rename from parser/Cargo.lock rename to Cargo.lock index 91514253c..e6fd1bc01 100644 --- a/parser/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -18,59 +18,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "ahash" -version = "0.8.11" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", - "zerocopy", + "memchr", ] [[package]] -name = "allocative" -version = "0.3.3" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082af274fd02beef17b7f0725a49ecafe6c075ef56cac9d6363eb3916a9817ae" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "allocative_derive", - "ctor", + "alloc-no-stdlib", ] [[package]] -name = "allocative_derive" -version = "0.3.3" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -83,36 +70,37 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -122,10 +110,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "assert-unchecked" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "7330592adf847ee2e3513587b4db2db410a0d751378654e7e993d9adcbe5c795" + +[[package]] +name = "async-compression" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] [[package]] name = "autocfg" @@ -133,6 +137,60 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -143,7 +201,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -165,10 +223,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "base64" -version = "0.22.1" +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] [[package]] name = "bech32" @@ -176,29 +238,11 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" -[[package]] -name = "bincode" -version = "2.0.0-rc.3" -source = "git+https://github.com/bincode-org/bincode.git#5a91c1210168b968b957a14b743cf134f2d20719" -dependencies = [ - "bincode_derive", - "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.0-rc.3" -source = "git+https://github.com/bincode-org/bincode.git#5a91c1210168b968b957a14b743cf134f2d20719" -dependencies = [ - "virtue", -] - [[package]] name = "bitcoin" -version = "0.32.3" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0032b0e8ead7074cda7fc4f034409607e3f03a6f71d66ade8a307f79b4d99e73" +checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ "base58ck", "bech32", @@ -223,9 +267,9 @@ dependencies = [ [[package]] name = "bitcoin-io" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" [[package]] name = "bitcoin-units" @@ -273,41 +317,156 @@ dependencies = [ ] [[package]] -name = "biter" -version = "0.1.1" +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "brk" +version = "0.0.0" dependencies = [ - "bitcoin", - "bitcoincore-rpc", - "crossbeam", - "derived-deref", - "rayon", - "serde", - "serde_json", + "brk_computer", + "brk_fetcher", + "brk_indexer", + "brk_logger", + "brk_parser", + "brk_server", ] [[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +name = "brk_cli" +version = "0.0.0" [[package]] -name = "bitflags" -version = "2.6.0" +name = "brk_computer" +version = "0.0.0" +dependencies = [ + "brk_fetcher", + "brk_indexer", + "brk_parser", + "byteview", + "color-eyre", + "derive_deref", + "fjall", + "hodor", + "storable_vec", + "zerocopy 0.8.20", +] + +[[package]] +name = "brk_fetcher" +version = "0.0.0" +dependencies = [ + "brk_indexer", + "brk_logger", + "color-eyre", + "derive_deref", + "jiff", + "log", + "minreq", + "serde", + "serde_json", + "storable_vec", + "zerocopy 0.8.20", +] + +[[package]] +name = "brk_indexer" +version = "0.0.0" +dependencies = [ + "bitcoin", + "brk_logger", + "brk_parser", + "byteview", + "color-eyre", + "derive_deref", + "fjall", + "hodor", + "jiff", + "log", + "rapidhash", + "rayon", + "rlimit", + "serde", + "serde_bytes", + "storable_vec", + "zerocopy 0.8.20", +] + +[[package]] +name = "brk_logger" +version = "0.0.0" +dependencies = [ + "color-eyre", + "env_logger", + "jiff", +] + +[[package]] +name = "brk_parser" +version = "0.0.0" +dependencies = [ + "bitcoin", + "bitcoincore-rpc", + "byteview", + "crossbeam", + "derive_deref", + "rayon", + "serde", + "serde_json", + "zerocopy 0.8.20", +] + +[[package]] +name = "brk_server" +version = "0.0.0" +dependencies = [ + "axum", + "brk_computer", + "brk_indexer", + "brk_logger", + "color-eyre", + "derive_deref", + "jiff", + "log", + "oxc", + "serde", + "serde_json", + "storable_vec", + "tokio", + "tower-http", +] + +[[package]] +name = "brotli" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytemuck" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +dependencies = [ + "allocator-api2", +] [[package]] name = "byteorder" @@ -317,15 +476,30 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + +[[package]] +name = "byteview" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4516a8561bff0598c45512f90ee04ed62cee2cb36839e650a0a0704d5f741f" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] [[package]] name = "cc" -version = "1.1.28" +version = "1.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "jobserver", "libc", @@ -344,61 +518,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets", -] - -[[package]] -name = "clap" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - [[package]] name = "color-eyre" version = "0.6.3" @@ -410,7 +529,7 @@ dependencies = [ "eyre", "indenter", "once_cell", - "owo-colors", + "owo-colors 3.5.0", "tracing-error", ] @@ -421,32 +540,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", - "owo-colors", + "owo-colors 3.5.0", "tracing-core", "tracing-error", ] [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] -name = "core-foundation" -version = "0.9.4" +name = "compact_str" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ - "core-foundation-sys", - "libc", + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "compare" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7" + +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] [[package]] name = "crossbeam" @@ -463,18 +601,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -491,28 +629,28 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" +name = "crossbeam-skiplist" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] [[package]] -name = "ctor" -version = "0.1.26" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "ctrlc" @@ -535,7 +673,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.10", + "parking_lot_core", ] [[package]] @@ -550,15 +688,10 @@ dependencies = [ ] [[package]] -name = "derived-deref" -version = "2.1.0" +name = "double-ended-peekable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805ef2023ccd65425743a91ecd11fc020979a0b01921db3104fb606d18a7b43e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] +checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57" [[package]] name = "either" @@ -567,47 +700,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] -name = "encoding_rs" -version = "0.8.34" +name = "enum_dispatch" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" dependencies = [ - "cfg-if", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", + "regex", ] [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ + "anstream", + "anstyle", "env_filter", + "humantime", "log", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -622,9 +762,42 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fjall" +version = "2.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e0fd3f3a8cbaa2b179ccc690ea0d37282d24787c06eab0dfd9137e1c4d4699" +dependencies = [ + "byteorder", + "byteview", + "dashmap", + "log", + "lsm-tree", + "path-absolutize", + "std-semaphore", + "tempfile", + "xxhash-rust", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.5", +] [[package]] name = "fnv" @@ -632,21 +805,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -656,16 +814,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -673,7 +821,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -682,12 +829,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-sink" version = "0.3.31" @@ -707,13 +848,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -724,7 +861,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -734,23 +883,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] -name = "h2" -version = "0.4.6" +name = "guardian" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" [[package]] name = "hashbrown" @@ -760,27 +896,12 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", +] [[package]] name = "hex-conservative" @@ -797,11 +918,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hodor" +version = "0.1.0" +dependencies = [ + "ctrlc", + "log", +] + [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -833,113 +962,55 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.4.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", - "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", - "socket2", "tokio", "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", ] [[package]] @@ -950,61 +1021,21 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] -name = "inferno" -version = "0.11.21" +name = "interval-heap" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" +checksum = "11274e5e8e89b8607cfedc2910b6626e998779b48a019151c7604d0adcb86ac6" dependencies = [ - "ahash", - "clap", - "crossbeam-channel", - "crossbeam-utils", - "dashmap", - "env_logger", - "indexmap", - "is-terminal", - "itoa", - "log", - "num-format", - "once_cell", - "quick-xml", - "rgb", - "str_stack", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", + "compare", ] [[package]] @@ -1015,18 +1046,47 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3590fea8e9e22d449600c9bbd481a8163bef223e4ff938e5f55899f8cf1adb93" +dependencies = [ + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" +dependencies = [ + "jiff-tzdb", +] [[package]] name = "jobserver" @@ -1037,22 +1097,13 @@ dependencies = [ "libc", ] -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "jsonrpc" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf" dependencies = [ - "base64 0.13.1", + "base64", "minreq", "serde", "serde_json", @@ -1066,15 +1117,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -1088,9 +1139,45 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "lsm-tree" +version = "2.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614a43954a8414dcca688f9d4c91f727860b55ffd9a029438b18f234ed33330f" +dependencies = [ + "byteorder", + "crossbeam-skiplist", + "double-ended-peekable", + "enum_dispatch", + "guardian", + "interval-heap", + "log", + "lz4_flex", + "path-absolutize", + "quick_cache", + "rustc-hash", + "self_cell", + "tempfile", + "value-log", + "varint-rs", + "xxhash-rust", +] + +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" @@ -1123,65 +1210,75 @@ dependencies = [ ] [[package]] -name = "minreq" -version = "2.12.0" +name = "miniz_oxide" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "minreq" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0c420feb01b9fb5061f8c8f452534361dd783756dcf38ec45191ce55e7a161" dependencies = [ "log", + "once_cell", + "rustls", + "rustls-webpki", "serde", "serde_json", + "webpki-roots", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[package]] -name = "num-format" -version = "0.4.4" +name = "nonmax" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "arrayvec", - "itoa", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", ] [[package]] @@ -1204,62 +1301,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] -name = "openssl" -version = "0.10.66" +name = "outref" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e7ccb95e240b7c9506a3d544f10d935e142cc90b0a1d56954fb44d89ad6b97" -dependencies = [ - "num-traits", -] +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "owo-colors" @@ -1268,28 +1318,353 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] -name = "parking_lot" -version = "0.11.2" +name = "owo-colors" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" + +[[package]] +name = "oxc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6be05b94a99c886e3c8f79c0330e746df6fe27f8440b498ea7a5eb2fee2f67" dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", + "oxc_allocator", + "oxc_ast", + "oxc_codegen", + "oxc_diagnostics", + "oxc_mangler", + "oxc_minifier", + "oxc_parser", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", ] [[package]] -name = "parking_lot_core" -version = "0.8.6" +name = "oxc-miette" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +checksum = "e03e63fd113c068b82d07c9c614b0b146c08a3ac0a4dface3ea1d1a9d14d549e" dependencies = [ "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "owo-colors 4.2.0", + "oxc-miette-derive", + "textwrap", + "thiserror", + "unicode-width 0.2.0", +] + +[[package]] +name = "oxc-miette-derive" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21f680e8c5f1900297d394627d495351b9e37761f7bbf90116bd5eeb6e80967" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "oxc_allocator" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf460405a383f3f7ac134a0e06afdb3525c8fa8f119453167f179ea6b12faaf6" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.2", + "rustc-hash", + "simdutf8", +] + +[[package]] +name = "oxc_ast" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b273094a3e96e84d3d8ce82190fd7113f70bb2a8dcb652c78b46df79fdd156cc" +dependencies = [ + "bitflags", + "cow-utils", + "nonmax", + "oxc_allocator", + "oxc_ast_macros", + "oxc_estree", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "oxc_ast_macros" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab1dff20655433e64452ee12a9c87dba5d307a47fc8e155b4e37d746d66b37dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "oxc_cfg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f289d7b40c97d1b7d2a8cf1528cd5af0f3f4e832dffdec20b6485b9d6240de0b" +dependencies = [ + "bitflags", + "itertools", + "nonmax", + "oxc_index", + "oxc_syntax", + "petgraph", + "rustc-hash", +] + +[[package]] +name = "oxc_codegen" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aa20c10288eacf1a8ec93981198c1428c7c9d0436ae7e0f0037a02e87535c94" +dependencies = [ + "assert-unchecked", + "bitflags", + "cow-utils", + "nonmax", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_index", + "oxc_semantic", + "oxc_sourcemap", + "oxc_span", + "oxc_syntax", + "rustc-hash", + "ryu-js", +] + +[[package]] +name = "oxc_data_structures" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e902c119eca30d0016d3d2aed31052d6e3ef1f594d71ca3fe193286771a8088d" +dependencies = [ + "assert-unchecked", + "ropey", +] + +[[package]] +name = "oxc_diagnostics" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640a5f40eb0e09725a5e9c90e88727272c999d9805403747e2140cab2a742b6e" +dependencies = [ + "cow-utils", + "oxc-miette", +] + +[[package]] +name = "oxc_ecmascript" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87543a59fd0d2d27102ccd8eee9f954564f7e82124992cc21975bbbe75851ce4" +dependencies = [ + "cow-utils", + "num-bigint", + "num-traits", + "oxc_ast", + "oxc_span", + "oxc_syntax", +] + +[[package]] +name = "oxc_estree" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1efeb3bb8ce6bced1e382e49d3978a2e5067cdc3881584f1cdef5aaf52d28b4" +dependencies = [ + "itoa", +] + +[[package]] +name = "oxc_index" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eca5d9726cd0a6e433debe003b7bc88b2ecad0bb6109f0cef7c55e692139a34" + +[[package]] +name = "oxc_mangler" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74396fefe8eb5a62b2d79023b468f3012d0daa25b4a6fdff64816ade625a6dfc" +dependencies = [ + "fixedbitset", + "itertools", + "oxc_allocator", + "oxc_ast", + "oxc_index", + "oxc_semantic", + "oxc_span", + "rustc-hash", +] + +[[package]] +name = "oxc_minifier" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc9099e8cb43a9362ffde2cfcb5d69bbbf87f0303c36d6209379b4f88f3d866" +dependencies = [ + "cow-utils", + "oxc_allocator", + "oxc_ast", + "oxc_codegen", + "oxc_data_structures", + "oxc_ecmascript", + "oxc_mangler", + "oxc_parser", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "oxc_traverse", + "rustc-hash", +] + +[[package]] +name = "oxc_parser" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f5195b60a980baf22e34c801c8a37901ee21149e523d2a2fc68111991dd0b6" +dependencies = [ + "assert-unchecked", + "bitflags", + "cow-utils", + "memchr", + "num-bigint", + "num-traits", + "oxc_allocator", + "oxc_ast", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_regular_expression", + "oxc_span", + "oxc_syntax", + "rustc-hash", + "seq-macro", +] + +[[package]] +name = "oxc_regular_expression" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b7199022f1e8a071a118737884cdf75bd3ba869471fd500f00dd4612a4faeb" +dependencies = [ + "oxc_allocator", + "oxc_ast_macros", + "oxc_diagnostics", + "oxc_estree", + "oxc_span", + "phf", + "rustc-hash", + "unicode-id-start", +] + +[[package]] +name = "oxc_semantic" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55da371ce5345676e602719ce048eb2343b3e7b1798c6275e06ea431917340af" +dependencies = [ + "assert-unchecked", + "itertools", + "oxc_allocator", + "oxc_ast", + "oxc_cfg", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_index", + "oxc_span", + "oxc_syntax", + "phf", + "rustc-hash", + "self_cell", +] + +[[package]] +name = "oxc_sourcemap" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb048fad7eee078b23dc3f3be7aba94eeba596c892c9255fc2646fba232a2ec" +dependencies = [ + "base64-simd", + "cfg-if", + "cow-utils", + "rustc-hash", + "serde", + "serde_json", +] + +[[package]] +name = "oxc_span" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3cbaec227f5bb91e52a04f9028915973cbb7522b123b41af2d3189eb1ce41d8" +dependencies = [ + "compact_str", + "oxc-miette", + "oxc_allocator", + "oxc_ast_macros", + "oxc_estree", +] + +[[package]] +name = "oxc_syntax" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91bd42fae0c0e6212ea704ed533df47748002abc0af5496911fdb09735028a77" +dependencies = [ + "assert-unchecked", + "bitflags", + "cow-utils", + "nonmax", + "oxc_allocator", + "oxc_ast_macros", + "oxc_estree", + "oxc_index", + "oxc_span", + "phf", + "rustc-hash", + "ryu-js", + "unicode-id-start", +] + +[[package]] +name = "oxc_traverse" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3558bebe882232c4c6fd5f28f08ce58586970c56b486a694b29c59b8b976a8" +dependencies = [ + "compact_str", + "itoa", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_ecmascript", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "rustc-hash", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", ] [[package]] @@ -1300,35 +1675,27 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall", "smallvec", "windows-targets", ] [[package]] -name = "parser" -version = "0.5.0" +name = "path-absolutize" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5" dependencies = [ - "allocative", - "bincode", - "bitcoin_hashes", - "biter", - "chrono", - "clap", - "color-eyre", - "ctrlc", - "derive_deref", - "inferno", - "itertools", - "ordered-float", - "rayon", - "reqwest", - "sanakirja", - "serde", - "serde_json", - "struct_iterable", - "toml", - "zstd", + "path-dedot", +] + +[[package]] +name = "path-dedot" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" +dependencies = [ + "once_cell", ] [[package]] @@ -1338,10 +1705,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "petgraph" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1355,38 +1774,54 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] -name = "quick-xml" -version = "0.26.0" +name = "quick_cache" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "3f67cfc9c723c39f3615eb0840b00c4cb9e2b068d2fa761a30d845ec91730a59" dependencies = [ - "memchr", + "equivalent", + "hashbrown 0.14.5", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1418,9 +1853,15 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] +[[package]] +name = "rapidhash" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9813f789f95ee4fe6b4d01834404d7cccacbc3f6c029343af910b3c2835eb9f1" + [[package]] name = "rayon" version = "1.10.0" @@ -1443,90 +1884,75 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] -name = "redox_syscall" -version = "0.5.7" +name = "regex" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "bitflags 2.6.0", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "reqwest" -version = "0.12.9" +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "rgb" -version = "0.8.50" +name = "regex-syntax" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" -version = "0.17.8" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + +[[package]] +name = "ropey" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" +dependencies = [ + "smallvec", + "str_indices", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1534,92 +1960,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rustix" -version = "0.38.37" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.14" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "once_cell", - "rustls-pki-types", + "log", + "ring", "rustls-webpki", - "subtle", - "zeroize", + "sct", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" - [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", - "rustls-pki-types", "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] -name = "sanakirja" -version = "1.4.3" +name = "ryu-js" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81aaf70d064e2122209f04d01fd91e8908e7a327b516236e1cbc0c3f34ac6d11" -dependencies = [ - "fs2", - "log", - "memmap2", - "parking_lot", - "sanakirja-core", - "serde", - "thiserror", -] - -[[package]] -name = "sanakirja-core" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8376db34ae3eac6e7bd91168bc638450073b708ce9fb46940de676f552238bf5" - -[[package]] -name = "schannel" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" -dependencies = [ - "windows-sys 0.59.0", -] +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" [[package]] name = "scopeguard" @@ -1627,6 +2024,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secp256k1" version = "0.29.1" @@ -1649,53 +2056,51 @@ dependencies = [ ] [[package]] -name = "security-framework" -version = "2.11.1" +name = "self_cell" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] -name = "security-framework-sys" -version = "2.12.0" +name = "seq-macro" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.214" +name = "serde_bytes" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", @@ -1704,11 +2109,12 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.8" +name = "serde_path_to_error" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ + "itoa", "serde", ] @@ -1740,75 +2146,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "slab" -version = "0.4.9" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ - "autocfg", + "libc", ] [[package]] -name = "smallvec" -version = "1.13.2" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] -name = "spin" -version = "0.9.8" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "str_stack" +name = "std-semaphore" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" +checksum = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e" [[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "struct_iterable" -version = "0.1.2" +name = "storable_vec" +version = "0.1.3" dependencies = [ - "struct_iterable_derive", - "struct_iterable_internal", + "memmap2", + "rayon", + "serde", + "serde_json", + "zerocopy 0.8.20", ] [[package]] -name = "struct_iterable_derive" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "struct_iterable_internal", - "syn 2.0.85", -] - -[[package]] -name = "struct_iterable_internal" -version = "0.1.1" - -[[package]] -name = "subtle" -version = "2.6.1" +name = "str_indices" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" [[package]] name = "syn" @@ -1823,9 +2230,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1834,65 +2241,53 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] -name = "thiserror" -version = "1.0.64" +name = "textwrap" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.1.14", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] @@ -1905,62 +2300,40 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.40.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-macros" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -1970,38 +2343,45 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.8.19" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "toml_datetime" -version = "0.6.8" +name = "tower-http" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ - "serde", + "async-compression", + "bitflags", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", ] [[package]] -name = "toml_edit" -version = "0.22.22" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" @@ -2011,19 +2391,20 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2031,9 +2412,9 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", @@ -2041,9 +2422,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "sharded-slab", "thread_local", @@ -2051,31 +2432,34 @@ dependencies = [ ] [[package]] -name = "try-lock" -version = "0.2.5" +name = "unicode-id-start" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] -name = "unicode-normalization" -version = "0.1.24" +name = "unicode-linebreak" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "untrusted" @@ -2083,23 +2467,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a88342087869553c259588a3ec9ca73ce9b2d538b7051ba5789ff236b6c129" - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "utf8parse" version = "0.2.2" @@ -2108,37 +2475,39 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "vcpkg" -version = "0.2.15" +name = "value-log" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "virtue" -version = "0.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7302ac74a033bf17b6e609ceec0f891ca9200d502d31f02dc7908d3d98767c9d" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "f2398750880b2b9770afbd1a3e299b9e859d6143c299867eb35fdf484b7625d3" dependencies = [ - "try-lock", + "byteorder", + "byteview", + "interval-heap", + "log", + "path-absolutize", + "quick_cache", + "rustc-hash", + "tempfile", + "xxhash-rust", ] +[[package]] +name = "varint-rs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2146,142 +2515,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.93" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", + "wit-bindgen-rt", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" +name = "webpki-roots" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.85", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "windows-sys" @@ -2366,14 +2612,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.6.20" +name = "wit-bindgen-rt" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "memchr", + "bitflags", ] +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "zerocopy" version = "0.7.35" @@ -2381,7 +2633,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive 0.8.20", ] [[package]] @@ -2392,38 +2653,43 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.98", ] [[package]] -name = "zeroize" -version = "1.8.1" +name = "zerocopy-derive" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..48a9425e5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[workspace] +members = ["crates/*"] +resolver = "2" +package.license = "MIT" +package.edition = "2024" +package.version = "0.0.0" + +[workspace.dependencies] +bitcoin = { version = "0.32.5", features = ["serde"] } +brk_computer = { version = "0", path = "crates/brk_computer" } +brk_fetcher = { version = "0", path = "crates/brk_fetcher" } +brk_indexer = { version = "0", path = "crates/brk_indexer" } +brk_parser = { version = "0", path = "crates/brk_parser" } +brk_logger = { version = "0", path = "crates/brk_logger" } +brk_server = { version = "0", path = "crates/brk_server" } +byteview = "0.5.4" +color-eyre = "0.6.3" +derive_deref = "1.1.1" +fjall = "2.6.5" +hodor = { version = "0", path = "crates/hodor" } +jiff = "0.2.1" +log = { version = "0.4.26" } +minreq = { version = "2.13.2", features = ["https", "serde_json"] } +rayon = "1.10.0" +serde = { version = "1.0.218", features = ["derive"] } +serde_bytes = "0.11.15" +serde_json = { version = "1.0.139", features = ["float_roundtrip"] } +storable_vec = { version = "0", path = "crates/storable_vec", features = [ + "json", +] } +zerocopy = { version = "0.8.20", features = ["derive"] } diff --git a/LICENSE.md b/LICENSE.md index 77197e6d3..4827b5007 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 kibō +Copyright (c) 2025 kibō.money Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c2e0ce2cb..7aecf84a9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ - - - kibō + + + kibō @@ -63,17 +63,18 @@ Please open an issue if you want to add another instance ### Requirements - At least 16 GB of RAM -- 1 TB of free space (will use 70% of that without defragmentation and 40% after) -- A running instance of bitcoin-core with: - - `-txindex=1` - - `-blocksxor=0` - - RPC credentials - - Example: `bitcoind -datadir="$HOME/.bitcoin" -blocksonly -txindex=1 -blocksxor=0` +- A disk with 1 TB of free space (will use between 40% to 80% depending on several things) + - Recommended: Rated at 3 GB/s (Thunderbolt 4 speed) +- A running instance of bitcoin-core + - Example: `bitcoind -datadir="$HOME/.bitcoin" -blocksonly` - Git +- Unix based operating system (Mac OS or Linux) + - Ubuntu users need to install `open-ssl` via `sudo apt install libssl-dev pkg-config` + - Mac OS: + - Disable Spotlight or exclude the `--kibodir` folder from it + - Don't use Time Machine or exclude the `--kibodir` folder (especially needed for local snapshots) -### Manual - -_Mac OS and Linux only, Windows is unsupported_ +### Build First we need to install Rust (https://www.rust-lang.org/tools/install) @@ -81,74 +82,33 @@ First we need to install Rust (https://www.rust-lang.org/tools/install) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -If you already had Rust installed you could update it just in case +If you already had Rust installed you could update it ```bash rustup update ``` -> If you're on Ubuntu you'll probably also need to install `open-ssl` with -> -> ```bash -> sudo apt install libssl-dev pkg-config -> ``` - -Optionally, you can also install `cargo-watch` for the server to automatically restart it on file change, which will be triggered by new code and new datasets from the parser (https://github.com/watchexec/cargo-watch?tab=readme-ov-file#install) - -```bash -cargo install cargo-watch --locked -``` - -Then you need to choose a path where all files related to **kibō** will live +Then you need to choose a path where the project will reside and then clone it ```bash cd ??? -``` - -We can now clone the repository - -```bash git clone https://github.com/kibo-money/kibo.git +cd kibo ``` -In a new terminal, go to the `parser`'s folder of the repository +If it's your first time running kibo, it will need several information such as: -```bash -cd ???/kibo/parser -``` +- `--bitcoindir PATH`: path to bitcoin core data directory, `???/bitcoin` +- `--kibodir PATH`: path to kibo outputs, if you have enough space on your main disk `~/.kibo` is fine -Now we can finally start by running the parser, you need to use the `./run.sh` script instead of `cargo run -r` as we need to set various system variables for the program to run smoothly +Everything will be saved at `~/.kibo/config.toml`, which will allow you to simply run `cargo run -r` next time -For the first launch, the parser will need several information such as: - -- `--datadir`: which is bitcoin data directory path, prefer `$HOME` to `~` as the latter might not work - -Optionally you can also specify: - -- `--rpccookiefile`: the path to the cookie file if not default -- `--rpcuser`: the username of the RPC credentials to talk to the bitcoin server if set -- `--rpcpassword`: the password of the RPC credentials if set -- `--rpcconnect`: if the bitcoin core server's IP is different than `localhost` -- `--rpcport`: if the port is different than `8332` - -Everything will be saved in a `config.toml` file, which will allow you to simply run `./run.sh` next time +If you need more options please run `cargo run -r --help` to see what parameters are available. Here's an example ```bash -./run.sh --datadir=$HOME/Developer/bitcoin -``` - -In a **new** terminal, go to the `server`'s folder of the repository - -```bash -cd ???/kibo/server -``` - -And start it also with the `run.sh` script instead of `cargo run -r` - -```bash -./run.sh +cargo run -r -- --bitcoindir=~/Developer/bitcoin --kibodir=~/.kibo ``` Then the easiest to let others access your server is to use `cloudflared` which will also cache requests. For more information go to: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/ diff --git a/assets/dove-orange.svg b/_assets/dove-orange.svg similarity index 100% rename from assets/dove-orange.svg rename to _assets/dove-orange.svg diff --git a/assets/dove-white.svg b/_assets/dove-white.svg similarity index 100% rename from assets/dove-white.svg rename to _assets/dove-white.svg diff --git a/assets/logo-dove-dark.svg b/_assets/logo-dove-dark.svg similarity index 100% rename from assets/logo-dove-dark.svg rename to _assets/logo-dove-dark.svg diff --git a/assets/logo-dove-light.svg b/_assets/logo-dove-light.svg similarity index 100% rename from assets/logo-dove-light.svg rename to _assets/logo-dove-light.svg diff --git a/assets/logo-dove-orange.svg b/_assets/logo-dove-orange.svg similarity index 100% rename from assets/logo-dove-orange.svg rename to _assets/logo-dove-orange.svg diff --git a/assets/logo-full-dark.svg b/_assets/logo-full-dark.svg similarity index 100% rename from assets/logo-full-dark.svg rename to _assets/logo-full-dark.svg diff --git a/assets/logo-full-light.svg b/_assets/logo-full-light.svg similarity index 100% rename from assets/logo-full-light.svg rename to _assets/logo-full-light.svg diff --git a/assets/logo-icon.svg b/_assets/logo-icon.svg similarity index 100% rename from assets/logo-icon.svg rename to _assets/logo-icon.svg diff --git a/assets/logo-long-text-dark.svg b/_assets/logo-long-text-dark.svg similarity index 100% rename from assets/logo-long-text-dark.svg rename to _assets/logo-long-text-dark.svg diff --git a/assets/logo-long-text-light.svg b/_assets/logo-long-text-light.svg similarity index 100% rename from assets/logo-long-text-light.svg rename to _assets/logo-long-text-light.svg diff --git a/assets/logo-short-text-dark.svg b/_assets/logo-short-text-dark.svg similarity index 100% rename from assets/logo-short-text-dark.svg rename to _assets/logo-short-text-dark.svg diff --git a/assets/logo-short-text-light.svg b/_assets/logo-short-text-light.svg similarity index 100% rename from assets/logo-short-text-light.svg rename to _assets/logo-short-text-light.svg diff --git a/assets/logo-stamp-orange.svg b/_assets/logo-stamp-orange.svg similarity index 100% rename from assets/logo-stamp-orange.svg rename to _assets/logo-stamp-orange.svg diff --git a/assets/logo-stamp.svg b/_assets/logo-stamp.svg similarity index 100% rename from assets/logo-stamp.svg rename to _assets/logo-stamp.svg diff --git a/assets/v0.0.X.jpg b/_assets/v0.0.X.jpg similarity index 100% rename from assets/v0.0.X.jpg rename to _assets/v0.0.X.jpg diff --git a/assets/v0.1.0.jpg b/_assets/v0.1.0.jpg similarity index 100% rename from assets/v0.1.0.jpg rename to _assets/v0.1.0.jpg diff --git a/assets/v0.1.1.jpg b/_assets/v0.1.1.jpg similarity index 100% rename from assets/v0.1.1.jpg rename to _assets/v0.1.1.jpg diff --git a/assets/v0.2.0.jpg b/_assets/v0.2.0.jpg similarity index 100% rename from assets/v0.2.0.jpg rename to _assets/v0.2.0.jpg diff --git a/assets/v0.3.0.jpg b/_assets/v0.3.0.jpg similarity index 100% rename from assets/v0.3.0.jpg rename to _assets/v0.3.0.jpg diff --git a/assets/v0.4.0.jpg b/_assets/v0.4.0.jpg similarity index 100% rename from assets/v0.4.0.jpg rename to _assets/v0.4.0.jpg diff --git a/assets/v0.5.0.jpg b/_assets/v0.5.0.jpg similarity index 100% rename from assets/v0.5.0.jpg rename to _assets/v0.5.0.jpg diff --git a/docker/Dockerfile b/_docker/Dockerfile similarity index 100% rename from docker/Dockerfile rename to _docker/Dockerfile diff --git a/docker/build.sh b/_docker/build.sh similarity index 100% rename from docker/build.sh rename to _docker/build.sh diff --git a/docker/cmd.sh b/_docker/cmd.sh similarity index 100% rename from docker/cmd.sh rename to _docker/cmd.sh diff --git a/docker/run.sh b/_docker/run.sh similarity index 100% rename from docker/run.sh rename to _docker/run.sh diff --git a/_src/main.rs b/_src/main.rs new file mode 100644 index 000000000..3dfe00316 --- /dev/null +++ b/_src/main.rs @@ -0,0 +1,67 @@ +use brk_parser::bitcoincore_rpc::Client; +use log::info; +use rlimit::{Resource, getrlimit, setrlimit}; + +mod io; +mod parser; +mod server; +mod structs; +mod utils; + +use brk_parser::Datasets; +use server::api::structs::Routes; +use structs::{Config, Exit}; +use utils::init_log; + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + init_log(); + + let (_, nofile_limit) = getrlimit(Resource::NOFILE).unwrap(); + setrlimit(Resource::NOFILE, 138_240, nofile_limit)?; + + std::thread::Builder::new() + .stack_size(getrlimit(Resource::STACK).unwrap().1 as usize) + .spawn(|| -> color_eyre::Result<()> { + let exit = Exit::new(); + + let config = Config::import()?; + + info!("Starting..."); + + let rpc = Client::from(&config); + + let routes = Routes::build(&Datasets::import(&config)?, &config); + + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + let run_parser = config.parser(); + let run_server = config.server(); + + let config_clone = config.clone(); + let handle = tokio::spawn(async move { + if run_server { + server::main(routes, config_clone).await.unwrap(); + } else { + info!("Skipping server"); + } + }); + + if run_parser { + parser::main(&config, &rpc, &exit)?; + } else { + info!("Skipping parser"); + } + + handle.await?; + + Ok(()) + }) + })? + .join() + .unwrap() +} diff --git a/_src/parser/actions/export.rs b/_src/parser/actions/export.rs new file mode 100644 index 000000000..5a82e71a3 --- /dev/null +++ b/_src/parser/actions/export.rs @@ -0,0 +1,63 @@ +use log::info; + +use crate::{ + parser::{databases::Databases, datasets::Datasets, states::States}, + structs::{Config, Date, Exit, Height}, + utils::time, +}; + +pub struct ExportedData<'a> { + pub config: &'a Config, + pub databases: Option<&'a mut Databases>, + pub datasets: &'a mut Datasets, + pub date: Date, + pub defragment: bool, + pub exit: Exit, + pub height: Height, + pub states: Option<&'a States>, +} + +pub fn export( + ExportedData { + config, + databases, + datasets, + date, + defragment, + exit, + height, + states, + }: ExportedData, +) -> color_eyre::Result<()> { + if exit.active() { + info!("Exit in progress, skipping export"); + return Ok(()); + } + + exit.block(); + + let text = if defragment { + "Exporting and defragmenting..." + } else { + "Exporting..." + }; + info!("{text}"); + + time("Finished export", || -> color_eyre::Result<()> { + datasets.export(config, height)?; + + if let Some(databases) = databases { + databases.export(height, date, defragment)?; + } + + if let Some(states) = states { + states.export(config)?; + } + + Ok(()) + })?; + + exit.unblock(); + + Ok(()) +} diff --git a/parser/src/actions/iter_blocks.rs b/_src/parser/actions/iter_blocks.rs similarity index 62% rename from parser/src/actions/iter_blocks.rs rename to _src/parser/actions/iter_blocks.rs index aaf46e35e..bb8eb84be 100644 --- a/parser/src/actions/iter_blocks.rs +++ b/_src/parser/actions/iter_blocks.rs @@ -1,70 +1,56 @@ use std::{collections::BTreeSet, time::Instant}; +use brk_parser::bitcoincore_rpc::Client; +use chrono::Datelike; use export::ExportedData; use itertools::Itertools; +use log::info; use parse::ParseData; use crate::{ - actions::{export, find_first_inserted_unsafe_height, parse}, - create_rpc, - databases::Databases, - datasets::{AllDatasets, ComputeData}, - io::OUTPUTS_FOLDER_PATH, - states::{AddressCohortsDurableStates, States, UTXOCohortsDurableStates}, - structs::{DateData, MapKey, Timestamp}, - utils::{generate_allocation_files, log, time}, - Config, Exit, Height, + parser::{ + actions::{export, find_first_inserted_unsafe_height, parse}, + databases::Databases, + datasets::{ComputeData, Datasets}, + states::{AddressCohortsDurableStates, States, UTXOCohortsDurableStates}, + }, + structs::{Config, DateData, DisplayInstant, Exit, Height, MapKey, Timestamp}, + utils::{generate_allocation_files, time}, }; pub fn iter_blocks( - config: &mut Config, - rpc: &biter::bitcoincore_rpc::Client, + config: &Config, + rpc: &Client, approx_block_count: usize, exit: Exit, + databases: &mut Databases, + datasets: &mut Datasets, ) -> color_eyre::Result<()> { - log("Starting..."); + let mut states = States::import(config).unwrap_or_default(); - let mut datasets = AllDatasets::import(config)?; + info!("Imported states"); - log("Imported datasets"); - - let mut databases = Databases::import(); - - if config.first_defragment() { - databases.defragment(&exit); - config.disable_defragment(); - } - - log("Imported databases"); - - let mut states = States::import().unwrap_or_default(); - - log("Imported states"); - - let first_unsafe_heights = - find_first_inserted_unsafe_height(&mut states, &mut databases, &mut datasets); + let first_unsafe_heights = find_first_inserted_unsafe_height(&mut states, databases, datasets, config); let mut height = first_unsafe_heights.min(); - log(&format!("Starting parsing at height: {height}")); + info!("Starting parsing at height: {height}"); let mut next_block_opt = None; let mut blocks_loop_date = None; + let mut next_date_opt; let block_receiver = biter::new( - config.datadir.as_ref().unwrap(), - OUTPUTS_FOLDER_PATH, + &config.path_bitcoindir(), Some(height.to_usize()), None, - create_rpc(config).unwrap(), + Client::from(config), ); let mut block_iter = block_receiver.iter(); 'parsing: loop { - let instant = Instant::now(); - let mut processed_heights = BTreeSet::new(); let mut processed_dates = BTreeSet::new(); @@ -75,15 +61,15 @@ pub fn iter_blocks( blocks_loop_date.take(); } + let instant = Instant::now(); + 'blocks: loop { let current_block_opt = next_block_opt.take().or_else(|| block_iter.next()); next_block_opt = block_iter.next(); - if let Some((_current_block_height, current_block, _current_block_hash)) = - current_block_opt - { - let timestamp = Timestamp::wrap(current_block.header.time); + if let Some((_current_block_height, current_block, _current_block_hash)) = current_block_opt { + let timestamp = Timestamp::from(current_block.header.time); let current_block_date = timestamp.to_date(); let current_block_height: Height = height + blocks_loop_i; @@ -93,16 +79,12 @@ pub fn iter_blocks( panic!() } - let next_block_date = next_block_opt.as_ref().map(|(_, next_block, _)| { - Timestamp::wrap(next_block.header.time).to_date() - }); + next_date_opt = next_block_opt + .as_ref() + .map(|(_, next_block, _)| Timestamp::from(next_block.header.time).to_date()); // Always run for the first block of the loop if blocks_loop_date.is_none() { - log(&format!( - "Processing {current_block_date} (height: {height})..." - )); - blocks_loop_date.replace(current_block_date); if states @@ -111,9 +93,7 @@ pub fn iter_blocks( .map(|date_data| *date_data.date < *current_block_date) .unwrap_or(true) { - states - .date_data_vec - .push(DateData::new(current_block_date, vec![])); + states.date_data_vec.push(DateData::new(current_block_date, vec![])); } processed_dates.insert(current_block_date); @@ -125,17 +105,15 @@ pub fn iter_blocks( panic!("current block should always have the same date as the current blocks loop"); } - let is_date_last_block = next_block_date + let is_date_last_block = next_date_opt // Do NOT change `blocks_loop_date` to `current_block_date` !!! .map_or(true, |next_block_date| blocks_loop_date < next_block_date); processed_heights.insert(current_block_height); if first_unsafe_heights.inserted <= current_block_height { - let compute_addresses = databases.check_if_needs_to_compute_addresses( - current_block_height, - blocks_loop_date, - ); + let compute_addresses = + databases.check_if_needs_to_compute_addresses(current_block_height, blocks_loop_date); if states.address_cohorts_durable_states.is_none() && (compute_addresses @@ -143,10 +121,9 @@ pub fn iter_blocks( .address .needs_durable_states(current_block_height, current_block_date)) { - states.address_cohorts_durable_states = - Some(AddressCohortsDurableStates::init( - &mut databases.address_index_to_address_data, - )); + states.address_cohorts_durable_states = Some(AddressCohortsDurableStates::init( + &mut databases.address_index_to_address_data, + )); } if states.utxo_cohorts_durable_states.is_none() @@ -159,16 +136,17 @@ pub fn iter_blocks( } parse(ParseData { - rpc, block: current_block, block_index: blocks_loop_i, compute_addresses, - databases: &mut databases, - datasets: &mut datasets, + config, + databases, + datasets, date: blocks_loop_date, first_date_height: height, height: current_block_height, is_date_last_block, + rpc, states: &mut states, }); } @@ -176,15 +154,16 @@ pub fn iter_blocks( blocks_loop_i += 1; if is_date_last_block { + info!( + "Processed {current_block_date} ({height} - {current_block_height}) {}", + instant.display() + ); + height += blocks_loop_i; - let is_check_point = next_block_date - .as_ref() - .map_or(true, |date| date.is_first_of_month()); + let is_check_point = next_date_opt.as_ref().map_or(true, |date| date.is_first_of_month()); - let ran_for_at_least_a_minute = instant.elapsed().as_secs() >= 60; - - if (is_check_point && ran_for_at_least_a_minute) + if (is_check_point && instant.elapsed().as_secs() >= 1) || height.is_close_to_end(approx_block_count) { break 'days; @@ -201,14 +180,9 @@ pub fn iter_blocks( // Don't remember why -1 let last_height = height - 1_u32; - log(&format!( - "Parsing group took {} seconds (last height: {last_height})\n", - instant.elapsed().as_secs_f32(), - )); - if first_unsafe_heights.computed <= last_height { - log("Computing datasets..."); - time("Computing datasets", || { + info!("Computing datasets..."); + time("Computed datasets", || { let dates = processed_dates.into_iter().collect_vec(); let heights = processed_heights.into_iter().collect_vec(); @@ -223,25 +197,31 @@ pub fn iter_blocks( if !config.dry_run() { let is_safe = height.is_safe(approx_block_count); + let defragment = is_safe + && next_date_opt.is_some_and(|date| { + (date.year() >= 2020 && date.is_january() || date.year() >= 2022 && date.is_july()) + && date.is_first_of_month() + }); + export(ExportedData { - databases: is_safe.then_some(&mut databases), - datasets: &mut datasets, + config, + databases: is_safe.then_some(databases), + datasets, date: blocks_loop_date.unwrap(), + defragment, height: last_height, states: is_safe.then_some(&states), exit: exit.clone(), })?; if config.record_ram_usage() { - time("Exporing allocation files", || { - generate_allocation_files(&datasets, &databases, &states, last_height) + time("Exporting allocation files", || { + generate_allocation_files(datasets, databases, &states, last_height) })?; } } else { - log("Skipping export"); + info!("Skipping export"); } - - println!(); } Ok(()) diff --git a/parser/src/actions/min_height.rs b/_src/parser/actions/min_height.rs similarity index 82% rename from parser/src/actions/min_height.rs rename to _src/parser/actions/min_height.rs index 953c1ddf1..e71b6ae50 100644 --- a/parser/src/actions/min_height.rs +++ b/_src/parser/actions/min_height.rs @@ -1,9 +1,12 @@ +use log::info; + use crate::{ - databases::Databases, - datasets::{AllDatasets, AnyDatasets}, - states::States, - structs::Height, - utils::log, + parser::{ + databases::Databases, + datasets::{AnyDatasets, Datasets}, + states::States, + }, + structs::{Config, Height}, }; #[derive(Default, Debug)] @@ -21,7 +24,8 @@ impl Heights { pub fn find_first_inserted_unsafe_height( states: &mut States, databases: &mut Databases, - datasets: &mut AllDatasets, + datasets: &mut Datasets, + config: &Config, ) -> Heights { let min_initial_inserted_last_address_height = datasets .address @@ -51,7 +55,7 @@ pub fn find_first_inserted_unsafe_height( .map(|date_data| date_data.date) .and_then(|last_safe_date| { if !usable_databases { - log("Unusable databases"); + info!("Unusable databases"); return None; } @@ -61,8 +65,8 @@ pub fn find_first_inserted_unsafe_height( let min_datasets_inserted_last_height = datasets_min_initial_states.inserted.last_height; let min_datasets_inserted_last_date = datasets_min_initial_states.inserted.last_date; - log(&format!("min_datasets_inserted_last_height: {:?}", min_datasets_inserted_last_height)); - log(&format!("min_datasets_inserted_last_date: {:?}", min_datasets_inserted_last_date)); + info!("min_datasets_inserted_last_height: {:?}", min_datasets_inserted_last_height); + info!("min_datasets_inserted_last_date: {:?}", min_datasets_inserted_last_date); let inserted_last_date_is_older_than_saved_state = min_datasets_inserted_last_date.map_or(true, |min_datasets_last_date| min_datasets_last_date < last_safe_date); @@ -80,7 +84,7 @@ pub fn find_first_inserted_unsafe_height( let inserted_heights_and_dates_are_out_of_sync = min_datasets_inserted_last_height.map_or(true, |min_datasets_inserted_last_height| min_datasets_inserted_last_height < last_safe_height); if inserted_heights_and_dates_are_out_of_sync { - log(&format!("last_safe_height ({last_safe_height}) > min_datasets_height ({min_datasets_inserted_last_height:?})")); + info!("last_safe_height ({last_safe_height}) > min_datasets_height ({min_datasets_inserted_last_height:?})"); None } else { @@ -108,18 +112,13 @@ pub fn find_first_inserted_unsafe_height( ) }) .unwrap_or_else(|| { - log("Starting over..."); + info!("Starting over..."); let include_addresses = !usable_databases || min_initial_inserted_last_address_date.is_none() || min_initial_inserted_last_address_height.is_none(); - // if include_addresses { - // dbg!(include_addresses); - // panic!(""); - // } - - states.reset(include_addresses); + states.reset(config, include_addresses); databases.reset(include_addresses); diff --git a/parser/src/actions/mod.rs b/_src/parser/actions/mod.rs similarity index 100% rename from parser/src/actions/mod.rs rename to _src/parser/actions/mod.rs diff --git a/parser/src/actions/parse.rs b/_src/parser/actions/parse.rs similarity index 52% rename from parser/src/actions/parse.rs rename to _src/parser/actions/parse.rs index 2199a5c3b..d601ac62c 100644 --- a/parser/src/actions/parse.rs +++ b/_src/parser/actions/parse.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, ops::ControlFlow, thread}; -use biter::{ +use brk_parser::{ bitcoin::{Block, Txid}, bitcoincore_rpc::RpcApi, }; @@ -9,17 +9,19 @@ use itertools::Itertools; use rayon::prelude::*; use crate::{ - databases::{ - AddressIndexToAddressData, AddressIndexToEmptyAddressData, AddressToAddressIndex, - Databases, TxidToTxData, TxoutIndexToAddressIndex, TxoutIndexToAmount, - }, - datasets::{AllDatasets, InsertData}, - states::{ - AddressCohortsInputStates, AddressCohortsOutputStates, AddressCohortsRealizedStates, - States, UTXOCohortsOneShotStates, UTXOCohortsSentStates, + parser::{ + databases::{ + AddressIndexToAddressData, AddressIndexToEmptyAddressData, AddressToAddressIndex, Databases, TxidToTxData, + TxoutIndexToAddressIndex, TxoutIndexToAmount, + }, + datasets::{Datasets, InsertData}, + states::{ + AddressCohortsInputStates, AddressCohortsOutputStates, AddressCohortsRealizedStates, States, + UTXOCohortsOneShotStates, UTXOCohortsSentStates, + }, }, structs::{ - Address, AddressData, AddressRealizedData, Amount, BlockData, BlockPath, Counter, Date, + Address, AddressData, AddressRealizedData, Amount, BlockData, BlockPath, Config, Counter, Date, EmptyAddressData, Height, PartialTxoutData, Price, SentData, Timestamp, TxData, TxoutIndex, }, }; @@ -28,9 +30,10 @@ pub struct ParseData<'a> { // pub bitcoin_cli: &'a BitcoinCli, pub block: Block, pub block_index: usize, + pub config: &'a Config, pub compute_addresses: bool, pub databases: &'a mut Databases, - pub datasets: &'a mut AllDatasets, + pub datasets: &'a mut Datasets, pub date: Date, pub first_date_height: Height, pub height: Height, @@ -43,6 +46,7 @@ pub fn parse( ParseData { block, block_index, + config, compute_addresses, databases, datasets, @@ -56,7 +60,7 @@ pub fn parse( ) { // log(&format!("{height}")); - let timestamp = Timestamp::wrap(block.header.time); + let timestamp = Timestamp::from(block.header.time); // If false, expect that the code is flawless // or create a 0 value txid database @@ -72,7 +76,7 @@ pub fn parse( let block_price = Price::from_dollar( datasets .price - .get_height_ohlc(height, timestamp, previous_timestamp) + .get_height_ohlc(height, timestamp, previous_timestamp, config) .unwrap_or_else(|_| panic!("Expect {height} to have a price")) .close as f64, ); @@ -106,8 +110,7 @@ pub fn parse( let mut block_path_to_sent_data: BTreeMap = BTreeMap::default(); // let mut received_data: ReceivedData = ReceivedData::default(); - let mut address_index_to_address_realized_data: BTreeMap = - BTreeMap::default(); + let mut address_index_to_address_realized_data: BTreeMap = BTreeMap::default(); let mut coinbase = Amount::ZERO; let mut satblocks_destroyed = Amount::ZERO; @@ -166,263 +169,228 @@ pub fn parse( ) }); - block - .txdata - .iter() - .enumerate() - .try_for_each(|(block_tx_index, tx)| { - let txid = tx.compute_txid(); - let tx_index = databases.txid_to_tx_data.metadata.serial as u32; + block.txdata.iter().enumerate().try_for_each(|(block_tx_index, tx)| { + let txid = tx.compute_txid(); + let tx_index = databases.txid_to_tx_data.metadata.serial as u32; - transaction_count += 1; + transaction_count += 1; - // -- - // outputs - // --- + // -- + // outputs + // --- - let mut utxos = BTreeMap::new(); - let mut spendable_amount = Amount::ZERO; + let mut utxos = BTreeMap::new(); + let mut spendable_amount = Amount::ZERO; - let is_coinbase = tx.is_coinbase(); + let is_coinbase = tx.is_coinbase(); - if is_coinbase != (block_tx_index == 0) { - unreachable!(); - } + if is_coinbase != (block_tx_index == 0) { + unreachable!(); + } - let mut inputs_sum = Amount::ZERO; - let mut outputs_sum = Amount::ZERO; + let mut inputs_sum = Amount::ZERO; + let mut outputs_sum = Amount::ZERO; - let last_block = states.date_data_vec.last_mut_block().unwrap(); + let last_block = states.date_data_vec.last_mut_block().unwrap(); - // Before `input` to cover outputs being used in the same block as inputs - tx.output - .iter() - .enumerate() - .filter_map(|(vout, tx_out)| { - if vout > (u16::MAX as usize) { - panic!("vout can indeed be bigger than u16::MAX !"); - } + // Before `input` to cover outputs being used in the same block as inputs + tx.output + .iter() + .enumerate() + .filter_map(|(vout, tx_out)| { + if vout > (u16::MAX as usize) { + panic!("vout can indeed be bigger than u16::MAX !"); + } - let amount = Amount::wrap(tx_out.value); + let amount = Amount::wrap(tx_out.value); - if is_coinbase { - coinbase += amount; - } else { - outputs_sum += amount; - } + if is_coinbase { + coinbase += amount; + } else { + outputs_sum += amount; + } - partial_txout_data_vec - .pop() - .unwrap() - // None if not worth parsing (empty/op_return/...) - .map(|partial_txout_data| (vout, partial_txout_data)) - }) - .for_each(|(vout, partial_txout_data)| { - let vout = vout as u16; + partial_txout_data_vec + .pop() + .unwrap() + // None if not worth parsing (empty/op_return/...) + .map(|partial_txout_data| (vout, partial_txout_data)) + }) + .for_each(|(vout, partial_txout_data)| { + let vout = vout as u16; - let txout_index = TxoutIndex::new(tx_index, vout); + let txout_index = TxoutIndex::new(tx_index, vout); - let PartialTxoutData { - address, - address_index_opt, - amount, - } = partial_txout_data; + let PartialTxoutData { + address, + address_index_opt, + amount, + } = partial_txout_data; - spendable_amount += amount; + spendable_amount += amount; - last_block.receive(amount); + last_block.receive(amount); - utxos.insert(vout, amount); + utxos.insert(vout, amount); + + databases.txout_index_to_amount.insert_to_ram(txout_index, amount); + + if compute_addresses { + let address = address.unwrap(); + + let address_index_to_address_data = address_index_to_address_data.as_mut().unwrap(); + + let (address_data, address_index) = { + if let Some(address_index) = address_index_opt + .or_else(|| databases.address_to_address_index.get_from_ram(&address).cloned()) + { + let address_data = address_index_to_address_data.get_mut(&address_index).unwrap(); + + (address_data, address_index) + } else { + let address_index = databases.address_to_address_index.metadata.serial as u32; + + let address_type = address.to_type(); + + if let Some(previous) = databases.address_to_address_index.insert(address, address_index) { + dbg!(previous); + panic!("address #{address_index} shouldn't be present during put"); + } + + // Checked new + let address_data = address_index_to_address_data + .entry(address_index) + .and_modify(|_| { + panic!("Shouldn't exist"); + }) + // Will always insert, it's to avoid insert + get + .or_insert(AddressData::new(address_type)); + + (address_data, address_index) + } + }; + + // MUST be before received ! + let address_realized_data = address_index_to_address_realized_data + .entry(address_index) + .or_insert_with(|| AddressRealizedData::default(address_data)); + + address_data.receive(amount, block_price); + + address_realized_data.receive(amount); databases - .txout_index_to_amount - .unsafe_insert(txout_index, amount); + .txout_index_to_address_index + .insert_to_ram(txout_index, address_index); + } + }); - if compute_addresses { - let address = address.unwrap(); + if !utxos.is_empty() { + databases.txid_to_tx_data.insert( + &txid, + TxData::new( + tx_index, + BlockPath::new(date_index as u16, block_index as u16), + utxos.len() as u16, + ), + ); + } - let address_index_to_address_data = - address_index_to_address_data.as_mut().unwrap(); + // --- + // inputs + // --- - let (address_data, address_index) = { - if let Some(address_index) = address_index_opt.or_else(|| { - databases - .address_to_address_index - .unsafe_get_from_puts(&address) - .cloned() - }) { - let address_data = address_index_to_address_data - .get_mut(&address_index) - .unwrap(); + if !is_coinbase { + tx.input.iter().try_for_each(|txin| { + let outpoint = txin.previous_output; + let input_txid = outpoint.txid; + let input_vout = outpoint.vout; - (address_data, address_index) - } else { - let address_index = - databases.address_to_address_index.metadata.serial as u32; + let remove_tx_data_from_cached_puts = { + let mut is_tx_data_from_cached_puts = false; - let address_type = address.to_type(); + let input_tx_data = txid_to_tx_data.get_mut(&input_txid).unwrap().as_mut().or_else(|| { + is_tx_data_from_cached_puts = true; - if let Some(previous) = databases - .address_to_address_index - .insert(address, address_index) - { - dbg!(previous); - panic!( - "address #{address_index} shouldn't be present during put" - ); - } + databases.txid_to_tx_data.get_mut_from_ram(&input_txid) + }); - // Checked new - let address_data = address_index_to_address_data - .entry(address_index) - .and_modify(|_| { - panic!("Shouldn't exist"); - }) - // Will always insert, it's to avoid insert + get - .or_insert(AddressData::new(address_type)); + // Can be none because 0 sats inputs happen + // https://mempool.space/tx/f329e55c2de9b821356e6f2c4bba923ea7030cad61120f5ced5d4429f5c86fda#vin=27 - (address_data, address_index) - } - }; + if input_tx_data.is_none() { + if !enable_check_if_txout_value_is_zero_in_db + || rpc + .get_raw_transaction(&input_txid, None) + .unwrap() + .output + .get(input_vout as usize) + .unwrap() + .value + .to_sat() + == 0 + { + return ControlFlow::Continue::<()>(()); + } - // MUST be before received ! - let address_realized_data = address_index_to_address_realized_data - .entry(address_index) - .or_insert_with(|| AddressRealizedData::default(address_data)); - - address_data.receive(amount, block_price); - - address_realized_data.receive(amount); - - databases - .txout_index_to_address_index - .unsafe_insert(txout_index, address_index); + dbg!((input_txid, txid, tx_index, input_vout)); + panic!("Txid to be in txid_to_tx_data"); } - }); - if !utxos.is_empty() { - databases.txid_to_tx_data.insert( - &txid, - TxData::new( - tx_index, - BlockPath::new(date_index as u16, block_index as u16), - utxos.len() as u16, - ), - ); - } + let input_tx_data = input_tx_data.unwrap(); + let input_tx_index = input_tx_data.index; + let input_vout = input_vout as u16; + let input_txout_index = TxoutIndex::new(input_tx_index, input_vout); - // --- - // inputs - // --- + // if input_tx_index == 2516 || input_tx_index == 2490 { + // dbg!(input_tx_index, &input_tx_data.utxos); + // } - if !is_coinbase { - tx.input.iter().try_for_each(|txin| { - let outpoint = txin.previous_output; - let input_txid = outpoint.txid; - let input_vout = outpoint.vout; + // let input_amount = input_tx_data.utxos.remove(&input_vout); - let remove_tx_data_from_cached_puts = { - let mut is_tx_data_from_cached_puts = false; + let input_amount_and_address_index = databases + .txout_index_to_amount + .remove(&input_txout_index) + .map(|amount| { + ( + amount, + databases.txout_index_to_address_index.remove(&input_txout_index), + ) + }) // Remove from cached puts + .or_else(|| txout_index_to_amount_and_address_index.remove(&input_txout_index)); - let input_tx_data = txid_to_tx_data - .get_mut(&input_txid) - .unwrap() - .as_mut() - .or_else(|| { - is_tx_data_from_cached_puts = true; - - databases - .txid_to_tx_data - .unsafe_get_mut_from_puts(&input_txid) - }); - - // Can be none because 0 sats inputs happen - // https://mempool.space/tx/f329e55c2de9b821356e6f2c4bba923ea7030cad61120f5ced5d4429f5c86fda#vin=27 - - if input_tx_data.is_none() { - if !enable_check_if_txout_value_is_zero_in_db - || rpc - .get_raw_transaction(&input_txid, None) - .unwrap() - .output - .get(input_vout as usize) - .unwrap() - .value - .to_sat() - == 0 - { - return ControlFlow::Continue::<()>(()); - } - - dbg!((input_txid, txid, tx_index, input_vout)); - panic!("Txid to be in txid_to_tx_data"); + if input_amount_and_address_index.is_none() { + if !enable_check_if_txout_value_is_zero_in_db + || rpc + .get_raw_transaction(&input_txid, None) + .unwrap() + .output + .get(input_vout as usize) + .unwrap() + .value + .to_sat() + == 0 + { + return ControlFlow::Continue::<()>(()); } - let input_tx_data = input_tx_data.unwrap(); - let input_tx_index = input_tx_data.index; - let input_vout = input_vout as u16; - let input_txout_index = TxoutIndex::new(input_tx_index, input_vout); + dbg!((input_txid, tx_index, input_tx_index, input_vout, input_tx_data, txid,)); + panic!("Txout index to be in txout_index_to_txout_value"); + } - // if input_tx_index == 2516 || input_tx_index == 2490 { - // dbg!(input_tx_index, &input_tx_data.utxos); - // } + input_tx_data.utxos -= 1; - // let input_amount = input_tx_data.utxos.remove(&input_vout); + let (input_amount, input_address_index) = input_amount_and_address_index.unwrap(); - let input_amount_and_address_index = databases - .txout_index_to_amount - .remove(&input_txout_index) - .map(|amount| { - ( - amount, - databases - .txout_index_to_address_index - .remove(&input_txout_index), - ) - }) // Remove from cached puts - .or_else(|| { - txout_index_to_amount_and_address_index.remove(&input_txout_index) - }); + let input_block_path = input_tx_data.block_path; - if input_amount_and_address_index.is_none() { - if !enable_check_if_txout_value_is_zero_in_db - || rpc - .get_raw_transaction(&input_txid, None) - .unwrap() - .output - .get(input_vout as usize) - .unwrap() - .value - .to_sat() - == 0 - { - return ControlFlow::Continue::<()>(()); - } + let BlockPath { + date_index: input_date_index, + block_index: input_block_index, + } = input_block_path; - dbg!(( - input_txid, - tx_index, - input_tx_index, - input_vout, - input_tx_data, - txid, - )); - panic!("Txout index to be in txout_index_to_txout_value"); - } - - input_tx_data.utxos -= 1; - - let (input_amount, input_address_index) = - input_amount_and_address_index.unwrap(); - - let input_block_path = input_tx_data.block_path; - - let BlockPath { - date_index: input_date_index, - block_index: input_block_index, - } = input_block_path; - - let input_date_data = states + let input_date_data = + states .date_data_vec .get_mut(input_date_index as usize) .unwrap_or_else(|| { @@ -430,121 +398,112 @@ pub fn parse( panic!() }); - let input_block_data = input_date_data - .blocks - .get_mut(input_block_index as usize) + let input_block_data = input_date_data + .blocks + .get_mut(input_block_index as usize) + .unwrap_or_else(|| { + dbg!( + height, + &input_txid, + input_block_path, + input_date_index, + input_block_index, + ); + panic!() + }); + + input_block_data.send(input_amount); + + inputs_sum += input_amount; + + block_path_to_sent_data + .entry(input_block_path) + .or_default() + .send(input_amount); + + satblocks_destroyed += input_amount * (height - input_block_data.height); + + satdays_destroyed += + input_amount * date.signed_duration_since(*input_date_data.date).num_days() as u64; + + if compute_addresses { + let input_address_index = input_address_index.unwrap_or_else(|| { + dbg!( + height, + input_amount, + &input_tx_data, + input_address_index, + input_txout_index, + txid, + input_txid, + input_vout + ); + panic!() + }); + + let address_index_to_address_data = address_index_to_address_data.as_mut().unwrap(); + + let input_address_data = address_index_to_address_data + .get_mut(&input_address_index) .unwrap_or_else(|| { - dbg!( - height, - &input_txid, - input_block_path, - input_date_index, - input_block_index, - ); - panic!() + dbg!(input_address_index, input_txout_index, input_txid, input_vout); + panic!(); }); - input_block_data.send(input_amount); + let input_address_realized_data = address_index_to_address_realized_data + .entry(input_address_index) + .or_insert_with(|| AddressRealizedData::default(input_address_data)); - inputs_sum += input_amount; + let previous_price = input_block_data.price; - block_path_to_sent_data - .entry(input_block_path) - .or_default() - .send(input_amount); - - satblocks_destroyed += input_amount * (height - input_block_data.height); - - satdays_destroyed += input_amount - * date.signed_duration_since(*input_date_data.date).num_days() as u64; - - if compute_addresses { - let input_address_index = input_address_index.unwrap_or_else(|| { + // MUST be after `or_insert_with` + input_address_data + .send(input_amount, previous_price) + .unwrap_or_else(|_| { dbg!( - height, - input_amount, - &input_tx_data, input_address_index, - input_txout_index, txid, input_txid, - input_vout + input_amount, + tx_index, + input_tx_index, + input_vout, + &input_address_data ); + panic!() }); - let address_index_to_address_data = - address_index_to_address_data.as_mut().unwrap(); - - let input_address_data = address_index_to_address_data - .get_mut(&input_address_index) - .unwrap_or_else(|| { - dbg!( - input_address_index, - input_txout_index, - input_txid, - input_vout - ); - panic!(); - }); - - let input_address_realized_data = - address_index_to_address_realized_data - .entry(input_address_index) - .or_insert_with(|| { - AddressRealizedData::default(input_address_data) - }); - - let previous_price = input_block_data.price; - - // MUST be after `or_insert_with` - input_address_data - .send(input_amount, previous_price) - .unwrap_or_else(|_| { - dbg!( - input_address_index, - txid, - input_txid, - input_amount, - tx_index, - input_tx_index, - input_vout, - &input_address_data - ); - - panic!() - }); - - input_address_realized_data.send( - input_amount, - block_price, - previous_price, - timestamp, - input_block_data.timestamp, - ); - }; - - is_tx_data_from_cached_puts && input_tx_data.is_empty() + input_address_realized_data.send( + input_amount, + block_price, + previous_price, + timestamp, + input_block_data.timestamp, + ); }; - if remove_tx_data_from_cached_puts { - // Pre remove tx_datas that are empty and weren't yet added to the database to avoid having it was in there or not (and thus avoid useless operations) - databases.txid_to_tx_data.remove_from_puts(&input_txid) - } + is_tx_data_from_cached_puts && input_tx_data.is_empty() + }; - ControlFlow::Continue(()) - })?; - } + if remove_tx_data_from_cached_puts { + // Pre remove tx_datas that are empty and weren't yet added to the database to avoid having it was in there or not (and thus avoid useless operations) + databases.txid_to_tx_data.remove_from_ram(&input_txid) + } - amount_sent += inputs_sum; + ControlFlow::Continue(()) + })?; + } - let fee = inputs_sum - outputs_sum; + amount_sent += inputs_sum; - fees_total += fee; - fees.push(fee); + let fee = inputs_sum - outputs_sum; - ControlFlow::Continue(()) - }); + fees_total += fee; + fees.push(fee); + + ControlFlow::Continue(()) + }); if !partial_txout_data_vec.is_empty() { panic!("partial_txout_data_vec should've been fully consumed"); @@ -553,7 +512,7 @@ pub fn parse( txid_to_tx_data.into_iter().for_each(|(txid, tx_data)| { if let Some(tx_data) = tx_data { if tx_data.is_empty() { - databases.txid_to_tx_data.remove_from_db(txid); + databases.txid_to_tx_data.remove_later_from_disk(txid); } else { databases.txid_to_tx_data.update(txid, tx_data); } @@ -577,24 +536,17 @@ pub fn parse( if datasets.utxo.needs_durable_states(height, date) { if let Some(previous_last_block_data) = previous_last_block_data { - block_path_to_sent_data - .iter() - .for_each(|(block_path, sent_data)| { - let block_data = - states.date_data_vec.get_block_data(block_path).unwrap(); + block_path_to_sent_data.iter().for_each(|(block_path, sent_data)| { + let block_data = states.date_data_vec.get_block_data(block_path).unwrap(); - if block_data.height != height { - states - .utxo_cohorts_durable_states - .as_mut() - .unwrap() - .subtract_moved( - block_data, - sent_data, - previous_last_block_data, - ); - } - }); + if block_data.height != height { + states.utxo_cohorts_durable_states.as_mut().unwrap().subtract_moved( + block_data, + sent_data, + previous_last_block_data, + ); + } + }); } let last_block_data = states.date_data_vec.last_block().unwrap(); @@ -612,11 +564,7 @@ pub fn parse( .utxo_cohorts_durable_states .as_mut() .unwrap() - .udpate_age_if_needed( - block_data, - last_block_data, - previous_last_block_data, - ); + .udpate_age_if_needed(block_data, last_block_data, previous_last_block_data); }); } @@ -625,14 +573,7 @@ pub fn parse( .utxo_cohorts_durable_states .as_ref() .unwrap() - .compute_one_shot_states( - block_price, - if is_date_last_block { - Some(date_price) - } else { - None - }, - ); + .compute_one_shot_states(block_price, if is_date_last_block { Some(date_price) } else { None }); } }); @@ -665,10 +606,10 @@ pub fn parse( // TODO: Only compute if needed address_cohorts_output_states.replace(AddressCohortsOutputStates::default()); - address_index_to_address_realized_data.iter().for_each( - |(address_index, address_realized_data)| { - let current_address_data = - address_index_to_address_data.get(address_index).unwrap(); + address_index_to_address_realized_data + .iter() + .for_each(|(address_index, address_realized_data)| { + let current_address_data = address_index_to_address_data.get(address_index).unwrap(); states .address_cohorts_durable_states @@ -708,42 +649,34 @@ pub fn parse( ¤t_address_data.compute_liquidity_classification(), ) .unwrap(); - }, - ); + }); address_cohorts_one_shot_states.replace( states .address_cohorts_durable_states .as_ref() .unwrap() - .compute_one_shot_states( - block_price, - if is_date_last_block { - Some(date_price) - } else { - None - }, - ), + .compute_one_shot_states(block_price, if is_date_last_block { Some(date_price) } else { None }), ); }); } }); if compute_addresses { - address_index_to_address_data.unwrap().into_iter().for_each( - |(address_index, address_data)| { + address_index_to_address_data + .unwrap() + .into_iter() + .for_each(|(address_index, address_data)| { if address_data.is_empty() { - databases.address_index_to_empty_address_data.unsafe_insert( - address_index, - EmptyAddressData::from_non_empty(&address_data), - ); + databases + .address_index_to_empty_address_data + .insert_to_ram(address_index, EmptyAddressData::from_non_empty(&address_data)); } else { databases .address_index_to_address_data - .unsafe_insert(address_index, address_data); + .insert_to_ram(address_index, address_data); } - }, - ) + }) } datasets.insert(InsertData { @@ -848,17 +781,15 @@ fn prepare_outputs( .collect_vec(); if compute_addresses { - partial_txout_data_vec - .par_iter_mut() - .for_each(|partial_tx_out_data| { - if let Some(partial_tx_out_data) = partial_tx_out_data { - let address_index_opt = address_to_address_index - .unsafe_get(partial_tx_out_data.address.as_ref().unwrap()) - .cloned(); + partial_txout_data_vec.par_iter_mut().for_each(|partial_tx_out_data| { + if let Some(partial_tx_out_data) = partial_tx_out_data { + let address_index_opt = address_to_address_index + .unsafe_get(partial_tx_out_data.address.as_ref().unwrap()) + .cloned(); - partial_tx_out_data.address_index_opt = address_index_opt; - } - }); + partial_tx_out_data.address_index_opt = address_index_opt; + } + }); } TxoutsParsingResults { @@ -896,7 +827,7 @@ fn prepare_inputs<'a>( let mut tx_datas = txid_to_tx_data .par_iter() - .map(|(txid, _)| txid_to_tx_data_db.unsafe_get(txid)) + .map(|(txid, _)| txid_to_tx_data_db.get(txid)) .collect::>(); txid_to_tx_data.values_mut().rev().for_each(|tx_data_opt| { @@ -934,11 +865,8 @@ fn prepare_inputs<'a>( // https://mempool.space/tx/9d8a0d851c9fb2cdf1c6d9406ce97e19e6911ae3503ab2dd5f38640bacdac996 // which is used later as input .map(|amount| { - let address_index = compute_addresses.then(|| { - *txout_index_to_address_index_db - .unsafe_get(&txout_index) - .unwrap() - }); + let address_index = + compute_addresses.then(|| *txout_index_to_address_index_db.unsafe_get(&txout_index).unwrap()); (txout_index, (*amount, address_index)) }) @@ -986,21 +914,16 @@ fn compute_address_index_to_address_data( address_index_to_address_data .par_iter_mut() .for_each(|(address_index, address_data)| { - if let Some(_address_data) = - address_index_to_address_data_db.unsafe_get_from_cache(address_index) - { + if let Some(_address_data) = address_index_to_address_data_db.get_from_ram(address_index) { _address_data.clone_into(address_data); - } else if let Some(empty_address_data) = - address_index_to_empty_address_data_db.unsafe_get_from_cache(address_index) + } else if let Some(empty_address_data) = address_index_to_empty_address_data_db.get_from_ram(address_index) { *address_data = AddressData::from_empty(empty_address_data); - } else if let Some(_address_data) = - address_index_to_address_data_db.unsafe_get_from_db(address_index) - { + } else if let Some(_address_data) = address_index_to_address_data_db.get_from_disk(address_index) { _address_data.clone_into(address_data); } else { let empty_address_data = address_index_to_empty_address_data_db - .unsafe_get_from_db(address_index) + .get_from_disk(address_index) .unwrap(); *address_data = AddressData::from_empty(empty_address_data); diff --git a/_src/parser/databases/_trait.rs b/_src/parser/databases/_trait.rs new file mode 100644 index 000000000..5da93dd8a --- /dev/null +++ b/_src/parser/databases/_trait.rs @@ -0,0 +1,60 @@ +use std::{fs, io, path::Path}; + +use log::info; +use snkrj::AnyDatabase; + +use crate::structs::{Config, Date, Height}; + +use super::Metadata; + +pub trait AnyDatabaseGroup +where + Self: Sized, +{ + fn init(config: &Config) -> Self { + let s = Self::import(config); + s.create_dir_all().unwrap(); + s + } + + fn import(config: &Config) -> Self; + + fn drain_to_vec(&mut self) -> Vec>; + fn open_all(&mut self); + + fn metadata(&mut self) -> &mut Metadata; + fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { + self.metadata().export(height, date) + } + + fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { + fs::create_dir_all(self.path()) + } + + fn remove_dir_all(&self) -> color_eyre::Result<(), io::Error> { + fs::remove_dir_all(self.path()) + } + + fn reset(&mut self) -> color_eyre::Result<(), io::Error> { + info!( + "Reset {}", + self.path() + .components() + .last() + .unwrap() + .as_os_str() + .to_str() + .unwrap() + ); + + self.reset_metadata(); + self.remove_dir_all()?; + self.create_dir_all()?; + + Ok(()) + } + + fn reset_metadata(&mut self); + + fn path(&self) -> &Path; +} diff --git a/parser/src/databases/address_index_to_address_data.rs b/_src/parser/databases/address_index_to_address_data.rs similarity index 63% rename from parser/src/databases/address_index_to_address_data.rs rename to _src/parser/databases/address_index_to_address_data.rs index e6ed7fc80..fdcb09205 100644 --- a/parser/src/databases/address_index_to_address_data.rs +++ b/_src/parser/databases/address_index_to_address_data.rs @@ -2,19 +2,20 @@ use std::{ collections::BTreeMap, fs, mem, ops::{Deref, DerefMut}, + path::{Path, PathBuf}, }; use allocative::Allocative; use itertools::Itertools; use rayon::prelude::*; +use snkrj::{AnyDatabase, Database as _Database}; use crate::{ - states::AddressCohortsDurableStates, - structs::{AddressData, Date, Height}, - utils::time, + parser::states::AddressCohortsDurableStates, + structs::{AddressData, Config}, }; -use super::{AnyDatabase, AnyDatabaseGroup, Database as _Database, Metadata}; +use super::{AnyDatabaseGroup, Metadata}; type Key = u32; type Value = AddressData; @@ -22,8 +23,9 @@ type Database = _Database; #[derive(Allocative)] pub struct AddressIndexToAddressData { + path: PathBuf, pub metadata: Metadata, - + #[allocative(skip)] pub map: BTreeMap, } @@ -44,10 +46,10 @@ impl DerefMut for AddressIndexToAddressData { pub const ADDRESS_INDEX_DB_MAX_SIZE: usize = 250_000; impl AddressIndexToAddressData { - pub fn unsafe_insert(&mut self, key: Key, value: Value) -> Option { + pub fn insert_to_ram(&mut self, key: Key, value: Value) -> Option { self.metadata.called_insert(); - self.open_db(&key).unsafe_insert(key, value) + self.open_db(&key).insert_to_ram(key, value) } pub fn remove(&mut self, key: &Key) -> Option { @@ -58,20 +60,21 @@ impl AddressIndexToAddressData { /// Doesn't check if the database is open contrary to `safe_get` which does and opens if needed /// Though it makes it easy to use with rayon. - pub fn unsafe_get_from_cache(&self, key: &Key) -> Option<&Value> { + pub fn get_from_ram(&self, key: &Key) -> Option<&Value> { let db_index = Self::db_index(key); - self.get(&db_index).unwrap().get_from_puts(key) + self.get(&db_index).unwrap().get_from_ram(key) } - pub fn unsafe_get_from_db(&self, key: &Key) -> Option<&Value> { + pub fn get_from_disk(&self, key: &Key) -> Option<&Value> { let db_index = Self::db_index(key); - self.get(&db_index).unwrap().db_get(key) + self.get(&db_index).unwrap().get_from_disk(key) } pub fn open_db(&mut self, key: &Key) -> &mut Database { let db_index = Self::db_index(key); + let path = self.path().to_owned(); self.entry(db_index).or_insert_with(|| { let db_name = format!( @@ -80,31 +83,31 @@ impl AddressIndexToAddressData { (db_index + 1) * ADDRESS_INDEX_DB_MAX_SIZE ); - let path = Self::root().join(db_name); + let path = path.join(db_name); Database::open(path).unwrap() }) } pub fn compute_addres_cohorts_durable_states(&mut self) -> AddressCohortsDurableStates { - time("Iter through address_index_to_address_data", || { - self.open_all(); + // time("Iter through address_index_to_address_data", || { + self.open_all(); - // MUST CLEAR MAP, otherwise some weird things are happening later in the export I think - mem::take(&mut self.map) - .par_iter() - .map(|(_, database)| { - let mut s = AddressCohortsDurableStates::default(); + // MUST CLEAR MAP, otherwise some weird things are happening later in the export I think + mem::take(&mut self.map) + .par_iter() + .map(|(_, database)| { + let mut s = AddressCohortsDurableStates::default(); - database - .iter() - .map(|r| r.unwrap().1) - .for_each(|address_data| s.increment(address_data).unwrap()); + database + .iter_disk() + .map(|r| r.unwrap().1) + .for_each(|address_data| s.increment(address_data).unwrap()); - s - }) - .sum() - }) + s + }) + .sum() + // }) } fn db_index(key: &Key) -> usize { @@ -113,30 +116,23 @@ impl AddressIndexToAddressData { } impl AnyDatabaseGroup for AddressIndexToAddressData { - fn import() -> Self { + fn import(config: &Config) -> Self { + let path = config + .path_databases() + .join("address_index_to_address_data"); Self { - metadata: Metadata::import(Self::root(), 1), - + metadata: Metadata::import(&path, 1), + path, map: BTreeMap::default(), } } - fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { - fs::create_dir_all(Self::root()) - } - fn reset_metadata(&mut self) { self.metadata.reset(); } - fn folder<'a>() -> &'a str { - "address_index_to_address_data" - } - fn open_all(&mut self) { - let path = Self::root(); - - let folder = fs::read_dir(path); + let folder = fs::read_dir(&self.path); if folder.is_err() { return; @@ -167,7 +163,11 @@ impl AnyDatabaseGroup for AddressIndexToAddressData { .collect_vec() } - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { - self.metadata.export(height, date) + fn metadata(&mut self) -> &mut Metadata { + &mut self.metadata + } + + fn path(&self) -> &Path { + &self.path } } diff --git a/parser/src/databases/address_index_to_empty_address_data.rs b/_src/parser/databases/address_index_to_empty_address_data.rs similarity index 73% rename from parser/src/databases/address_index_to_empty_address_data.rs rename to _src/parser/databases/address_index_to_empty_address_data.rs index cdb0127fc..df940c2f3 100644 --- a/parser/src/databases/address_index_to_empty_address_data.rs +++ b/_src/parser/databases/address_index_to_empty_address_data.rs @@ -2,16 +2,16 @@ use std::{ collections::BTreeMap, fs, mem, ops::{Deref, DerefMut}, + path::{Path, PathBuf}, }; use allocative::Allocative; use itertools::Itertools; +use snkrj::{AnyDatabase, Database as _Database}; -use crate::structs::{Date, EmptyAddressData, Height}; +use crate::structs::{Config, EmptyAddressData}; -use super::{ - AnyDatabase, AnyDatabaseGroup, Database as _Database, Metadata, ADDRESS_INDEX_DB_MAX_SIZE, -}; +use super::{AnyDatabaseGroup, Metadata, ADDRESS_INDEX_DB_MAX_SIZE}; type Key = u32; type Value = EmptyAddressData; @@ -19,8 +19,9 @@ type Database = _Database; #[derive(Allocative)] pub struct AddressIndexToEmptyAddressData { + path: PathBuf, pub metadata: Metadata, - + #[allocative(skip)] map: BTreeMap, } @@ -39,10 +40,10 @@ impl DerefMut for AddressIndexToEmptyAddressData { } impl AddressIndexToEmptyAddressData { - pub fn unsafe_insert(&mut self, key: Key, value: Value) -> Option { + pub fn insert_to_ram(&mut self, key: Key, value: Value) -> Option { self.metadata.called_insert(); - self.open_db(&key).unsafe_insert(key, value) + self.open_db(&key).insert_to_ram(key, value) } pub fn remove(&mut self, key: &Key) -> Option { @@ -53,13 +54,13 @@ impl AddressIndexToEmptyAddressData { /// Doesn't check if the database is open contrary to `safe_get` which does and opens if needed /// Though it makes it easy to use with rayon. - pub fn unsafe_get_from_cache(&self, key: &Key) -> Option<&Value> { + pub fn get_from_ram(&self, key: &Key) -> Option<&Value> { let db_index = Self::db_index(key); - self.get(&db_index).and_then(|db| db.get_from_puts(key)) + self.get(&db_index).and_then(|db| db.get_from_ram(key)) } - pub fn unsafe_get_from_db(&self, key: &Key) -> Option<&Value> { + pub fn get_from_disk(&self, key: &Key) -> Option<&Value> { let db_index = Self::db_index(key); self.get(&db_index) @@ -67,11 +68,12 @@ impl AddressIndexToEmptyAddressData { dbg!(&self.map.keys(), &key, &db_index); panic!() }) - .db_get(key) + .get_from_disk(key) } pub fn open_db(&mut self, key: &Key) -> &mut Database { let db_index = Self::db_index(key); + let path = self.path.to_owned(); self.entry(db_index).or_insert_with(|| { let db_name = format!( @@ -80,7 +82,7 @@ impl AddressIndexToEmptyAddressData { (db_index + 1) * ADDRESS_INDEX_DB_MAX_SIZE ); - let path = Self::root().join(db_name); + let path = path.join(db_name); Database::open(path).unwrap() }) @@ -92,30 +94,23 @@ impl AddressIndexToEmptyAddressData { } impl AnyDatabaseGroup for AddressIndexToEmptyAddressData { - fn import() -> Self { + fn import(config: &Config) -> Self { + let path = config + .path_databases() + .join("address_index_to_empty_address_data"); Self { - metadata: Metadata::import(Self::root(), 1), - + metadata: Metadata::import(&path, 1), + path, map: BTreeMap::default(), } } - fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { - fs::create_dir_all(Self::root()) - } - fn reset_metadata(&mut self) { self.metadata.reset(); } - fn folder<'a>() -> &'a str { - "address_index_to_empty_address_data" - } - fn open_all(&mut self) { - let path = Self::root(); - - let folder = fs::read_dir(path); + let folder = fs::read_dir(&self.path); if folder.is_err() { return; @@ -146,7 +141,11 @@ impl AnyDatabaseGroup for AddressIndexToEmptyAddressData { .collect_vec() } - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { - self.metadata.export(height, date) + fn metadata(&mut self) -> &mut Metadata { + &mut self.metadata + } + + fn path(&self) -> &Path { + &self.path } } diff --git a/parser/src/databases/address_to_address_index.rs b/_src/parser/databases/address_to_address_index.rs similarity index 81% rename from parser/src/databases/address_to_address_index.rs rename to _src/parser/databases/address_to_address_index.rs index 34b7e8d5b..01518a9fe 100644 --- a/parser/src/databases/address_to_address_index.rs +++ b/_src/parser/databases/address_to_address_index.rs @@ -6,10 +6,11 @@ use std::{ use allocative::Allocative; use itertools::Itertools; +use snkrj::{AnyDatabase, Database}; -use crate::structs::{Address, Date, Height, U8x19, U8x31}; +use crate::structs::{Address, Config, U8x19, U8x31}; -use super::{AnyDatabase, AnyDatabaseGroup, Database, Metadata}; +use super::{AnyDatabaseGroup, Metadata}; type Value = u32; type U8x19Database = Database; @@ -30,18 +31,30 @@ type MultisigDatabase = U32Database; #[derive(Allocative)] pub struct AddressToAddressIndex { + path: PathBuf, pub metadata: Metadata, + #[allocative(skip)] p2pk: BTreeMap, + #[allocative(skip)] p2pkh: BTreeMap, + #[allocative(skip)] p2sh: BTreeMap, + #[allocative(skip)] p2wpkh: BTreeMap, + #[allocative(skip)] p2wsh: BTreeMap, + #[allocative(skip)] p2tr: BTreeMap, + #[allocative(skip)] op_return: Option, + #[allocative(skip)] push_only: Option, + #[allocative(skip)] unknown: Option, + #[allocative(skip)] empty: Option, + #[allocative(skip)] multisig: Option, } @@ -102,19 +115,19 @@ impl AddressToAddressIndex { } } - pub fn unsafe_get_from_puts(&self, address: &Address) -> Option<&Value> { + pub fn get_from_ram(&self, address: &Address) -> Option<&Value> { match address { - Address::Empty(key) => self.empty.as_ref().unwrap().get_from_puts(key), - Address::Unknown(key) => self.unknown.as_ref().unwrap().get_from_puts(key), - Address::OpReturn(key) => self.op_return.as_ref().unwrap().get_from_puts(key), - Address::PushOnly(key) => self.push_only.as_ref().unwrap().get_from_puts(key), - Address::MultiSig(key) => self.multisig.as_ref().unwrap().get_from_puts(key), - Address::P2PK((prefix, key)) => self.p2pk.get(prefix).unwrap().get_from_puts(key), - Address::P2PKH((prefix, key)) => self.p2pkh.get(prefix).unwrap().get_from_puts(key), - Address::P2SH((prefix, key)) => self.p2sh.get(prefix).unwrap().get_from_puts(key), - Address::P2WPKH((prefix, key)) => self.p2wpkh.get(prefix).unwrap().get_from_puts(key), - Address::P2WSH((prefix, key)) => self.p2wsh.get(prefix).unwrap().get_from_puts(key), - Address::P2TR((prefix, key)) => self.p2tr.get(prefix).unwrap().get_from_puts(key), + Address::Empty(key) => self.empty.as_ref().unwrap().get_from_ram(key), + Address::Unknown(key) => self.unknown.as_ref().unwrap().get_from_ram(key), + Address::OpReturn(key) => self.op_return.as_ref().unwrap().get_from_ram(key), + Address::PushOnly(key) => self.push_only.as_ref().unwrap().get_from_ram(key), + Address::MultiSig(key) => self.multisig.as_ref().unwrap().get_from_ram(key), + Address::P2PK((prefix, key)) => self.p2pk.get(prefix).unwrap().get_from_ram(key), + Address::P2PKH((prefix, key)) => self.p2pkh.get(prefix).unwrap().get_from_ram(key), + Address::P2SH((prefix, key)) => self.p2sh.get(prefix).unwrap().get_from_ram(key), + Address::P2WPKH((prefix, key)) => self.p2wpkh.get(prefix).unwrap().get_from_ram(key), + Address::P2WSH((prefix, key)) => self.p2wsh.get(prefix).unwrap().get_from_ram(key), + Address::P2TR((prefix, key)) => self.p2tr.get(prefix).unwrap().get_from_ram(key), } } @@ -160,12 +173,12 @@ impl AddressToAddressIndex { .collect_vec() } - fn path_p2pk() -> PathBuf { - Self::root().join("p2pk") + fn path_p2pk(&self) -> PathBuf { + self.path().join("p2pk") } pub fn open_p2pk(&mut self, prefix: u16) -> &mut P2PKDatabase { - let path = Self::path_p2pk(); + let path = self.path_p2pk(); self.p2pk.entry(prefix).or_insert_with(|| { let path = path.join(prefix.to_string()); Database::open(path).unwrap() @@ -173,7 +186,7 @@ impl AddressToAddressIndex { } fn open_all_p2pk(&mut self) { - let path = Self::path_p2pk(); + let path = self.path_p2pk(); Self::path_to_group_prefixes(&path) .into_iter() .for_each(|prefix| { @@ -184,12 +197,12 @@ impl AddressToAddressIndex { }); } - fn path_p2pkh() -> PathBuf { - Self::root().join("p2pkh") + fn path_p2pkh(&self) -> PathBuf { + self.path().join("p2pkh") } pub fn open_p2pkh(&mut self, prefix: u16) -> &mut P2PKHDatabase { - let path = Self::path_p2pkh(); + let path = self.path_p2pkh(); self.p2pkh.entry(prefix).or_insert_with(|| { let path = path.join(prefix.to_string()); @@ -198,7 +211,7 @@ impl AddressToAddressIndex { } fn open_all_p2pkh(&mut self) { - let path = Self::path_p2pkh(); + let path = self.path_p2pkh(); Self::path_to_group_prefixes(&path) .into_iter() .for_each(|prefix| { @@ -209,12 +222,12 @@ impl AddressToAddressIndex { }); } - fn path_p2sh() -> PathBuf { - Self::root().join("p2sh") + fn path_p2sh(&self) -> PathBuf { + self.path().join("p2sh") } pub fn open_p2sh(&mut self, prefix: u16) -> &mut P2SHDatabase { - let path = Self::path_p2sh(); + let path = self.path_p2sh(); self.p2sh.entry(prefix).or_insert_with(|| { let path = path.join(prefix.to_string()); @@ -223,7 +236,7 @@ impl AddressToAddressIndex { } fn open_all_p2sh(&mut self) { - let path = Self::path_p2sh(); + let path = self.path_p2sh(); Self::path_to_group_prefixes(&path) .into_iter() .for_each(|prefix| { @@ -234,12 +247,12 @@ impl AddressToAddressIndex { }); } - fn path_p2wpkh() -> PathBuf { - Self::root().join("p2wpkh") + fn path_p2wpkh(&self) -> PathBuf { + self.path().join("p2wpkh") } pub fn open_p2wpkh(&mut self, prefix: u16) -> &mut P2WPKHDatabase { - let path = Self::path_p2wpkh(); + let path = self.path_p2wpkh(); self.p2wpkh.entry(prefix).or_insert_with(|| { let path = path.join(prefix.to_string()); @@ -248,7 +261,7 @@ impl AddressToAddressIndex { } fn open_all_p2wpkh(&mut self) { - let path = Self::path_p2wpkh(); + let path = self.path_p2wpkh(); Self::path_to_group_prefixes(&path) .into_iter() .for_each(|prefix| { @@ -259,12 +272,12 @@ impl AddressToAddressIndex { }); } - fn path_p2wsh() -> PathBuf { - Self::root().join("p2wsh") + fn path_p2wsh(&self) -> PathBuf { + self.path().join("p2wsh") } pub fn open_p2wsh(&mut self, prefix: u16) -> &mut P2WSHDatabase { - let path = Self::path_p2wsh(); + let path = self.path_p2wsh(); self.p2wsh.entry(prefix).or_insert_with(|| { let path = path.join(prefix.to_string()); @@ -273,7 +286,7 @@ impl AddressToAddressIndex { } fn open_all_p2wsh(&mut self) { - let path = Self::path_p2wsh(); + let path = self.path_p2wsh(); Self::path_to_group_prefixes(&path) .into_iter() .for_each(|prefix| { @@ -284,12 +297,12 @@ impl AddressToAddressIndex { }); } - fn path_p2tr() -> PathBuf { - Self::root().join("p2tr") + fn path_p2tr(&self) -> PathBuf { + self.path().join("p2tr") } pub fn open_p2tr(&mut self, prefix: u16) -> &mut P2TRDatabase { - let path = Self::path_p2tr(); + let path = self.path_p2tr(); self.p2tr.entry(prefix).or_insert_with(|| { let path = path.join(prefix.to_string()); @@ -298,7 +311,7 @@ impl AddressToAddressIndex { } fn open_all_p2tr(&mut self) { - let path = Self::path_p2tr(); + let path = self.path_p2tr(); Self::path_to_group_prefixes(&path) .into_iter() .for_each(|prefix| { @@ -311,34 +324,36 @@ impl AddressToAddressIndex { pub fn open_unknown(&mut self) -> &mut UnknownDatabase { self.unknown - .get_or_insert_with(|| Database::open(Self::root().join("unknown")).unwrap()) + .get_or_insert_with(|| Database::open(self.path.join("unknown")).unwrap()) } pub fn open_op_return(&mut self) -> &mut UnknownDatabase { self.op_return - .get_or_insert_with(|| Database::open(Self::root().join("op_return")).unwrap()) + .get_or_insert_with(|| Database::open(self.path.join("op_return")).unwrap()) } pub fn open_push_only(&mut self) -> &mut UnknownDatabase { self.push_only - .get_or_insert_with(|| Database::open(Self::root().join("push_only")).unwrap()) + .get_or_insert_with(|| Database::open(self.path.join("push_only")).unwrap()) } pub fn open_empty(&mut self) -> &mut UnknownDatabase { self.empty - .get_or_insert_with(|| Database::open(Self::root().join("empty")).unwrap()) + .get_or_insert_with(|| Database::open(self.path.join("empty")).unwrap()) } pub fn open_multisig(&mut self) -> &mut MultisigDatabase { self.multisig - .get_or_insert_with(|| Database::open(Self::root().join("multisig")).unwrap()) + .get_or_insert_with(|| Database::open(self.path.join("multisig")).unwrap()) } } impl AnyDatabaseGroup for AddressToAddressIndex { - fn import() -> Self { + fn import(config: &Config) -> Self { + let path = config.path_databases().join("address_to_address_index"); Self { - metadata: Metadata::import(Self::root(), 1), + metadata: Metadata::import(&path, 1), + path, p2pk: BTreeMap::default(), p2pkh: BTreeMap::default(), @@ -355,22 +370,18 @@ impl AnyDatabaseGroup for AddressToAddressIndex { } fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { - fs::create_dir_all(Self::path_p2pk()).unwrap(); - fs::create_dir_all(Self::path_p2pkh()).unwrap(); - fs::create_dir_all(Self::path_p2sh()).unwrap(); - fs::create_dir_all(Self::path_p2wpkh()).unwrap(); - fs::create_dir_all(Self::path_p2wsh()).unwrap(); - fs::create_dir_all(Self::path_p2tr()) + fs::create_dir_all(self.path_p2pk()).unwrap(); + fs::create_dir_all(self.path_p2pkh()).unwrap(); + fs::create_dir_all(self.path_p2sh()).unwrap(); + fs::create_dir_all(self.path_p2wpkh()).unwrap(); + fs::create_dir_all(self.path_p2wsh()).unwrap(); + fs::create_dir_all(self.path_p2tr()) } fn reset_metadata(&mut self) { self.metadata.reset() } - fn folder<'a>() -> &'a str { - "address_to_address_index" - } - fn drain_to_vec(&mut self) -> Vec> { mem::take(&mut self.p2pk) .into_values() @@ -424,7 +435,11 @@ impl AnyDatabaseGroup for AddressToAddressIndex { self.open_all_p2tr(); } - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { - self.metadata.export(height, date) + fn metadata(&mut self) -> &mut Metadata { + &mut self.metadata + } + + fn path(&self) -> &Path { + &self.path } } diff --git a/parser/src/databases/metadata.rs b/_src/parser/databases/metadata.rs similarity index 94% rename from parser/src/databases/metadata.rs rename to _src/parser/databases/metadata.rs index eb0046c5d..9d69c4817 100644 --- a/parser/src/databases/metadata.rs +++ b/_src/parser/databases/metadata.rs @@ -10,8 +10,8 @@ use std::{ }; use crate::{ + io::Serialization, structs::{Counter, Date, Height}, - Serialization, }; #[derive(Default, Debug, Encode, Decode, Allocative)] @@ -35,10 +35,10 @@ impl DerefMut for Metadata { } impl Metadata { - pub fn import(path: PathBuf, version: u16) -> Self { + pub fn import(path: &Path, version: u16) -> Self { Self { - data: MetadataData::import(&path, version), - path, + data: MetadataData::import(path, version), + path: path.to_owned(), } } diff --git a/parser/src/databases/mod.rs b/_src/parser/databases/mod.rs similarity index 72% rename from parser/src/databases/mod.rs rename to _src/parser/databases/mod.rs index 9ff6bc752..413790404 100644 --- a/parser/src/databases/mod.rs +++ b/_src/parser/databases/mod.rs @@ -1,8 +1,5 @@ -use std::thread::{self}; - use allocative::Allocative; -mod _database; mod _trait; mod address_index_to_address_data; mod address_index_to_empty_address_data; @@ -12,24 +9,20 @@ mod txid_to_tx_data; mod txout_index_to_address_index; mod txout_index_to_amount; -pub use _database::*; use _trait::*; pub use address_index_to_address_data::*; pub use address_index_to_empty_address_data::*; pub use address_to_address_index::*; use itertools::Itertools; +use log::info; use metadata::*; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use snkrj::AnyDatabase; pub use txid_to_tx_data::*; pub use txout_index_to_address_index::*; pub use txout_index_to_amount::*; -use crate::{ - log, - structs::{Date, Height}, - utils::time, - Exit, -}; +use crate::structs::{Config, Date, Height}; #[derive(Allocative)] pub struct Databases { @@ -42,18 +35,20 @@ pub struct Databases { } impl Databases { - pub fn import() -> Self { - let address_index_to_address_data = AddressIndexToAddressData::init(); + pub fn import(config: &Config) -> Self { + let address_index_to_address_data = AddressIndexToAddressData::init(config); - let address_index_to_empty_address_data = AddressIndexToEmptyAddressData::init(); + let address_index_to_empty_address_data = AddressIndexToEmptyAddressData::init(config); - let address_to_address_index = AddressToAddressIndex::init(); + let address_to_address_index = AddressToAddressIndex::init(config); - let txid_to_tx_data = TxidToTxData::init(); + let txid_to_tx_data = TxidToTxData::init(config); - let txout_index_to_address_index = TxoutIndexToAddressIndex::init(); + let txout_index_to_address_index = TxoutIndexToAddressIndex::init(config); - let txout_index_to_amount = TxoutIndexToAmount::init(); + let txout_index_to_amount = TxoutIndexToAmount::init(config); + + info!("Imported databases"); Self { address_index_to_address_data, @@ -91,62 +86,21 @@ impl Databases { Ok(()) } - pub fn export(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { + pub fn export( + &mut self, + height: Height, + date: Date, + defragment: bool, + ) -> color_eyre::Result<()> { self.export_metadata(height, date)?; self.drain_to_vec() .into_par_iter() - .try_for_each(AnyDatabase::boxed_export)?; + .try_for_each(|s| AnyDatabase::boxed_export(s, defragment))?; Ok(()) } - fn open_all(&mut self) { - thread::scope(|s| { - s.spawn(|| { - self.address_index_to_address_data.open_all(); - }); - - s.spawn(|| { - self.address_index_to_empty_address_data.open_all(); - }); - - s.spawn(|| { - self.address_to_address_index.open_all(); - }); - - s.spawn(|| { - self.txid_to_tx_data.open_all(); - }); - - s.spawn(|| { - self.txout_index_to_address_index.open_all(); - }); - - s.spawn(|| { - self.txout_index_to_amount.open_all(); - }); - }); - } - - pub fn defragment(&mut self, exit: &Exit) { - exit.block(); - - log("Databases defragmentation"); - - time("Defragmenting databases", || { - time("Opened all databases", || self.open_all()); - - log("Defragmenting..."); - - self.drain_to_vec() - .into_par_iter() - .for_each(AnyDatabase::boxed_defragment); - }); - - exit.unblock(); - } - pub fn reset(&mut self, include_addresses: bool) { if include_addresses { let _ = self.address_index_to_address_data.reset(); @@ -177,8 +131,8 @@ impl Databases { pub fn check_if_usable( &self, - min_initial_last_address_height: Option, - min_initial_last_address_date: Option, + last_address_height: Option, + last_address_date: Option, ) -> bool { let are_tx_databases_in_sync = self .txout_index_to_amount @@ -215,8 +169,7 @@ impl Databases { return false; } - // let are_address_datasets_farer_or_in_sync_with_address_databases = - min_initial_last_address_height >= self.address_to_address_index.metadata.last_height - && min_initial_last_address_date >= self.address_to_address_index.metadata.last_date + last_address_height >= self.address_to_address_index.metadata.last_height + && last_address_date >= self.address_to_address_index.metadata.last_date } } diff --git a/parser/src/databases/txid_to_tx_data.rs b/_src/parser/databases/txid_to_tx_data.rs similarity index 57% rename from parser/src/databases/txid_to_tx_data.rs rename to _src/parser/databases/txid_to_tx_data.rs index 59a706249..0d3581aec 100644 --- a/parser/src/databases/txid_to_tx_data.rs +++ b/_src/parser/databases/txid_to_tx_data.rs @@ -1,16 +1,17 @@ use std::{ collections::BTreeMap, fs, mem, - ops::{Deref, DerefMut}, + path::{Path, PathBuf}, }; use allocative::Allocative; -use biter::bitcoin::Txid; +use brk_parser::bitcoin::Txid; use itertools::Itertools; +use snkrj::{AnyDatabase, Database as _Database}; -use crate::structs::{Date, Height, TxData, U8x31}; +use crate::structs::{Config, TxData, U8x31}; -use super::{AnyDatabase, AnyDatabaseGroup, Database as _Database, Metadata}; +use super::{AnyDatabaseGroup, Metadata}; type Key = U8x31; type Value = TxData; @@ -18,25 +19,12 @@ type Database = _Database; #[derive(Allocative)] pub struct TxidToTxData { + path: PathBuf, pub metadata: Metadata, - + #[allocative(skip)] map: BTreeMap, } -impl Deref for TxidToTxData { - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.map - } -} - -impl DerefMut for TxidToTxData { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.map - } -} - impl TxidToTxData { pub fn insert(&mut self, txid: &Txid, tx_index: Value) -> Option { self.metadata.called_insert(); @@ -46,53 +34,38 @@ impl TxidToTxData { self.open_db(txid).insert(txid_key, tx_index) } - // pub fn safe_get(&mut self, txid: &Txid) -> Option<&Value> { - // let txid_key = Self::txid_to_key(txid); - // self.open_db(txid).get(&txid_key) - // } - /// Doesn't check if the database is open contrary to `safe_get` which does and opens if needed. /// Though it makes it easy to use with rayon - pub fn unsafe_get(&self, txid: &Txid) -> Option<&Value> { + pub fn get(&self, txid: &Txid) -> Option<&Value> { let txid_key = Self::txid_to_key(txid); let db_index = Self::db_index(txid); - self.get(&db_index).unwrap().get(&txid_key) + self.map.get(&db_index).unwrap().get(&txid_key) } - // pub fn unsafe_get_from_puts(&self, txid: &Txid) -> Option<&Value> { - // let txid_key = Self::txid_to_key(txid); - - // let db_index = Self::db_index(txid); - - // self.get(&db_index).unwrap().get_from_puts(&txid_key) - // } - - pub fn unsafe_get_mut_from_puts(&mut self, txid: &Txid) -> Option<&mut Value> { + pub fn get_mut_from_ram(&mut self, txid: &Txid) -> Option<&mut Value> { let txid_key = Self::txid_to_key(txid); let db_index = Self::db_index(txid); - self.get_mut(&db_index) - .unwrap() - .get_mut_from_puts(&txid_key) + self.map.get_mut(&db_index).unwrap().get_mut_from_ram(&txid_key) } - pub fn remove_from_db(&mut self, txid: &Txid) { + pub fn remove_later_from_disk(&mut self, txid: &Txid) { self.metadata.called_remove(); let txid_key = Self::txid_to_key(txid); - self.open_db(txid).db_remove(&txid_key); + self.open_db(txid).remove_later_from_disk(&txid_key); } - pub fn remove_from_puts(&mut self, txid: &Txid) { + pub fn remove_from_ram(&mut self, txid: &Txid) { self.metadata.called_remove(); let txid_key = Self::txid_to_key(txid); - self.open_db(txid).remove_from_puts(&txid_key); + self.open_db(txid).remove_from_ram(&txid_key); } pub fn update(&mut self, txid: &Txid, tx_data: TxData) { @@ -108,9 +81,10 @@ impl TxidToTxData { } #[inline(always)] - pub fn _open_db(&mut self, db_index: u16) -> &mut Database { - self.entry(db_index).or_insert_with(|| { - let path = Self::root().join(db_index.to_string()); + fn _open_db(&mut self, db_index: u16) -> &mut Database { + let path = self.path.to_owned(); + self.map.entry(db_index).or_insert_with(|| { + let path = path.join(db_index.to_string()); Database::open(path).unwrap() }) } @@ -125,31 +99,23 @@ impl TxidToTxData { } impl AnyDatabaseGroup for TxidToTxData { - fn import() -> Self { - let metadata = Metadata::import(Self::root(), 2); + fn import(config: &Config) -> Self { + let path = config.path_databases().join("txid_to_tx_data"); + let metadata = Metadata::import(&path, 2); Self { + path, metadata, map: BTreeMap::default(), } } - fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { - fs::create_dir_all(Self::root()) - } - fn reset_metadata(&mut self) { self.metadata.reset(); } - fn folder<'a>() -> &'a str { - "txid_to_tx_data" - } - fn open_all(&mut self) { - let path = Self::root(); - - let folder = fs::read_dir(path); + let folder = fs::read_dir(&self.path); if folder.is_err() { return; @@ -180,7 +146,11 @@ impl AnyDatabaseGroup for TxidToTxData { .collect_vec() } - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { - self.metadata.export(height, date) + fn metadata(&mut self) -> &mut Metadata { + &mut self.metadata + } + + fn path(&self) -> &Path { + &self.path } } diff --git a/parser/src/databases/txout_index_to_address_index.rs b/_src/parser/databases/txout_index_to_address_index.rs similarity index 74% rename from parser/src/databases/txout_index_to_address_index.rs rename to _src/parser/databases/txout_index_to_address_index.rs index 79baeac10..4e060ddfc 100644 --- a/parser/src/databases/txout_index_to_address_index.rs +++ b/_src/parser/databases/txout_index_to_address_index.rs @@ -2,14 +2,16 @@ use std::{ collections::BTreeMap, fs, mem, ops::{Deref, DerefMut}, + path::{Path, PathBuf}, }; use allocative::Allocative; use itertools::Itertools; +use snkrj::{AnyDatabase, Database as _Database}; -use crate::structs::{Date, Height, TxoutIndex}; +use crate::structs::{Config, TxoutIndex}; -use super::{AnyDatabase, AnyDatabaseGroup, Database as _Database, Metadata}; +use super::{AnyDatabaseGroup, Metadata}; type Key = TxoutIndex; type Value = u32; @@ -17,8 +19,9 @@ type Database = _Database; #[derive(Allocative)] pub struct TxoutIndexToAddressIndex { + path: PathBuf, pub metadata: Metadata, - + #[allocative(skip)] map: BTreeMap, } @@ -39,20 +42,12 @@ impl DerefMut for TxoutIndexToAddressIndex { const DB_MAX_SIZE: usize = 10_000_000_000; impl TxoutIndexToAddressIndex { - pub fn unsafe_insert(&mut self, key: Key, value: Value) -> Option { + pub fn insert_to_ram(&mut self, key: Key, value: Value) -> Option { self.metadata.called_insert(); - self.open_db(&key).unsafe_insert(key, value) + self.open_db(&key).insert_to_ram(key, value) } - // pub fn undo_insert(&mut self, key: &Key) -> Option { - // self.open_db(key).remove_from_puts(key).map(|v| { - // self.metadata.called_remove(); - - // v - // }) - // } - pub fn remove(&mut self, key: &Key) -> Option { self.metadata.called_remove(); @@ -69,6 +64,7 @@ impl TxoutIndexToAddressIndex { pub fn open_db(&mut self, key: &Key) -> &mut Database { let db_index = Self::db_index(key); + let path = self.path.to_owned(); self.entry(db_index).or_insert_with(|| { let db_name = format!( @@ -77,7 +73,7 @@ impl TxoutIndexToAddressIndex { (db_index + 1) * DB_MAX_SIZE ); - let path = Self::root().join(db_name); + let path = path.join(db_name); Database::open(path).unwrap() }) @@ -89,30 +85,21 @@ impl TxoutIndexToAddressIndex { } impl AnyDatabaseGroup for TxoutIndexToAddressIndex { - fn import() -> Self { + fn import(config: &Config) -> Self { + let path = config.path_databases().join("txout_index_to_address_index"); Self { - metadata: Metadata::import(Self::root(), 1), - + metadata: Metadata::import(&path, 1), + path, map: BTreeMap::default(), } } - fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { - fs::create_dir_all(Self::root()) - } - fn reset_metadata(&mut self) { self.metadata.reset(); } - fn folder<'a>() -> &'a str { - "txout_index_to_address_index" - } - fn open_all(&mut self) { - let path = Self::root(); - - let folder = fs::read_dir(path); + let folder = fs::read_dir(&self.path); if folder.is_err() { return; @@ -151,7 +138,11 @@ impl AnyDatabaseGroup for TxoutIndexToAddressIndex { .collect_vec() } - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { - self.metadata.export(height, date) + fn metadata(&mut self) -> &mut Metadata { + &mut self.metadata + } + + fn path(&self) -> &Path { + &self.path } } diff --git a/parser/src/databases/txout_index_to_amount.rs b/_src/parser/databases/txout_index_to_amount.rs similarity index 74% rename from parser/src/databases/txout_index_to_amount.rs rename to _src/parser/databases/txout_index_to_amount.rs index 6774dfdb4..bc86a2c2e 100644 --- a/parser/src/databases/txout_index_to_amount.rs +++ b/_src/parser/databases/txout_index_to_amount.rs @@ -2,14 +2,16 @@ use std::{ collections::BTreeMap, fs, mem, ops::{Deref, DerefMut}, + path::{Path, PathBuf}, }; use allocative::Allocative; use itertools::Itertools; +use snkrj::{AnyDatabase, Database as _Database}; -use crate::structs::{Amount, Date, Height, TxoutIndex}; +use crate::structs::{Amount, Config, TxoutIndex}; -use super::{AnyDatabase, AnyDatabaseGroup, Database as _Database, Metadata}; +use super::{AnyDatabaseGroup, Metadata}; type Key = TxoutIndex; type Value = Amount; @@ -17,8 +19,9 @@ type Database = _Database; #[derive(Allocative)] pub struct TxoutIndexToAmount { + path: PathBuf, pub metadata: Metadata, - + #[allocative(skip)] map: BTreeMap, } @@ -39,20 +42,12 @@ impl DerefMut for TxoutIndexToAmount { const DB_MAX_SIZE: usize = 10_000_000_000; impl TxoutIndexToAmount { - pub fn unsafe_insert(&mut self, key: Key, value: Value) -> Option { + pub fn insert_to_ram(&mut self, key: Key, value: Value) -> Option { self.metadata.called_insert(); - self.open_db(&key).unsafe_insert(key, value) + self.open_db(&key).insert_to_ram(key, value) } - // pub fn undo_insert(&mut self, key: &Key) -> Option { - // self.open_db(key).remove_from_puts(key).map(|v| { - // self.metadata.called_remove(); - - // v - // }) - // } - pub fn remove(&mut self, key: &Key) -> Option { self.metadata.called_remove(); @@ -69,6 +64,7 @@ impl TxoutIndexToAmount { pub fn open_db(&mut self, key: &Key) -> &mut Database { let db_index = Self::db_index(key); + let path = self.path.to_owned(); self.entry(db_index).or_insert_with(|| { let db_name = format!( @@ -77,7 +73,7 @@ impl TxoutIndexToAmount { (db_index + 1) * DB_MAX_SIZE ); - let path = Self::root().join(db_name); + let path = path.join(db_name); Database::open(path).unwrap() }) @@ -89,30 +85,21 @@ impl TxoutIndexToAmount { } impl AnyDatabaseGroup for TxoutIndexToAmount { - fn import() -> Self { + fn import(config: &Config) -> Self { + let path = config.path_databases().join("txout_index_to_amount"); Self { - metadata: Metadata::import(Self::root(), 1), - + metadata: Metadata::import(&path, 1), + path, map: BTreeMap::default(), } } - fn create_dir_all(&self) -> color_eyre::Result<(), std::io::Error> { - fs::create_dir_all(Self::root()) - } - fn reset_metadata(&mut self) { self.metadata.reset(); } - fn folder<'a>() -> &'a str { - "txout_index_to_amount" - } - fn open_all(&mut self) { - let path = Self::root(); - - let folder = fs::read_dir(path); + let folder = fs::read_dir(&self.path); if folder.is_err() { return; @@ -151,7 +138,11 @@ impl AnyDatabaseGroup for TxoutIndexToAmount { .collect_vec() } - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()> { - self.metadata.export(height, date) + fn metadata(&mut self) -> &mut Metadata { + &mut self.metadata + } + + fn path(&self) -> &Path { + &self.path } } diff --git a/parser/src/datasets/_traits/any_dataset.rs b/_src/parser/datasets/_traits/any_dataset.rs similarity index 99% rename from parser/src/datasets/_traits/any_dataset.rs rename to _src/parser/datasets/_traits/any_dataset.rs index 6a5091efc..4f5eba00b 100644 --- a/parser/src/datasets/_traits/any_dataset.rs +++ b/_src/parser/datasets/_traits/any_dataset.rs @@ -3,14 +3,14 @@ use rayon::prelude::*; use struct_iterable::Iterable; use crate::{ - datasets::{ + parser::datasets::{ cohort_metadata::AddressCohortMetadataDataset, ComputeData, DateRecapDataset, RatioDataset, SubDataset, }, structs::{ - AnyBiMap, AnyDateMap, AnyHeightMap, AnyMap, BiMap, Date, Height, MapKind, Timestamp, OHLC, + AnyBiMap, AnyDateMap, AnyHeightMap, AnyMap, BiMap, Date, DateMap, Height, HeightMap, + MapKind, Timestamp, OHLC, }, - DateMap, HeightMap, }; use super::{AnyDatasetGroup, MinInitialStates}; diff --git a/parser/src/datasets/_traits/any_dataset_group.rs b/_src/parser/datasets/_traits/any_dataset_group.rs similarity index 100% rename from parser/src/datasets/_traits/any_dataset_group.rs rename to _src/parser/datasets/_traits/any_dataset_group.rs diff --git a/parser/src/datasets/_traits/any_datasets.rs b/_src/parser/datasets/_traits/any_datasets.rs similarity index 100% rename from parser/src/datasets/_traits/any_datasets.rs rename to _src/parser/datasets/_traits/any_datasets.rs diff --git a/parser/src/datasets/_traits/min_initial_state.rs b/_src/parser/datasets/_traits/min_initial_state.rs similarity index 97% rename from parser/src/datasets/_traits/min_initial_state.rs rename to _src/parser/datasets/_traits/min_initial_state.rs index db747aea8..d89674eb7 100644 --- a/parser/src/datasets/_traits/min_initial_state.rs +++ b/_src/parser/datasets/_traits/min_initial_state.rs @@ -29,6 +29,10 @@ impl MinInitialStates { computed: MinInitialState::compute_from_datasets(datasets, Mode::Computed, config), } } + + pub fn min_last_height(&self) -> Option { + self.computed.last_height.min(self.inserted.last_height) + } } #[derive(Default, Debug, Clone, Copy, Allocative)] @@ -45,13 +49,6 @@ enum Mode { } impl MinInitialState { - // pub fn consume(&mut self, other: Self) { - // self.first_unsafe_date = other.first_unsafe_date; - // self.first_unsafe_height = other.first_unsafe_height; - // self.last_date = other.last_date; - // self.last_height = other.last_height; - // } - fn compute_from_datasets(datasets: &dyn AnyDatasets, mode: Mode, config: &Config) -> Self { match mode { Mode::Inserted => { diff --git a/parser/src/datasets/_traits/mod.rs b/_src/parser/datasets/_traits/mod.rs similarity index 100% rename from parser/src/datasets/_traits/mod.rs rename to _src/parser/datasets/_traits/mod.rs diff --git a/parser/src/datasets/address/all_metadata.rs b/_src/parser/datasets/address/all_metadata.rs similarity index 88% rename from parser/src/datasets/address/all_metadata.rs rename to _src/parser/datasets/address/all_metadata.rs index 124c27e6d..1087d62c6 100644 --- a/parser/src/datasets/address/all_metadata.rs +++ b/_src/parser/datasets/address/all_metadata.rs @@ -2,8 +2,8 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, - structs::{BiMap, Config, MapKind}, + parser::datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, + structs::{BiMap, Config, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -15,8 +15,8 @@ pub struct AllAddressesMetadataDataset { } impl AllAddressesMetadataDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(path: &MapPath, config: &Config) -> color_eyre::Result { + let f = |s: &str| path.join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/address/cohort.rs b/_src/parser/datasets/address/cohort.rs similarity index 96% rename from parser/src/datasets/address/cohort.rs rename to _src/parser/datasets/address/cohort.rs index c7d24ac82..f7676ad83 100644 --- a/parser/src/datasets/address/cohort.rs +++ b/_src/parser/datasets/address/cohort.rs @@ -3,9 +3,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates, SubDataset}, - states::{AddressCohortId, DurableStates}, - structs::{AddressSplit, BiMap, Config, Date, Height}, + parser::{ + datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates, SubDataset}, + states::{AddressCohortId, DurableStates}, + }, + structs::{AddressSplit, BiMap, Config, Date, Height, MapPath}, }; use super::cohort_metadata::AddressCohortMetadataDataset; @@ -23,7 +25,7 @@ pub struct CohortDataset { impl CohortDataset { pub fn import( - parent_path: &str, + path: &MapPath, id: AddressCohortId, config: &Config, ) -> color_eyre::Result { @@ -33,8 +35,8 @@ impl CohortDataset { let mut s = Self { min_initial_states: MinInitialStates::default(), split, - metadata: AddressCohortMetadataDataset::import(parent_path, &name, config)?, - subs: SubDataset::import(parent_path, &name, config)?, + metadata: AddressCohortMetadataDataset::import(path, &name, config)?, + subs: SubDataset::import(path, &name, config)?, }; s.min_initial_states diff --git a/parser/src/datasets/address/cohort_metadata.rs b/_src/parser/datasets/address/cohort_metadata.rs similarity index 87% rename from parser/src/datasets/address/cohort_metadata.rs rename to _src/parser/datasets/address/cohort_metadata.rs index 185cda2de..860596d0c 100644 --- a/parser/src/datasets/address/cohort_metadata.rs +++ b/_src/parser/datasets/address/cohort_metadata.rs @@ -2,8 +2,8 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, InsertData, MinInitialStates}, - structs::{BiMap, Config, MapKind}, + parser::datasets::{AnyDataset, InsertData, MinInitialStates}, + structs::{BiMap, Config, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -19,15 +19,15 @@ pub struct AddressCohortMetadataDataset { impl AddressCohortMetadataDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; diff --git a/parser/src/datasets/address/mod.rs b/_src/parser/datasets/address/mod.rs similarity index 93% rename from parser/src/datasets/address/mod.rs rename to _src/parser/datasets/address/mod.rs index 392bf8bd6..a8760c860 100644 --- a/parser/src/datasets/address/mod.rs +++ b/_src/parser/datasets/address/mod.rs @@ -7,9 +7,8 @@ use itertools::Itertools; use rayon::prelude::*; use crate::{ - states::SplitByAddressCohort, - structs::{BiMap, Config, Height}, - Date, + parser::states::SplitByAddressCohort, + structs::{BiMap, Config, Date, Height}, }; use self::{all_metadata::AllAddressesMetadataDataset, cohort::CohortDataset}; @@ -26,13 +25,15 @@ pub struct AddressDatasets { } impl AddressDatasets { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { + pub fn import(config: &Config) -> color_eyre::Result { let mut cohorts = SplitByAddressCohort::>::default(); + let path_dataset = config.path_datasets(); + cohorts .as_vec() .into_par_iter() - .map(|(_, id)| (id, CohortDataset::import(parent_path, id, config))) + .map(|(_, id)| (id, CohortDataset::import(&path_dataset, id, config))) .collect::>() .into_iter() .try_for_each(|(id, dataset)| -> color_eyre::Result<()> { @@ -43,7 +44,7 @@ impl AddressDatasets { let mut s = Self { min_initial_states: MinInitialStates::default(), - metadata: AllAddressesMetadataDataset::import(parent_path, config)?, + metadata: AllAddressesMetadataDataset::import(&path_dataset, config)?, cohorts: cohorts.unwrap(), }; diff --git a/parser/src/datasets/block_metadata.rs b/_src/parser/datasets/block_metadata.rs similarity index 87% rename from parser/src/datasets/block_metadata.rs rename to _src/parser/datasets/block_metadata.rs index aaa6ddfe9..1bfd17b0a 100644 --- a/parser/src/datasets/block_metadata.rs +++ b/_src/parser/datasets/block_metadata.rs @@ -2,7 +2,7 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::AnyDataset, + parser::datasets::AnyDataset, structs::{Config, Date, HeightMap, MapKind, Timestamp}, }; @@ -16,8 +16,8 @@ pub struct BlockMetadataDataset { } impl BlockMetadataDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let f = |s: &str| config.path_datasets().join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/coindays.rs b/_src/parser/datasets/coindays.rs similarity index 87% rename from parser/src/datasets/coindays.rs rename to _src/parser/datasets/coindays.rs index aae63dfc1..c6eca0151 100644 --- a/parser/src/datasets/coindays.rs +++ b/_src/parser/datasets/coindays.rs @@ -2,9 +2,8 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::AnyDataset, - structs::{Config, MapKind}, - DateMap, HeightMap, + parser::datasets::AnyDataset, + structs::{Config, DateMap, HeightMap, MapKind}, }; use super::{InsertData, MinInitialStates}; @@ -17,8 +16,8 @@ pub struct CoindaysDataset { } impl CoindaysDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let f = |s: &str| config.path_datasets().join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/cointime.rs b/_src/parser/datasets/cointime.rs similarity index 97% rename from parser/src/datasets/cointime.rs rename to _src/parser/datasets/cointime.rs index 0cd6d6773..2c4b79271 100644 --- a/parser/src/datasets/cointime.rs +++ b/_src/parser/datasets/cointime.rs @@ -2,9 +2,8 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - structs::{BiMap, Config, DateMap, Height, MapKind}, + structs::{BiMap, Config, DateMap, Height, HeightMap, MapKind}, utils::{ONE_DAY_IN_DAYS, ONE_YEAR_IN_DAYS, THREE_MONTHS_IN_DAYS, TWO_WEEK_IN_DAYS}, - HeightMap, }; use super::{AnyDataset, ComputeData, InsertData, MinInitialStates, RatioDataset}; @@ -72,8 +71,9 @@ pub struct CointimeDataset { } impl CointimeDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let path_dataset = config.path_datasets(); + let f = |s: &str| path_dataset.join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), @@ -93,7 +93,7 @@ impl CointimeDataset { // Computed active_cap: BiMap::new_bin(1, MapKind::Computed, &f("active_cap")), active_price: BiMap::new_bin(1, MapKind::Computed, &f("active_price")), - active_price_ratio: RatioDataset::import(parent_path, "active_price", config)?, + active_price_ratio: RatioDataset::import(&path_dataset, "active_price", config)?, active_supply: BiMap::new_bin(1, MapKind::Computed, &f("active_supply")), active_supply_3m_net_change: BiMap::new_bin( 1, @@ -140,7 +140,7 @@ impl CointimeDataset { ), cointime_cap: BiMap::new_bin(1, MapKind::Computed, &f("cointime_cap")), cointime_price: BiMap::new_bin(1, MapKind::Computed, &f("cointime_price")), - cointime_price_ratio: RatioDataset::import(parent_path, "cointime_price", config)?, + cointime_price_ratio: RatioDataset::import(&path_dataset, "cointime_price", config)?, cointime_value_created: HeightMap::new_bin( 1, MapKind::Computed, @@ -237,7 +237,11 @@ impl CointimeDataset { &f("true_market_deviation"), ), true_market_mean: BiMap::new_bin(1, MapKind::Computed, &f("true_market_mean")), - true_market_mean_ratio: RatioDataset::import(parent_path, "true_market_mean", config)?, + true_market_mean_ratio: RatioDataset::import( + &path_dataset, + "true_market_mean", + config, + )?, true_market_net_unrealized_profit_and_loss: BiMap::new_bin( 1, MapKind::Computed, @@ -245,7 +249,7 @@ impl CointimeDataset { ), vaulted_cap: BiMap::new_bin(1, MapKind::Computed, &f("vaulted_cap")), vaulted_price: BiMap::new_bin(1, MapKind::Computed, &f("vaulted_price")), - vaulted_price_ratio: RatioDataset::import(parent_path, "vaulted_price", config)?, + vaulted_price_ratio: RatioDataset::import(&path_dataset, "vaulted_price", config)?, vaulted_supply: BiMap::new_bin(1, MapKind::Computed, &f("vaulted_supply")), vaulted_supply_3m_net_change: BiMap::new_bin( 1, diff --git a/parser/src/datasets/constant.rs b/_src/parser/datasets/constant.rs similarity index 90% rename from parser/src/datasets/constant.rs rename to _src/parser/datasets/constant.rs index ae94965ae..4f1dce63f 100644 --- a/parser/src/datasets/constant.rs +++ b/_src/parser/datasets/constant.rs @@ -16,8 +16,8 @@ pub struct ConstantDataset { } impl ConstantDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let f = |s: &str| config.path_datasets().join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/date_metadata.rs b/_src/parser/datasets/date_metadata.rs similarity index 88% rename from parser/src/datasets/date_metadata.rs rename to _src/parser/datasets/date_metadata.rs index 4f69ec24f..80f8fa9d6 100644 --- a/parser/src/datasets/date_metadata.rs +++ b/_src/parser/datasets/date_metadata.rs @@ -2,7 +2,7 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::AnyDataset, + parser::datasets::AnyDataset, structs::{Config, DateMap, Height, MapKind}, }; @@ -17,8 +17,8 @@ pub struct DateMetadataDataset { } impl DateMetadataDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let f = |s: &str| config.path_datasets().join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/mining.rs b/_src/parser/datasets/mining.rs similarity index 99% rename from parser/src/datasets/mining.rs rename to _src/parser/datasets/mining.rs index 759fcf35b..667b0a8a7 100644 --- a/parser/src/datasets/mining.rs +++ b/_src/parser/datasets/mining.rs @@ -4,7 +4,7 @@ use ordered_float::OrderedFloat; use struct_iterable::Iterable; use crate::{ - datasets::AnyDataset, + parser::datasets::AnyDataset, structs::{Amount, BiMap, Config, DateMap, Height, HeightMap, MapKey, MapKind}, utils::{ BYTES_IN_MB, ONE_DAY_IN_DAYS, ONE_MONTH_IN_DAYS, ONE_WEEK_IN_DAYS, ONE_YEAR_IN_DAYS, @@ -123,8 +123,8 @@ pub struct MiningDataset { } impl MiningDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let f = |s: &str| config.path_datasets().join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/mod.rs b/_src/parser/datasets/mod.rs similarity index 66% rename from parser/src/datasets/mod.rs rename to _src/parser/datasets/mod.rs index 43a8c94c1..e8b797b18 100644 --- a/parser/src/datasets/mod.rs +++ b/_src/parser/datasets/mod.rs @@ -1,9 +1,10 @@ -use std::{collections::BTreeMap, fs, ops::RangeInclusive, path::Path}; +use std::{collections::BTreeMap, ops::RangeInclusive}; use allocative::Allocative; use itertools::Itertools; +use log::info; use rayon::prelude::*; mod _traits; @@ -34,16 +35,18 @@ pub use transaction::*; pub use utxo::*; use crate::{ - databases::Databases, - io::{Json, JSON_EXTENSION}, - states::{ - AddressCohortsInputStates, - AddressCohortsOneShotStates, - AddressCohortsRealizedStates, - States, - UTXOCohortsOneShotStates, - // UTXOCohortsReceivedStates, - UTXOCohortsSentStates, + io::Json, + parser::{ + databases::Databases, + states::{ + AddressCohortsInputStates, + AddressCohortsOneShotStates, + AddressCohortsRealizedStates, + States, + UTXOCohortsOneShotStates, + // UTXOCohortsReceivedStates, + UTXOCohortsSentStates, + }, }, structs::{Amount, Config, Date, Height, Price, Timestamp}, }; @@ -84,7 +87,7 @@ pub struct ComputeData<'a> { } #[derive(Allocative)] -pub struct AllDatasets { +pub struct Datasets { min_initial_states: MinInitialStates, pub constant: ConstantDataset, @@ -99,31 +102,27 @@ pub struct AllDatasets { pub utxo: UTXODatasets, } -const DATASETS_PATH: &str = "../datasets"; - -impl AllDatasets { +impl Datasets { pub fn import(config: &Config) -> color_eyre::Result { - let path = DATASETS_PATH; + let price = PriceDatasets::import(config)?; - let price = PriceDatasets::import(path, config)?; + let constant = ConstantDataset::import(config)?; - let constant = ConstantDataset::import(path, config)?; + let date_metadata = DateMetadataDataset::import(config)?; - let date_metadata = DateMetadataDataset::import(path, config)?; + let cointime = CointimeDataset::import(config)?; - let cointime = CointimeDataset::import(path, config)?; + let coindays = CoindaysDataset::import(config)?; - let coindays = CoindaysDataset::import(path, config)?; + let mining = MiningDataset::import(config)?; - let mining = MiningDataset::import(path, config)?; + let block_metadata = BlockMetadataDataset::import(config)?; - let block_metadata = BlockMetadataDataset::import(path, config)?; + let transaction = TransactionDataset::import(config)?; - let transaction = TransactionDataset::import(path, config)?; + let address = AddressDatasets::import(config)?; - let address = AddressDatasets::import(path, config)?; - - let utxo = UTXODatasets::import(path, config)?; + let utxo = UTXODatasets::import(config)?; let mut s = Self { min_initial_states: MinInitialStates::default(), @@ -140,14 +139,18 @@ impl AllDatasets { utxo, }; - s.min_initial_states - .consume(MinInitialStates::compute_from_datasets(&s, config)); + s.set_initial_states(config); - s.export_meta_files()?; + info!("Imported datasets"); Ok(s) } + fn set_initial_states(&mut self, config: &Config) { + self.min_initial_states + .consume(MinInitialStates::compute_from_datasets(self, config)); + } + pub fn insert(&mut self, insert_data: InsertData) { if insert_data.compute_addresses { self.address.insert(&insert_data); @@ -229,21 +232,6 @@ impl AllDatasets { &mut self.price.market_cap, ); - // No compute needed for now - // if self.block_metadata.should_compute(height, date) { - // self.block_metadata.compute(&compute_data); - // } - - // No compute needed for now - // if self.date_metadata.should_compute(height, date) { - // self.date_metadata.compute(&compute_data); - // } - - // No compute needed for now - // if self.coindays.should_compute(height, date) { - // self.coindays.compute(&compute_data); - // } - if self.transaction.should_compute(&compute_data) { self.transaction.compute( &compute_data, @@ -269,43 +257,12 @@ impl AllDatasets { } } - pub fn export_meta_files(&self) -> color_eyre::Result<()> { - let mut path_to_type: BTreeMap<&Path, &str> = self - .to_any_dataset_vec() - .into_iter() - .flat_map(|dataset| dataset.to_all_map_vec()) - .flat_map(|map| map.exported_path_with_t_name()) - .collect(); + pub fn export(&mut self, config: &Config, height: Height) -> color_eyre::Result<()> { + let is_new = self + .min_initial_states + .min_last_height() + .map_or(true, |last| last <= height); - path_to_type.insert(Path::new("../datasets/last"), "Value"); - - let datasets_len = path_to_type.len(); - - let server_inputs_path = "../server/in"; - - fs::create_dir_all(server_inputs_path)?; - - Json::export( - Path::new(&format!("{server_inputs_path}/disk_path_to_type.json")), - &path_to_type, - )?; - - let datasets_len_path = format!("{server_inputs_path}/datasets_len.txt"); - - if let Ok(len) = fs::read_to_string(&datasets_len_path) { - if let Ok(len) = len.parse::() { - if datasets_len == len { - return Ok(()); - } - } - } - - fs::write(datasets_len_path, datasets_len.to_string())?; - - Ok(()) - } - - pub fn export(&mut self) -> color_eyre::Result<()> { self.to_mut_any_dataset_vec() .into_iter() .for_each(|dataset| dataset.pre_export()); @@ -321,39 +278,28 @@ impl AllDatasets { .for_each(|dataset| { dataset.post_export(); - dataset.to_all_map_vec().iter().for_each(|map| { - if let Some(last_path) = map.path_last() { - if let Some(last_value) = map.last_value() { - let mut last_path = last_path.clone(); - last_path.pop(); - - let last_path = last_path.to_str().unwrap(); - - let skip = if last_path.starts_with(DATASETS_PATH) { - 2 - } else { - 1 - }; - - path_to_last.insert( - last_path.split('/').skip(skip).join("-").to_string(), - last_value, - ); + if is_new { + dataset.to_all_map_vec().iter().for_each(|map| { + if map.path_last().is_some() { + if let Some(last_value) = map.last_value() { + path_to_last.insert(map.id(config), last_value); + } } - } - }); + }); + } }); - Json::export( - Path::new(&format!("{DATASETS_PATH}/last.{JSON_EXTENSION}")), - &path_to_last, - )?; + if is_new { + Json::export(&config.path_datasets_last_values(), &path_to_last)?; + } + + self.set_initial_states(config); Ok(()) } } -impl AnyDatasets for AllDatasets { +impl AnyDatasets for Datasets { fn get_min_initial_states(&self) -> &MinInitialStates { &self.min_initial_states } diff --git a/parser/src/datasets/price.rs b/_src/parser/datasets/price.rs similarity index 93% rename from parser/src/datasets/price.rs rename to _src/parser/datasets/price.rs index d2735c4ee..ca79bd945 100644 --- a/parser/src/datasets/price.rs +++ b/_src/parser/datasets/price.rs @@ -7,7 +7,7 @@ use color_eyre::eyre::Error; use struct_iterable::Iterable; use crate::{ - price::{Binance, Kibo, Kraken}, + parser::price::{Binance, Kibo, Kraken}, structs::{ Amount, BiMap, Config, Date, DateMap, DateMapChunkId, Height, HeightMapChunkId, MapKey, MapKind, Timestamp, OHLC, @@ -85,10 +85,9 @@ pub struct PriceDatasets { } impl PriceDatasets { - pub fn import(datasets_path: &str, config: &Config) -> color_eyre::Result { - let price_path = "../price"; - - let f = |s: &str| format!("{datasets_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let path_dataset = config.path_datasets(); + let f = |s: &str| path_dataset.join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), @@ -104,7 +103,7 @@ impl PriceDatasets { // --- // Inserted // --- - ohlc: BiMap::new_json(1, MapKind::Inserted, price_path), + ohlc: BiMap::new_json(1, MapKind::Inserted, &config.path_price()), // --- // Computed @@ -115,31 +114,31 @@ impl PriceDatasets { close: BiMap::new_bin(1, MapKind::Computed, &f("close")), market_cap: BiMap::new_bin(1, MapKind::Computed, &f("market_cap")), price_1w_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_1w_sma")), - price_1w_sma_ratio: RatioDataset::import(datasets_path, "price_1w_sma", config)?, + price_1w_sma_ratio: RatioDataset::import(&path_dataset, "price_1w_sma", config)?, price_1m_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_1m_sma")), - price_1m_sma_ratio: RatioDataset::import(datasets_path, "price_1m_sma", config)?, + price_1m_sma_ratio: RatioDataset::import(&path_dataset, "price_1m_sma", config)?, price_1y_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_1y_sma")), - price_1y_sma_ratio: RatioDataset::import(datasets_path, "price_1y_sma", config)?, + price_1y_sma_ratio: RatioDataset::import(&path_dataset, "price_1y_sma", config)?, price_2y_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_2y_sma")), - price_2y_sma_ratio: RatioDataset::import(datasets_path, "price_2y_sma", config)?, + price_2y_sma_ratio: RatioDataset::import(&path_dataset, "price_2y_sma", config)?, price_4y_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_4y_sma")), - price_4y_sma_ratio: RatioDataset::import(datasets_path, "price_4y_sma", config)?, + price_4y_sma_ratio: RatioDataset::import(&path_dataset, "price_4y_sma", config)?, price_8d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_8d_sma")), - price_8d_sma_ratio: RatioDataset::import(datasets_path, "price_8d_sma", config)?, + price_8d_sma_ratio: RatioDataset::import(&path_dataset, "price_8d_sma", config)?, price_13d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_13d_sma")), - price_13d_sma_ratio: RatioDataset::import(datasets_path, "price_13d_sma", config)?, + price_13d_sma_ratio: RatioDataset::import(&path_dataset, "price_13d_sma", config)?, price_21d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_21d_sma")), - price_21d_sma_ratio: RatioDataset::import(datasets_path, "price_21d_sma", config)?, + price_21d_sma_ratio: RatioDataset::import(&path_dataset, "price_21d_sma", config)?, price_34d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_34d_sma")), - price_34d_sma_ratio: RatioDataset::import(datasets_path, "price_34d_sma", config)?, + price_34d_sma_ratio: RatioDataset::import(&path_dataset, "price_34d_sma", config)?, price_55d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_55d_sma")), - price_55d_sma_ratio: RatioDataset::import(datasets_path, "price_55d_sma", config)?, + price_55d_sma_ratio: RatioDataset::import(&path_dataset, "price_55d_sma", config)?, price_89d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_89d_sma")), - price_89d_sma_ratio: RatioDataset::import(datasets_path, "price_89d_sma", config)?, + price_89d_sma_ratio: RatioDataset::import(&path_dataset, "price_89d_sma", config)?, price_144d_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_144d_sma")), - price_144d_sma_ratio: RatioDataset::import(datasets_path, "price_144d_sma", config)?, + price_144d_sma_ratio: RatioDataset::import(&path_dataset, "price_144d_sma", config)?, price_200w_sma: BiMap::new_bin(1, MapKind::Computed, &f("price_200w_sma")), - price_200w_sma_ratio: RatioDataset::import(datasets_path, "price_200w_sma", config)?, + price_200w_sma_ratio: RatioDataset::import(&path_dataset, "price_200w_sma", config)?, price_1d_total_return: DateMap::new_bin( 1, MapKind::Computed, @@ -540,6 +539,7 @@ impl PriceDatasets { height: Height, timestamp: Timestamp, previous_timestamp: Option, + config: &Config, ) -> color_eyre::Result { if let Some(ohlc) = self.ohlc.height.get_or_import(&height) { return Ok(ohlc); @@ -558,7 +558,7 @@ impl PriceDatasets { .unwrap_or_else(|_| { self.get_from_1mn_binance(timestamp, previous_timestamp) .unwrap_or_else(|_| { - self.get_from_har_binance(timestamp, previous_timestamp) + self.get_from_har_binance(timestamp, previous_timestamp, config) .unwrap_or_else(|_| { self.get_from_height_kibo(&height).unwrap_or_else(|_| { let date = timestamp.to_date(); @@ -659,10 +659,11 @@ How to fix this: &mut self, timestamp: Timestamp, previous_timestamp: Option, + config: &Config, ) -> color_eyre::Result { if self.binance_har.is_none() { self.binance_har - .replace(Binance::read_har_file().unwrap_or_default()); + .replace(Binance::read_har_file(config).unwrap_or_default()); } Self::find_height_ohlc( diff --git a/parser/src/datasets/subs/capitalization.rs b/_src/parser/datasets/subs/capitalization.rs similarity index 90% rename from parser/src/datasets/subs/capitalization.rs rename to _src/parser/datasets/subs/capitalization.rs index 20a792662..834f19a3e 100644 --- a/parser/src/datasets/subs/capitalization.rs +++ b/_src/parser/datasets/subs/capitalization.rs @@ -2,9 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, - states::CapitalizationState, - structs::{BiMap, Config, MapKind}, + parser::{ + datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, + states::CapitalizationState, + }, + structs::{BiMap, Config, MapKind, MapPath}, utils::ONE_MONTH_IN_DAYS, }; @@ -22,15 +24,15 @@ pub struct CapitalizationDataset { impl CapitalizationDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; @@ -52,7 +54,7 @@ impl CapitalizationDataset { ), realized_price: BiMap::new_bin(1, MapKind::Computed, &f("realized_price")), realized_price_ratio: RatioDataset::import( - parent_path, + path, &format!( "{}realized_price", name.as_ref().map_or("".to_owned(), |n| format!("{n}-")) diff --git a/parser/src/datasets/subs/input.rs b/_src/parser/datasets/subs/input.rs similarity index 87% rename from parser/src/datasets/subs/input.rs rename to _src/parser/datasets/subs/input.rs index b9091205e..f7522cc03 100644 --- a/parser/src/datasets/subs/input.rs +++ b/_src/parser/datasets/subs/input.rs @@ -2,10 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, InsertData, MinInitialStates}, - states::InputState, - structs::{BiMap, Config, MapKind}, - DateMap, HeightMap, + parser::{ + datasets::{AnyDataset, InsertData, MinInitialStates}, + states::InputState, + }, + structs::{BiMap, Config, DateMap, HeightMap, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -22,15 +23,15 @@ pub struct InputSubDataset { impl InputSubDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; diff --git a/parser/src/datasets/subs/mod.rs b/_src/parser/datasets/subs/mod.rs similarity index 95% rename from parser/src/datasets/subs/mod.rs rename to _src/parser/datasets/subs/mod.rs index c34be68cd..566cd8388 100644 --- a/parser/src/datasets/subs/mod.rs +++ b/_src/parser/datasets/subs/mod.rs @@ -21,7 +21,10 @@ pub use supply::*; pub use unrealized::*; pub use utxo::*; -use crate::{datasets::AnyDataset, structs::Config}; +use crate::{ + parser::datasets::AnyDataset, + structs::{Config, MapPath}, +}; use super::AnyDatasetGroup; @@ -39,7 +42,7 @@ pub struct SubDataset { impl SubDataset { pub fn import( - parent_path: &str, + parent_path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { diff --git a/parser/src/datasets/subs/output.rs b/_src/parser/datasets/subs/output.rs similarity index 100% rename from parser/src/datasets/subs/output.rs rename to _src/parser/datasets/subs/output.rs diff --git a/parser/src/datasets/subs/price_paid.rs b/_src/parser/datasets/subs/price_paid.rs similarity index 96% rename from parser/src/datasets/subs/price_paid.rs rename to _src/parser/datasets/subs/price_paid.rs index ca06ef2b5..7f1bba43d 100644 --- a/parser/src/datasets/subs/price_paid.rs +++ b/_src/parser/datasets/subs/price_paid.rs @@ -2,9 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, InsertData, MinInitialStates}, - states::PricePaidState, - structs::{BiMap, Config, Date, Height, MapKind}, + parser::{ + datasets::{AnyDataset, InsertData, MinInitialStates}, + states::PricePaidState, + }, + structs::{BiMap, Config, Date, Height, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -34,15 +36,15 @@ pub struct PricePaidSubDataset { impl PricePaidSubDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; diff --git a/parser/src/datasets/subs/ratio.rs b/_src/parser/datasets/subs/ratio.rs similarity index 93% rename from parser/src/datasets/subs/ratio.rs rename to _src/parser/datasets/subs/ratio.rs index d7ca03c3e..270f9dde0 100644 --- a/parser/src/datasets/subs/ratio.rs +++ b/_src/parser/datasets/subs/ratio.rs @@ -2,8 +2,8 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, MinInitialStates}, - structs::{BiMap, Config, MapKind}, + parser::datasets::{AnyDataset, ComputeData, MinInitialStates}, + structs::{BiMap, Config, MapKind, MapPath}, utils::{ONE_MONTH_IN_DAYS, ONE_WEEK_IN_DAYS, ONE_YEAR_IN_DAYS}, }; @@ -31,9 +31,9 @@ pub struct RatioDataset { } impl RatioDataset { - pub fn import(parent_path: &str, name: &str, config: &Config) -> color_eyre::Result { - let f_ratio = |s: &str| format!("{parent_path}/market_price_to_{name}_{s}"); - let f_price = |s: &str| format!("{parent_path}/{name}_{s}"); + pub fn import(path: &MapPath, name: &str, config: &Config) -> color_eyre::Result { + let f_ratio = |s: &str| path.join(&format!("market_price_to_{name}_{s}")); + let f_price = |s: &str| path.join(&format!("{name}_{s}")); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/subs/realized.rs b/_src/parser/datasets/subs/realized.rs similarity index 97% rename from parser/src/datasets/subs/realized.rs rename to _src/parser/datasets/subs/realized.rs index 005d4b389..c5c4ae98b 100644 --- a/parser/src/datasets/subs/realized.rs +++ b/_src/parser/datasets/subs/realized.rs @@ -2,11 +2,12 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, - states::RealizedState, - structs::{BiMap, Config, MapKind, Price}, + parser::{ + datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, + states::RealizedState, + }, + structs::{BiMap, Config, DateMap, HeightMap, MapKind, MapPath, Price}, utils::ONE_MONTH_IN_DAYS, - DateMap, HeightMap, }; #[derive(Allocative, Iterable)] @@ -45,18 +46,17 @@ pub struct RealizedSubDataset { impl RealizedSubDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; - let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/subs/recap.rs b/_src/parser/datasets/subs/recap.rs similarity index 95% rename from parser/src/datasets/subs/recap.rs rename to _src/parser/datasets/subs/recap.rs index e5661fa53..7dac8d9d3 100644 --- a/parser/src/datasets/subs/recap.rs +++ b/_src/parser/datasets/subs/recap.rs @@ -3,12 +3,14 @@ use std::{iter::Sum, ops::Add}; use allocative::Allocative; use crate::{ - structs::{DateMapChunkId, GenericMap, MapKey, MapKind, MapSerialized, MapValue}, + structs::{ + Date, DateMapChunkId, GenericMap, MapChunkId, MapKey, MapKind, MapPath, MapSerialized, + MapValue, SerializedDateMap, + }, utils::{get_percentile, LossyFrom}, - Date, MapChunkId, SerializedBTreeMap, }; -pub type DateRecapDataset = RecapDataset>; +pub type DateRecapDataset = RecapDataset>; #[derive(Allocative)] pub struct RecapDataset { @@ -91,8 +93,8 @@ where Key: MapKey, Serialized: MapSerialized, { - pub fn import(parent_path: &str, options: RecapOptions) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(path: &MapPath, options: RecapOptions) -> color_eyre::Result { + let f = |s: &str| path.join(s); let s = Self { // --- diff --git a/parser/src/datasets/subs/supply.rs b/_src/parser/datasets/subs/supply.rs similarity index 90% rename from parser/src/datasets/subs/supply.rs rename to _src/parser/datasets/subs/supply.rs index 316ae0904..7fe41efec 100644 --- a/parser/src/datasets/subs/supply.rs +++ b/_src/parser/datasets/subs/supply.rs @@ -2,9 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, - states::SupplyState, - structs::{BiMap, Config, MapKind}, + parser::{ + datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, + states::SupplyState, + }, + structs::{BiMap, Config, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -19,15 +21,15 @@ pub struct SupplySubDataset { impl SupplySubDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; diff --git a/parser/src/datasets/subs/unrealized.rs b/_src/parser/datasets/subs/unrealized.rs similarity index 95% rename from parser/src/datasets/subs/unrealized.rs rename to _src/parser/datasets/subs/unrealized.rs index 7801cf243..6290f589b 100644 --- a/parser/src/datasets/subs/unrealized.rs +++ b/_src/parser/datasets/subs/unrealized.rs @@ -2,9 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, - states::UnrealizedState, - structs::{BiMap, Config, MapKind}, + parser::{ + datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates}, + states::UnrealizedState, + }, + structs::{BiMap, Config, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -26,15 +28,15 @@ pub struct UnrealizedSubDataset { impl UnrealizedSubDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; diff --git a/parser/src/datasets/subs/utxo.rs b/_src/parser/datasets/subs/utxo.rs similarity index 83% rename from parser/src/datasets/subs/utxo.rs rename to _src/parser/datasets/subs/utxo.rs index 61ecf9f90..4f5a071ae 100644 --- a/parser/src/datasets/subs/utxo.rs +++ b/_src/parser/datasets/subs/utxo.rs @@ -2,9 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, InsertData, MinInitialStates}, - states::UTXOState, - structs::{BiMap, Config, MapKind}, + parser::{ + datasets::{AnyDataset, InsertData, MinInitialStates}, + states::UTXOState, + }, + structs::{BiMap, Config, MapKind, MapPath}, }; #[derive(Allocative, Iterable)] @@ -16,15 +18,15 @@ pub struct UTXOSubDataset { impl UTXOSubDataset { pub fn import( - parent_path: &str, + path: &MapPath, name: &Option, config: &Config, ) -> color_eyre::Result { let f = |s: &str| { if let Some(name) = name { - format!("{parent_path}/{name}/{s}") + path.join(&format!("{name}/{s}")) } else { - format!("{parent_path}/{s}") + path.join(s) } }; diff --git a/parser/src/datasets/transaction.rs b/_src/parser/datasets/transaction.rs similarity index 97% rename from parser/src/datasets/transaction.rs rename to _src/parser/datasets/transaction.rs index 7ab3ae300..94704de63 100644 --- a/parser/src/datasets/transaction.rs +++ b/_src/parser/datasets/transaction.rs @@ -2,12 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::InsertData, - structs::{BiMap, Config, HeightMap, MapKind}, + parser::datasets::InsertData, + structs::{BiMap, Config, DateMap, HeightMap, MapKind}, utils::{ ONE_DAY_IN_S, ONE_MONTH_IN_DAYS, ONE_WEEK_IN_DAYS, ONE_YEAR_IN_DAYS, TARGET_BLOCKS_PER_DAY, }, - DateMap, }; use super::{AnyDataset, ComputeData, MinInitialStates}; @@ -52,8 +51,8 @@ pub struct TransactionDataset { } impl TransactionDataset { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { - let f = |s: &str| format!("{parent_path}/{s}"); + pub fn import(config: &Config) -> color_eyre::Result { + let f = |s: &str| config.path_datasets().join(s); let mut s = Self { min_initial_states: MinInitialStates::default(), diff --git a/parser/src/datasets/utxo/dataset.rs b/_src/parser/datasets/utxo/dataset.rs similarity index 96% rename from parser/src/datasets/utxo/dataset.rs rename to _src/parser/datasets/utxo/dataset.rs index 96b878ddf..01087dbb5 100644 --- a/parser/src/datasets/utxo/dataset.rs +++ b/_src/parser/datasets/utxo/dataset.rs @@ -2,9 +2,11 @@ use allocative::Allocative; use struct_iterable::Iterable; use crate::{ - datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates, SubDataset}, - states::UTXOCohortId, - structs::{BiMap, Config, Date, Height}, + parser::{ + datasets::{AnyDataset, ComputeData, InsertData, MinInitialStates, SubDataset}, + states::UTXOCohortId, + }, + structs::{BiMap, Config, Date, Height, MapPath}, }; #[derive(Allocative, Iterable)] @@ -18,7 +20,7 @@ pub struct UTXODataset { impl UTXODataset { pub fn import( - parent_path: &str, + parent_path: &MapPath, id: UTXOCohortId, config: &Config, ) -> color_eyre::Result { diff --git a/parser/src/datasets/utxo/mod.rs b/_src/parser/datasets/utxo/mod.rs similarity index 94% rename from parser/src/datasets/utxo/mod.rs rename to _src/parser/datasets/utxo/mod.rs index d9bc811bd..c9d0390b4 100644 --- a/parser/src/datasets/utxo/mod.rs +++ b/_src/parser/datasets/utxo/mod.rs @@ -7,8 +7,8 @@ use rayon::prelude::*; use itertools::Itertools; use crate::{ - datasets::AnyDatasets, - states::{SplitByUTXOCohort, UTXOCohortId}, + parser::datasets::AnyDatasets, + parser::states::{SplitByUTXOCohort, UTXOCohortId}, structs::{BiMap, Config, Date, Height}, }; @@ -22,13 +22,15 @@ pub struct UTXODatasets { } impl UTXODatasets { - pub fn import(parent_path: &str, config: &Config) -> color_eyre::Result { + pub fn import(config: &Config) -> color_eyre::Result { let mut cohorts = SplitByUTXOCohort::>::default(); + let path_dataset = config.path_datasets(); + cohorts .as_vec() .into_par_iter() - .map(|(_, id)| (id, UTXODataset::import(parent_path, id, config))) + .map(|(_, id)| (id, UTXODataset::import(&path_dataset, id, config))) .collect::>() .into_iter() .try_for_each(|(id, dataset)| -> color_eyre::Result<()> { diff --git a/_src/parser/mod.rs b/_src/parser/mod.rs new file mode 100644 index 000000000..57fd87c40 --- /dev/null +++ b/_src/parser/mod.rs @@ -0,0 +1,42 @@ +use std::{thread::sleep, time::Duration}; + +use brk_parser::bitcoincore_rpc::{Client, RpcApi}; + +mod actions; +mod databases; +mod datasets; +mod price; +mod states; + +pub use actions::*; +pub use databases::*; +pub use datasets::*; +use log::info; +pub use states::*; + +use crate::structs::{Config, Exit}; + +pub fn main(config: &Config, rpc: &Client, exit: &Exit) -> color_eyre::Result<()> { + loop { + let block_count = rpc.get_blockchain_info().unwrap().blocks as usize; + + info!("{block_count} blocks found."); + + let mut databases = Databases::import(config); + let mut datasets = Datasets::import(config)?; + + iter_blocks(config, rpc, block_count, exit.clone(), &mut databases, &mut datasets)?; + + if let Some(delay) = config.delay() { + sleep(Duration::from_secs(delay)) + } + + info!("Waiting for a new block..."); + + while block_count == rpc.get_blockchain_info().unwrap().blocks as usize { + sleep(Duration::from_secs(1)) + } + } + + // Ok(()) +} diff --git a/parser/src/price/binance.rs b/_src/parser/price/binance.rs similarity index 63% rename from parser/src/price/binance.rs rename to _src/parser/price/binance.rs index bbd988b13..0967ad1e0 100644 --- a/parser/src/price/binance.rs +++ b/_src/parser/price/binance.rs @@ -1,26 +1,29 @@ #![allow(dead_code)] -use std::{collections::BTreeMap, fs, path::Path}; +use std::{collections::BTreeMap, fs}; use color_eyre::eyre::ContextCompat; use itertools::Itertools; +use log::info; use serde_json::Value; use crate::{ - io::{Json, INPUTS_FOLDER_PATH}, - structs::{Date, Timestamp, OHLC}, - utils::{log, retry}, + io::Json, + structs::{Config, Date, Timestamp, OHLC}, + utils::retry, }; pub struct Binance; impl Binance { - pub fn read_har_file() -> color_eyre::Result> { - log("binance: read har file"); + pub fn read_har_file(config: &Config) -> color_eyre::Result> { + info!("binance: read har file"); - fs::create_dir_all(INPUTS_FOLDER_PATH)?; + let path = config.path_inputs(); - let path_binance_har = Path::new(INPUTS_FOLDER_PATH).join("binance.har"); + fs::create_dir_all(&path)?; + + let path_binance_har = path.join("binance.har"); let json: BTreeMap = Json::import(&path_binance_har).unwrap_or_default(); @@ -104,7 +107,7 @@ impl Binance { } pub fn fetch_1mn_prices() -> color_eyre::Result> { - log("binance: fetch 1mn"); + info!("binance: fetch 1mn"); retry( |_| { @@ -117,33 +120,37 @@ impl Binance { .as_array() .context("Expect to be an array")? .iter() - .map(|value| { + .map(|value| -> color_eyre::Result<_> { // [timestamp, open, high, low, close, volume, ...] - let array = value.as_array().unwrap(); + let array = value.as_array().context("Expect to be array")?; - let timestamp = (array.first().unwrap().as_u64().unwrap() / 1_000) as u32; + let timestamp = (array + .first() + .context("Expect to have first")? + .as_u64() + .context("Expect to be convertible to u64")? + / 1_000) as u32; - let get_f32 = |index: usize| { - array + let get_f32 = |index: usize| -> color_eyre::Result { + Ok(array .get(index) - .unwrap() + .context("Expect to have index")? .as_str() - .unwrap() - .parse::() - .unwrap() + .context("Expect to have &str")? + .parse::()?) }; - ( + Ok(( timestamp, OHLC { - open: get_f32(1), - high: get_f32(2), - low: get_f32(3), - close: get_f32(4), + open: get_f32(1)?, + high: get_f32(2)?, + low: get_f32(3)?, + close: get_f32(4)?, }, - ) + )) }) - .collect::>()) + .collect::, _>>()?) }, 30, 10, @@ -151,7 +158,7 @@ impl Binance { } pub fn fetch_daily_prices() -> color_eyre::Result> { - log("binance: fetch 1d"); + info!("binance: fetch 1d"); retry( |_| { @@ -164,36 +171,40 @@ impl Binance { .as_array() .context("Expect to be an array")? .iter() - .map(|value| { + .map(|value| -> color_eyre::Result<_> { // [timestamp, open, high, low, close, volume, ...] - let array = value.as_array().unwrap(); + let array = value.as_array().context("Expect to be array")?; - let date = Timestamp::wrap( - (array.first().unwrap().as_u64().unwrap() / 1_000) as u32, + let date = Timestamp::from( + (array + .first() + .context("Expect to have first")? + .as_u64() + .context("Expect to be convertible to u64")? + / 1_000) as u32, ) .to_date(); - let get_f32 = |index: usize| { - array + let get_f32 = |index: usize| -> color_eyre::Result { + Ok(array .get(index) - .unwrap() + .context("Expect to have index")? .as_str() - .unwrap() - .parse::() - .unwrap() + .context("Expect to have &str")? + .parse::()?) }; - ( + Ok(( date, OHLC { - open: get_f32(1), - high: get_f32(2), - low: get_f32(3), - close: get_f32(4), + open: get_f32(1)?, + high: get_f32(2)?, + low: get_f32(3)?, + close: get_f32(4)?, }, - ) + )) }) - .collect::>()) + .collect::, _>>()?) }, 30, 10, diff --git a/parser/src/price/kibo.rs b/_src/parser/price/kibo.rs similarity index 70% rename from parser/src/price/kibo.rs rename to _src/parser/price/kibo.rs index 51a48adc0..858b7d7ac 100644 --- a/parser/src/price/kibo.rs +++ b/_src/parser/price/kibo.rs @@ -2,13 +2,12 @@ use std::{collections::BTreeMap, str::FromStr}; use chrono::NaiveDate; use color_eyre::eyre::ContextCompat; -use itertools::Itertools; +use log::info; use serde_json::Value; use crate::{ - structs::{Date, DateMapChunkId, HeightMapChunkId, OHLC}, - utils::{log, retry}, - MapChunkId, + structs::{Date, DateMapChunkId, HeightMapChunkId, MapChunkId, OHLC}, + utils::retry, }; pub struct Kibo; @@ -28,7 +27,7 @@ impl Kibo { } pub fn fetch_height_prices(chunk_id: HeightMapChunkId) -> color_eyre::Result> { - log("kibo: fetch height prices"); + info!("kibo: fetch height prices"); retry( |try_index| { @@ -40,7 +39,7 @@ impl Kibo { ))? .json()?; - Ok(body + let vec = body .as_object() .context("Expect to be an object")? .get("dataset") @@ -53,7 +52,9 @@ impl Kibo { .context("Expect to be an array")? .iter() .map(Self::value_to_ohlc) - .collect_vec()) + .collect::, _>>()?; + + Ok(vec) }, 30, RETRIES, @@ -61,7 +62,7 @@ impl Kibo { } pub fn fetch_date_prices(chunk_id: DateMapChunkId) -> color_eyre::Result> { - log("kibo: fetch date prices"); + info!("kibo: fetch date prices"); retry( |try_index| { @@ -85,28 +86,33 @@ impl Kibo { .as_object() .context("Expect to be an object")? .iter() - .map(|(serialized_date, value)| { - let date = Date::wrap(NaiveDate::from_str(serialized_date).unwrap()); - - (date, Self::value_to_ohlc(value)) + .map(|(serialized_date, value)| -> color_eyre::Result<_> { + let date = Date::wrap(NaiveDate::from_str(serialized_date)?); + Ok((date, Self::value_to_ohlc(value)?)) }) - .collect::>()) + .collect::, _>>()?) }, 30, RETRIES, ) } - fn value_to_ohlc(value: &Value) -> OHLC { - let ohlc = value.as_object().unwrap(); + fn value_to_ohlc(value: &Value) -> color_eyre::Result { + let ohlc = value.as_object().context("Expect as_object to work")?; - let get_value = |key: &str| ohlc.get(key).unwrap().as_f64().unwrap() as f32; + let get_value = |key: &str| -> color_eyre::Result { + Ok(ohlc + .get(key) + .context("Expect get key to work")? + .as_f64() + .context("Expect as_f64 to work")? as f32) + }; - OHLC { - open: get_value("open"), - high: get_value("high"), - low: get_value("low"), - close: get_value("close"), - } + Ok(OHLC { + open: get_value("open")?, + high: get_value("high")?, + low: get_value("low")?, + close: get_value("close")?, + }) } } diff --git a/parser/src/price/kraken.rs b/_src/parser/price/kraken.rs similarity index 51% rename from parser/src/price/kraken.rs rename to _src/parser/price/kraken.rs index 7567964b1..99566fee0 100644 --- a/parser/src/price/kraken.rs +++ b/_src/parser/price/kraken.rs @@ -1,18 +1,19 @@ use std::collections::BTreeMap; use color_eyre::eyre::ContextCompat; +use log::info; use serde_json::Value; use crate::{ structs::{Date, Timestamp, OHLC}, - utils::{log, retry}, + utils::retry, }; pub struct Kraken; impl Kraken { pub fn fetch_1mn_prices() -> color_eyre::Result> { - log("kraken: fetch 1mn"); + info!("kraken: fetch 1mn"); retry( |_| { @@ -33,32 +34,36 @@ impl Kraken { .as_array() .context("Expect to be an array")? .iter() - .map(|value| { - let array = value.as_array().unwrap(); + .map(|value| -> color_eyre::Result<_> { + let array = value.as_array().context("Expect as_array to work")?; - let timestamp = array.first().unwrap().as_u64().unwrap() as u32; + let timestamp = array + .first() + .context("Expect first to work")? + .as_u64() + .expect("Expect as_u64 to work") + as u32; - let get_f32 = |index: usize| { - array + let get_f32 = |index: usize| -> color_eyre::Result { + Ok(array .get(index) - .unwrap() + .context("Expect get index to work")? .as_str() - .unwrap() - .parse::() - .unwrap() + .context("Expect as_str to work")? + .parse::()?) }; - ( + Ok(( timestamp, OHLC { - open: get_f32(1), - high: get_f32(2), - low: get_f32(3), - close: get_f32(4), + open: get_f32(1)?, + high: get_f32(2)?, + low: get_f32(3)?, + close: get_f32(4)?, }, - ) + )) }) - .collect::>()) + .collect::, _>>()?) }, 30, 10, @@ -66,7 +71,7 @@ impl Kraken { } pub fn fetch_daily_prices() -> color_eyre::Result> { - log("fetch kraken daily"); + info!("fetch kraken daily"); retry( |_| { @@ -87,33 +92,39 @@ impl Kraken { .as_array() .context("Expect to be an array")? .iter() - .map(|value| { - let array = value.as_array().unwrap(); + .map(|value| -> color_eyre::Result<_> { + let array = value.as_array().context("Expect as_array to work")?; - let date = Timestamp::wrap(array.first().unwrap().as_u64().unwrap() as u32) - .to_date(); - - let get_f32 = |index: usize| { + let date = Timestamp::from( array + .first() + .context("Expect first to work")? + .as_u64() + .context("Expect as_u64 to work")? + as u32, + ) + .to_date(); + + let get_f32 = |index: usize| -> color_eyre::Result { + Ok(array .get(index) - .unwrap() + .context("Expect get index to work")? .as_str() - .unwrap() - .parse::() - .unwrap() + .context("Expect as_str to work")? + .parse::()?) }; - ( + Ok(( date, OHLC { - open: get_f32(1), - high: get_f32(2), - low: get_f32(3), - close: get_f32(4), + open: get_f32(1)?, + high: get_f32(2)?, + low: get_f32(3)?, + close: get_f32(4)?, }, - ) + )) }) - .collect::>()) + .collect::, _>>()?) }, 30, 10, diff --git a/parser/src/price/mod.rs b/_src/parser/price/mod.rs similarity index 100% rename from parser/src/price/mod.rs rename to _src/parser/price/mod.rs diff --git a/_src/parser/states/_trait.rs b/_src/parser/states/_trait.rs new file mode 100644 index 000000000..c07277890 --- /dev/null +++ b/_src/parser/states/_trait.rs @@ -0,0 +1,36 @@ +use std::{ + fmt::Debug, + fs, io, + path::{Path, PathBuf}, +}; + +use bincode::{Decode, Encode}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{io::Serialization, structs::Config}; + +pub trait AnyState +where + Self: Debug + Encode + Decode + Serialize + DeserializeOwned, +{ + fn name<'a>() -> &'a str; + + fn path(config: &Config) -> PathBuf { + config.path_states().join(Self::name()) + } + + fn reset(&mut self, config: &Config) -> color_eyre::Result<(), io::Error> { + self.clear(); + fs::remove_file(Self::path(config)) + } + + fn import(config: &Config) -> color_eyre::Result { + Serialization::Binary.import(&Self::path(config)) + } + + fn export(&self, config: &Config) -> color_eyre::Result<()> { + Serialization::Binary.export(Path::new(&Self::path(config)), self) + } + + fn clear(&mut self); +} diff --git a/parser/src/states/cohorts_states/address/cohort_durable_states.rs b/_src/parser/states/cohorts_states/address/cohort_durable_states.rs similarity index 97% rename from parser/src/states/cohorts_states/address/cohort_durable_states.rs rename to _src/parser/states/cohorts_states/address/cohort_durable_states.rs index 55c367ce8..8bcbb201b 100644 --- a/parser/src/states/cohorts_states/address/cohort_durable_states.rs +++ b/_src/parser/states/cohorts_states/address/cohort_durable_states.rs @@ -3,7 +3,7 @@ use std::ops::AddAssign; use allocative::Allocative; use crate::{ - states::{DurableStates, IsZero, OneShotStates, PriceToValue, UnrealizedState}, + parser::states::{DurableStates, IsZero, OneShotStates, PriceToValue, UnrealizedState}, structs::{Amount, Price}, }; diff --git a/parser/src/states/cohorts_states/address/cohort_id.rs b/_src/parser/states/cohorts_states/address/cohort_id.rs similarity index 100% rename from parser/src/states/cohorts_states/address/cohort_id.rs rename to _src/parser/states/cohorts_states/address/cohort_id.rs diff --git a/parser/src/states/cohorts_states/address/cohorts_durable_states.rs b/_src/parser/states/cohorts_states/address/cohorts_durable_states.rs similarity index 99% rename from parser/src/states/cohorts_states/address/cohorts_durable_states.rs rename to _src/parser/states/cohorts_states/address/cohorts_durable_states.rs index 932143461..0dece4f9d 100644 --- a/parser/src/states/cohorts_states/address/cohorts_durable_states.rs +++ b/_src/parser/states/cohorts_states/address/cohorts_durable_states.rs @@ -5,7 +5,7 @@ use derive_deref::{Deref, DerefMut}; use rayon::prelude::*; use crate::{ - databases::AddressIndexToAddressData, + parser::databases::AddressIndexToAddressData, structs::{AddressData, AddressRealizedData, Amount, Price}, }; diff --git a/parser/src/states/cohorts_states/address/cohorts_input_states.rs b/_src/parser/states/cohorts_states/address/cohorts_input_states.rs similarity index 97% rename from parser/src/states/cohorts_states/address/cohorts_input_states.rs rename to _src/parser/states/cohorts_states/address/cohorts_input_states.rs index 5f6ef9afa..0da4376c5 100644 --- a/parser/src/states/cohorts_states/address/cohorts_input_states.rs +++ b/_src/parser/states/cohorts_states/address/cohorts_input_states.rs @@ -1,7 +1,7 @@ use derive_deref::{Deref, DerefMut}; use crate::{ - states::InputState, + parser::states::InputState, structs::{AddressRealizedData, Amount, LiquidityClassification}, }; diff --git a/parser/src/states/cohorts_states/address/cohorts_one_shot_states.rs b/_src/parser/states/cohorts_states/address/cohorts_one_shot_states.rs similarity index 81% rename from parser/src/states/cohorts_states/address/cohorts_one_shot_states.rs rename to _src/parser/states/cohorts_states/address/cohorts_one_shot_states.rs index 042e44ea4..bda560fa6 100644 --- a/parser/src/states/cohorts_states/address/cohorts_one_shot_states.rs +++ b/_src/parser/states/cohorts_states/address/cohorts_one_shot_states.rs @@ -1,6 +1,6 @@ use derive_deref::{Deref, DerefMut}; -use crate::states::OneShotStates; +use crate::parser::states::OneShotStates; use super::SplitByAddressCohort; diff --git a/parser/src/states/cohorts_states/address/cohorts_output_states.rs b/_src/parser/states/cohorts_states/address/cohorts_output_states.rs similarity index 97% rename from parser/src/states/cohorts_states/address/cohorts_output_states.rs rename to _src/parser/states/cohorts_states/address/cohorts_output_states.rs index d0a682129..155ef98d2 100644 --- a/parser/src/states/cohorts_states/address/cohorts_output_states.rs +++ b/_src/parser/states/cohorts_states/address/cohorts_output_states.rs @@ -1,7 +1,7 @@ use derive_deref::{Deref, DerefMut}; use crate::{ - states::OutputState, + parser::states::OutputState, structs::{AddressRealizedData, Amount, LiquidityClassification}, }; diff --git a/parser/src/states/cohorts_states/address/cohorts_realized_states.rs b/_src/parser/states/cohorts_states/address/cohorts_realized_states.rs similarity index 98% rename from parser/src/states/cohorts_states/address/cohorts_realized_states.rs rename to _src/parser/states/cohorts_states/address/cohorts_realized_states.rs index 524cc31f6..22d2a228b 100644 --- a/parser/src/states/cohorts_states/address/cohorts_realized_states.rs +++ b/_src/parser/states/cohorts_states/address/cohorts_realized_states.rs @@ -1,7 +1,7 @@ use derive_deref::{Deref, DerefMut}; use crate::{ - states::RealizedState, + parser::states::RealizedState, structs::{AddressRealizedData, LiquidityClassification, Price}, }; diff --git a/parser/src/states/cohorts_states/address/mod.rs b/_src/parser/states/cohorts_states/address/mod.rs similarity index 100% rename from parser/src/states/cohorts_states/address/mod.rs rename to _src/parser/states/cohorts_states/address/mod.rs diff --git a/parser/src/states/cohorts_states/address/split_by_address_cohort.rs b/_src/parser/states/cohorts_states/address/split_by_address_cohort.rs similarity index 100% rename from parser/src/states/cohorts_states/address/split_by_address_cohort.rs rename to _src/parser/states/cohorts_states/address/split_by_address_cohort.rs diff --git a/parser/src/states/cohorts_states/any/capitalization_state.rs b/_src/parser/states/cohorts_states/any/capitalization_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/capitalization_state.rs rename to _src/parser/states/cohorts_states/any/capitalization_state.rs diff --git a/parser/src/states/cohorts_states/any/durable_states.rs b/_src/parser/states/cohorts_states/any/durable_states.rs similarity index 100% rename from parser/src/states/cohorts_states/any/durable_states.rs rename to _src/parser/states/cohorts_states/any/durable_states.rs diff --git a/parser/src/states/cohorts_states/any/input_state.rs b/_src/parser/states/cohorts_states/any/input_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/input_state.rs rename to _src/parser/states/cohorts_states/any/input_state.rs diff --git a/parser/src/states/cohorts_states/any/mod.rs b/_src/parser/states/cohorts_states/any/mod.rs similarity index 100% rename from parser/src/states/cohorts_states/any/mod.rs rename to _src/parser/states/cohorts_states/any/mod.rs diff --git a/parser/src/states/cohorts_states/any/one_shot_states.rs b/_src/parser/states/cohorts_states/any/one_shot_states.rs similarity index 100% rename from parser/src/states/cohorts_states/any/one_shot_states.rs rename to _src/parser/states/cohorts_states/any/one_shot_states.rs diff --git a/parser/src/states/cohorts_states/any/output_state.rs b/_src/parser/states/cohorts_states/any/output_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/output_state.rs rename to _src/parser/states/cohorts_states/any/output_state.rs diff --git a/parser/src/states/cohorts_states/any/price_paid_state.rs b/_src/parser/states/cohorts_states/any/price_paid_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/price_paid_state.rs rename to _src/parser/states/cohorts_states/any/price_paid_state.rs diff --git a/parser/src/states/cohorts_states/any/price_to_value.rs b/_src/parser/states/cohorts_states/any/price_to_value.rs similarity index 100% rename from parser/src/states/cohorts_states/any/price_to_value.rs rename to _src/parser/states/cohorts_states/any/price_to_value.rs diff --git a/parser/src/states/cohorts_states/any/realized_state.rs b/_src/parser/states/cohorts_states/any/realized_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/realized_state.rs rename to _src/parser/states/cohorts_states/any/realized_state.rs diff --git a/parser/src/states/cohorts_states/any/supply_state.rs b/_src/parser/states/cohorts_states/any/supply_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/supply_state.rs rename to _src/parser/states/cohorts_states/any/supply_state.rs diff --git a/parser/src/states/cohorts_states/any/unrealized_state.rs b/_src/parser/states/cohorts_states/any/unrealized_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/unrealized_state.rs rename to _src/parser/states/cohorts_states/any/unrealized_state.rs diff --git a/parser/src/states/cohorts_states/any/utxo_state.rs b/_src/parser/states/cohorts_states/any/utxo_state.rs similarity index 100% rename from parser/src/states/cohorts_states/any/utxo_state.rs rename to _src/parser/states/cohorts_states/any/utxo_state.rs diff --git a/parser/src/states/cohorts_states/mod.rs b/_src/parser/states/cohorts_states/mod.rs similarity index 100% rename from parser/src/states/cohorts_states/mod.rs rename to _src/parser/states/cohorts_states/mod.rs diff --git a/parser/src/states/cohorts_states/utxo/cohort_durable_states.rs b/_src/parser/states/cohorts_states/utxo/cohort_durable_states.rs similarity index 97% rename from parser/src/states/cohorts_states/utxo/cohort_durable_states.rs rename to _src/parser/states/cohorts_states/utxo/cohort_durable_states.rs index af47446e4..13ccce07a 100644 --- a/parser/src/states/cohorts_states/utxo/cohort_durable_states.rs +++ b/_src/parser/states/cohorts_states/utxo/cohort_durable_states.rs @@ -1,7 +1,7 @@ use allocative::Allocative; use crate::{ - states::{DurableStates, OneShotStates, PriceToValue, UnrealizedState}, + parser::states::{DurableStates, OneShotStates, PriceToValue, UnrealizedState}, structs::{Amount, Price}, }; diff --git a/parser/src/states/cohorts_states/utxo/cohort_filter.rs b/_src/parser/states/cohorts_states/utxo/cohort_filter.rs similarity index 100% rename from parser/src/states/cohorts_states/utxo/cohort_filter.rs rename to _src/parser/states/cohorts_states/utxo/cohort_filter.rs diff --git a/parser/src/states/cohorts_states/utxo/cohort_filters.rs b/_src/parser/states/cohorts_states/utxo/cohort_filters.rs similarity index 100% rename from parser/src/states/cohorts_states/utxo/cohort_filters.rs rename to _src/parser/states/cohorts_states/utxo/cohort_filters.rs diff --git a/parser/src/states/cohorts_states/utxo/cohort_id.rs b/_src/parser/states/cohorts_states/utxo/cohort_id.rs similarity index 100% rename from parser/src/states/cohorts_states/utxo/cohort_id.rs rename to _src/parser/states/cohorts_states/utxo/cohort_id.rs diff --git a/parser/src/states/cohorts_states/utxo/cohorts_durable_states.rs b/_src/parser/states/cohorts_states/utxo/cohorts_durable_states.rs similarity index 99% rename from parser/src/states/cohorts_states/utxo/cohorts_durable_states.rs rename to _src/parser/states/cohorts_states/utxo/cohorts_durable_states.rs index 16cafddcf..2ba92dbf2 100644 --- a/parser/src/states/cohorts_states/utxo/cohorts_durable_states.rs +++ b/_src/parser/states/cohorts_states/utxo/cohorts_durable_states.rs @@ -3,7 +3,7 @@ use derive_deref::{Deref, DerefMut}; use rayon::prelude::*; use crate::{ - states::DateDataVec, + parser::states::DateDataVec, structs::{Amount, BlockData, Price, SentData, Timestamp}, }; diff --git a/parser/src/states/cohorts_states/utxo/cohorts_one_shot_states.rs b/_src/parser/states/cohorts_states/utxo/cohorts_one_shot_states.rs similarity index 81% rename from parser/src/states/cohorts_states/utxo/cohorts_one_shot_states.rs rename to _src/parser/states/cohorts_states/utxo/cohorts_one_shot_states.rs index efd4db6a5..328fddf7a 100644 --- a/parser/src/states/cohorts_states/utxo/cohorts_one_shot_states.rs +++ b/_src/parser/states/cohorts_states/utxo/cohorts_one_shot_states.rs @@ -1,6 +1,6 @@ use derive_deref::{Deref, DerefMut}; -use crate::states::OneShotStates; +use crate::parser::states::OneShotStates; use super::SplitByUTXOCohort; diff --git a/parser/src/states/cohorts_states/utxo/cohorts_sent_states.rs b/_src/parser/states/cohorts_states/utxo/cohorts_sent_states.rs similarity index 98% rename from parser/src/states/cohorts_states/utxo/cohorts_sent_states.rs rename to _src/parser/states/cohorts_states/utxo/cohorts_sent_states.rs index 9b0da8a05..f09d908f7 100644 --- a/parser/src/states/cohorts_states/utxo/cohorts_sent_states.rs +++ b/_src/parser/states/cohorts_states/utxo/cohorts_sent_states.rs @@ -3,7 +3,7 @@ use std::{cmp::Ordering, collections::BTreeMap}; use derive_deref::{Deref, DerefMut}; use crate::{ - states::{DateDataVec, InputState, RealizedState}, + parser::states::{DateDataVec, InputState, RealizedState}, structs::{BlockPath, Price, SentData, Timestamp}, }; diff --git a/parser/src/states/cohorts_states/utxo/mod.rs b/_src/parser/states/cohorts_states/utxo/mod.rs similarity index 100% rename from parser/src/states/cohorts_states/utxo/mod.rs rename to _src/parser/states/cohorts_states/utxo/mod.rs diff --git a/parser/src/states/cohorts_states/utxo/split_by_utxo_cohort.rs b/_src/parser/states/cohorts_states/utxo/split_by_utxo_cohort.rs similarity index 100% rename from parser/src/states/cohorts_states/utxo/split_by_utxo_cohort.rs rename to _src/parser/states/cohorts_states/utxo/split_by_utxo_cohort.rs diff --git a/parser/src/states/counters.rs b/_src/parser/states/counters.rs similarity index 100% rename from parser/src/states/counters.rs rename to _src/parser/states/counters.rs diff --git a/parser/src/states/date_data_vec.rs b/_src/parser/states/date_data_vec.rs similarity index 100% rename from parser/src/states/date_data_vec.rs rename to _src/parser/states/date_data_vec.rs diff --git a/parser/src/states/mod.rs b/_src/parser/states/mod.rs similarity index 54% rename from parser/src/states/mod.rs rename to _src/parser/states/mod.rs index 878b6d672..1558b0005 100644 --- a/parser/src/states/mod.rs +++ b/_src/parser/states/mod.rs @@ -1,4 +1,4 @@ -use std::thread; +use std::{fs, thread}; mod _trait; mod cohorts_states; @@ -11,8 +11,9 @@ use allocative::Allocative; pub use cohorts_states::*; use counters::*; use date_data_vec::*; +use log::info; -use crate::utils::log; +use crate::structs::Config; #[derive(Default, Allocative)] pub struct States { @@ -23,12 +24,12 @@ pub struct States { } impl States { - pub fn import() -> color_eyre::Result { - let date_data_vec_handle = thread::spawn(DateDataVec::import); + pub fn import(config: &Config) -> color_eyre::Result { + fs::create_dir_all(config.path_states())?; - let address_counters = Counters::import()?; + let date_data_vec = DateDataVec::import(config)?; - let date_data_vec = date_data_vec_handle.join().unwrap()?; + let address_counters = Counters::import(config)?; Ok(Self { address_cohorts_durable_states: None, @@ -38,24 +39,24 @@ impl States { }) } - pub fn reset(&mut self, include_addresses: bool) { - log("Reseting all states..."); + pub fn reset(&mut self, config: &Config, include_addresses: bool) { + info!("Reseting all states..."); - let _ = self.date_data_vec.reset(); + let _ = self.date_data_vec.reset(config); self.utxo_cohorts_durable_states = None; if include_addresses { - let _ = self.address_counters.reset(); + let _ = self.address_counters.reset(config); self.address_cohorts_durable_states = None; } } - pub fn export(&self) -> color_eyre::Result<()> { + pub fn export(&self, config: &Config) -> color_eyre::Result<()> { thread::scope(|s| { - s.spawn(|| self.address_counters.export().unwrap()); - s.spawn(|| self.date_data_vec.export().unwrap()); + s.spawn(|| self.address_counters.export(config).unwrap()); + s.spawn(|| self.date_data_vec.export(config).unwrap()); }); Ok(()) diff --git a/parser/src/structs/address.rs b/_src/structs/address.rs similarity index 96% rename from parser/src/structs/address.rs rename to _src/structs/address.rs index 7fc0255d5..da6dc5d4a 100644 --- a/parser/src/structs/address.rs +++ b/_src/structs/address.rs @@ -1,5 +1,5 @@ -use bitcoin_hashes::{hash160, Hash}; -use biter::bitcoin::TxOut; +use bitcoin_hashes::hash160; +use brk_parser::bitcoin::TxOut; use super::{AddressType, Counter, U8x19, U8x31}; @@ -56,7 +56,7 @@ impl Address { let hash = hash160::Hash::hash(pk); - let (prefix, rest) = Self::split_slice(&hash[..]); + let (prefix, rest) = Self::split_slice(&hash.as_byte_array()[..]); Self::P2PK((prefix, rest.into())) } else if script.is_p2pkh() { diff --git a/parser/src/structs/address_data.rs b/_src/structs/address_data.rs similarity index 97% rename from parser/src/structs/address_data.rs rename to _src/structs/address_data.rs index 3d0968d0a..636819317 100644 --- a/parser/src/structs/address_data.rs +++ b/_src/structs/address_data.rs @@ -1,6 +1,6 @@ use allocative::Allocative; use color_eyre::eyre::eyre; -use sanakirja::{direct_repr, Storable, UnsizedStorable}; +use snkrj::{direct_repr, Storable, UnsizedStorable}; use super::{AddressType, Amount, EmptyAddressData, LiquidityClassification, Price}; diff --git a/parser/src/structs/address_liquidity.rs b/_src/structs/address_liquidity.rs similarity index 100% rename from parser/src/structs/address_liquidity.rs rename to _src/structs/address_liquidity.rs diff --git a/parser/src/structs/address_realized_data.rs b/_src/structs/address_realized_data.rs similarity index 100% rename from parser/src/structs/address_realized_data.rs rename to _src/structs/address_realized_data.rs diff --git a/parser/src/structs/address_size.rs b/_src/structs/address_size.rs similarity index 100% rename from parser/src/structs/address_size.rs rename to _src/structs/address_size.rs diff --git a/parser/src/structs/address_split.rs b/_src/structs/address_split.rs similarity index 100% rename from parser/src/structs/address_split.rs rename to _src/structs/address_split.rs diff --git a/parser/src/structs/address_type.rs b/_src/structs/address_type.rs similarity index 100% rename from parser/src/structs/address_type.rs rename to _src/structs/address_type.rs diff --git a/parser/src/structs/amount.rs b/_src/structs/amount.rs similarity index 91% rename from parser/src/structs/amount.rs rename to _src/structs/amount.rs index 6ff8ce1d4..5a29adf42 100644 --- a/parser/src/structs/amount.rs +++ b/_src/structs/amount.rs @@ -5,32 +5,19 @@ use std::{ use allocative::{Allocative, Visitor}; use bincode::{ + BorrowDecode, Decode, Encode, de::{BorrowDecoder, Decoder}, enc::Encoder, error::{DecodeError, EncodeError}, - BorrowDecode, Decode, Encode, }; -use biter::bitcoin::Amount as BitcoinAmount; +use brk_parser::bitcoin::Amount as BitcoinAmount; use derive_deref::{Deref, DerefMut}; -use sanakirja::{direct_repr, Storable, UnsizedStorable}; use serde::{Deserialize, Serialize}; +use snkrj::{Storable, UnsizedStorable, direct_repr}; use super::Height; -#[derive( - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Clone, - Copy, - Deref, - DerefMut, - Default, - Serialize, - Deserialize, -)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default, Serialize, Deserialize)] pub struct Amount(BitcoinAmount); direct_repr!(Amount); diff --git a/_src/structs/any_map.rs b/_src/structs/any_map.rs new file mode 100644 index 000000000..371898295 --- /dev/null +++ b/_src/structs/any_map.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use serde_json::Value; + +use crate::io::Serialization; + +use super::{Config, MapKind, MapPath}; + +pub trait AnyMap { + fn path(&self) -> &Path; + fn path_parent(&self) -> &Path; + fn path_last(&self) -> &Option; + fn last_value(&self) -> Option; + fn serialization(&self) -> Serialization; + fn type_name(&self) -> &str; + fn key_name(&self) -> &str; + fn pre_export(&mut self); + fn export(&self) -> color_eyre::Result<()>; + fn post_export(&mut self); + fn delete_files(&self); + fn kind(&self) -> MapKind; + fn id(&self, config: &Config) -> String; +} diff --git a/parser/src/structs/array.rs b/_src/structs/array.rs similarity index 92% rename from parser/src/structs/array.rs rename to _src/structs/array.rs index dbfb67592..8a915db6d 100644 --- a/parser/src/structs/array.rs +++ b/_src/structs/array.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use allocative::Allocative; use derive_deref::{Deref, DerefMut}; -use sanakirja::{direct_repr, Storable, UnsizedStorable}; +use snkrj::{direct_repr, Storable, UnsizedStorable}; #[derive( Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Default, Copy, Allocative, diff --git a/parser/src/structs/bi_map.rs b/_src/structs/bi_map.rs similarity index 97% rename from parser/src/structs/bi_map.rs rename to _src/structs/bi_map.rs index dd271ec14..733c06846 100644 --- a/parser/src/structs/bi_map.rs +++ b/_src/structs/bi_map.rs @@ -8,10 +8,10 @@ use allocative::Allocative; use crate::utils::{LossyFrom, TARGET_BLOCKS_PER_DAY}; use super::{ - AnyDateMap, AnyHeightMap, AnyMap, Date, DateMap, Height, HeightMap, MapKind, MapValue, + AnyDateMap, AnyHeightMap, AnyMap, Date, DateMap, Height, HeightMap, MapKind, MapPath, MapValue, }; -#[derive(Allocative)] +#[derive(Allocative, Debug)] pub struct BiMap where Value: MapValue, @@ -24,14 +24,14 @@ impl BiMap where Value: MapValue, { - pub fn new_bin(version: u32, kind: MapKind, path: &str) -> Self { + pub fn new_bin(version: u32, kind: MapKind, path: &MapPath) -> Self { Self { height: HeightMap::_new_bin(version, kind, path, true), date: DateMap::_new_bin(version, kind, path, false), } } - pub fn new_json(version: u32, kind: MapKind, path: &str) -> Self { + pub fn new_json(version: u32, kind: MapKind, path: &MapPath) -> Self { Self { height: HeightMap::new_json(version, kind, path, true), date: DateMap::new_json(version, kind, path, false), diff --git a/parser/src/structs/block_data.rs b/_src/structs/block_data.rs similarity index 100% rename from parser/src/structs/block_data.rs rename to _src/structs/block_data.rs diff --git a/parser/src/structs/block_path.rs b/_src/structs/block_path.rs similarity index 100% rename from parser/src/structs/block_path.rs rename to _src/structs/block_path.rs diff --git a/_src/structs/config.rs b/_src/structs/config.rs new file mode 100644 index 000000000..683ec1181 --- /dev/null +++ b/_src/structs/config.rs @@ -0,0 +1,326 @@ +use std::{ + fs::{self}, + mem, + path::{Path, PathBuf}, +}; + +use brk_parser::bitcoincore_rpc::Auth; +use clap::Parser; +use color_eyre::eyre::eyre; +use log::info; +use serde::{Deserialize, Serialize}; + +use crate::io::JSON_EXTENSION; + +use super::MapPath; + +#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[command(version, about, long_about = None)] +pub struct Config { + /// Bitcoin data directory path, saved + #[arg(long, value_name = "PATH")] + bitcoindir: Option, + + /// Kibo output directory path, saved + #[arg(long, value_name = "PATH")] + kibodir: Option, + + /// Bitcoin RPC ip, default: localhost, saved + #[arg(long, value_name = "IP")] + rpcconnect: Option, + + /// Bitcoin RPC port, default: 8332, saved + #[arg(long, value_name = "PORT")] + rpcport: Option, + + /// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved + #[arg(long, value_name = "PATH")] + rpccookiefile: Option, + + /// Bitcoin RPC username, saved + #[arg(long, value_name = "USERNAME")] + rpcuser: Option, + + /// Bitcoin RPC password, saved + #[arg(long, value_name = "PASSWORD")] + rpcpassword: Option, + + /// Delay between runs, default: 0, saved + #[arg(long, value_name = "SECONDS")] + delay: Option, + + /// Disable the parser, not saved + #[serde(default)] + #[arg(long, default_value_t = false)] + no_parser: bool, + + /// Disable the server, not saved + #[serde(default)] + #[arg(long, default_value_t = false)] + no_server: bool, + + /// Run without saving, not saved + #[serde(default)] + #[arg(long, default_value_t = false)] + dry_run: bool, + + /// Record ram usage, not saved + #[serde(default)] + #[arg(long, default_value_t = false)] + record_ram_usage: bool, + + /// Recompute all computed datasets, not saved + #[serde(default)] + #[arg(long, default_value_t = false)] + recompute_computed: bool, +} + +impl Config { + pub const DATASET_DIR_NAME: &str = "datasets"; + pub const DATABASES_DIR_NAME: &str = "databases"; + + pub fn import() -> color_eyre::Result { + let path = Self::path_dot_kibo(); + let _ = fs::create_dir_all(&path); + + let path = path.join("config.toml"); + + let mut config_saved = Self::read(&path); + + let mut config_args = Config::parse(); + + if let Some(bitcoindir) = config_args.bitcoindir.take() { + config_saved.bitcoindir = Some(bitcoindir); + } + + if let Some(kibodir) = config_args.kibodir.take() { + config_saved.kibodir = Some(kibodir); + } + + if let Some(rpcconnect) = config_args.rpcconnect.take() { + config_saved.rpcconnect = Some(rpcconnect); + } + + if let Some(rpcport) = config_args.rpcport.take() { + config_saved.rpcport = Some(rpcport); + } + + if let Some(rpccookiefile) = config_args.rpccookiefile.take() { + config_saved.rpccookiefile = Some(rpccookiefile); + } + + if let Some(rpcuser) = config_args.rpcuser.take() { + config_saved.rpcuser = Some(rpcuser); + } + + if let Some(rpcpassword) = config_args.rpcpassword.take() { + config_saved.rpcpassword = Some(rpcpassword); + } + + if let Some(delay) = config_args.delay.take() { + config_saved.delay = Some(delay); + } + + // if let Some(max_ram) = config_args.max_ram.take() { + // config_saved.max_ram = Some(max_ram); + // } + + // Done importing + + let mut config = config_saved; + + config.check(); + + config.write(&path)?; + + config.no_parser = mem::take(&mut config_args.no_parser); + config.no_server = mem::take(&mut config_args.no_server); + config.dry_run = mem::take(&mut config_args.dry_run); + config.record_ram_usage = mem::take(&mut config_args.record_ram_usage); + config.recompute_computed = mem::take(&mut config_args.recompute_computed); + + info!("Configuration {{"); + info!(" bitcoindir: {:?}", config.bitcoindir); + info!(" kibodir: {:?}", config.kibodir); + info!(" rpcconnect: {:?}", config.rpcconnect); + info!(" rpcport: {:?}", config.rpcport); + info!(" rpccookiefile: {:?}", config.rpccookiefile); + info!(" rpcuser: {:?}", config.rpcuser); + info!(" rpcpassword: {:?}", config.rpcpassword); + info!(" delay: {:?}", config.delay); + // info!(" max_ram: {:?}", config.max_ram); + info!(" parser: {:?}", config.parser()); + info!(" server: {:?}", config.server()); + info!(" dry_run: {:?}", config.dry_run()); + info!(" record_ram_usage: {:?}", config.record_ram_usage()); + info!(" recompute_computed: {:?}", config.recompute_computed()); + info!("}}"); + + if config_args != Config::default() { + dbg!(config_args); + panic!("Didn't consume the full config") + } + + Ok(config) + } + + fn check(&self) { + if self.bitcoindir.is_none() { + println!( + "You need to set the --bitcoindir parameter at least once to run the parser.\nRun the program with '-h' for help." + ); + std::process::exit(1); + } else if !self.path_bitcoindir().is_dir() { + println!( + "Given --bitcoindir parameter doesn't seem to be a valid directory path.\nRun the program with '-h' for help." + ); + std::process::exit(1); + } + + if self.kibodir.is_none() { + println!( + "You need to set the --kibodir parameter at least once to run the parser.\nRun the program with '-h' for help." + ); + std::process::exit(1); + } else if !self.path_kibodir().is_dir() { + println!( + "Given --kibodir parameter doesn't seem to be a valid directory path.\nRun the program with '-h' for help." + ); + std::process::exit(1); + } + + let path = self.path_bitcoindir(); + if !path.is_dir() { + println!("Expect path '{:#?}' to be a directory.", path); + std::process::exit(1); + } + + if self.to_rpc_auth().is_err() { + println!( + "No way found to authenticate the RPC client, please either set --rpccookiefile or --rpcuser and --rpcpassword.\nRun the program with '-h' for help." + ); + std::process::exit(1); + } + } + + fn read(path: &Path) -> Self { + fs::read_to_string(path).map_or(Config::default(), |contents| { + toml::from_str(&contents).unwrap_or_default() + }) + } + + fn write(&self, path: &Path) -> std::io::Result<()> { + fs::write(path, toml::to_string(self).unwrap()) + } + + pub fn to_rpc_auth(&self) -> color_eyre::Result { + let cookie = self.path_cookiefile(); + + if cookie.is_file() { + Ok(Auth::CookieFile(cookie)) + } else if self.rpcuser.is_some() && self.rpcpassword.is_some() { + Ok(Auth::UserPass( + self.rpcuser.clone().unwrap(), + self.rpcpassword.clone().unwrap(), + )) + } else { + Err(eyre!("Failed to find correct auth")) + } + } + + pub fn rpcconnect(&self) -> Option<&String> { + self.rpcconnect.as_ref() + } + + pub fn rpcport(&self) -> Option { + self.rpcport + } + + pub fn delay(&self) -> Option { + self.delay + } + + pub fn dry_run(&self) -> bool { + self.dry_run + } + + pub fn record_ram_usage(&self) -> bool { + self.record_ram_usage + } + + pub fn recompute_computed(&self) -> bool { + self.recompute_computed + } + + pub fn path_bitcoindir(&self) -> PathBuf { + Self::fix_user_path(self.bitcoindir.as_ref().unwrap().as_ref()) + } + + pub fn path_kibodir(&self) -> PathBuf { + Self::fix_user_path(self.kibodir.as_ref().unwrap().as_ref()) + } + + fn path_cookiefile(&self) -> PathBuf { + self.rpccookiefile.as_ref().map_or_else( + || self.path_bitcoindir().join(".cookie"), + |p| Self::fix_user_path(p.as_str()), + ) + } + + fn fix_user_path(path: &str) -> PathBuf { + let fix = move |pattern: &str| { + if path.starts_with(pattern) { + let path = &path.replace(&format!("{pattern}/"), "").replace(pattern, ""); + + let home = std::env::var("HOME").unwrap(); + + Some(Path::new(&home).join(path)) + } else { + None + } + }; + + fix("~").unwrap_or_else(|| fix("$HOME").unwrap_or_else(|| PathBuf::from(&path))) + } + + pub fn path_datasets(&self) -> MapPath { + MapPath::from(self.path_kibodir().join(Self::DATASET_DIR_NAME)) + } + + pub fn path_datasets_last_values(&self) -> MapPath { + self.path_datasets().join(&format!("last.{JSON_EXTENSION}")) + } + + pub fn path_price(&self) -> MapPath { + MapPath::from(self.path_kibodir().join("price")) + } + + pub fn path_databases(&self) -> PathBuf { + self.path_kibodir().join(Self::DATABASES_DIR_NAME) + } + + pub fn path_states(&self) -> PathBuf { + self.path_kibodir().join("states") + } + + pub fn path_inputs(&self) -> PathBuf { + self.path_kibodir().join("inputs") + } + + fn path_dot_kibo() -> PathBuf { + let home = std::env::var("HOME").unwrap(); + Path::new(&home).join(".kibo") + } + + pub fn path_log() -> PathBuf { + Self::path_dot_kibo().join("log") + } + + pub fn parser(&self) -> bool { + !self.no_parser + } + + pub fn server(&self) -> bool { + !self.no_server + } +} diff --git a/parser/src/structs/counter.rs b/_src/structs/counter.rs similarity index 100% rename from parser/src/structs/counter.rs rename to _src/structs/counter.rs diff --git a/parser/src/structs/date.rs b/_src/structs/date.rs similarity index 91% rename from parser/src/structs/date.rs rename to _src/structs/date.rs index ec36f4c52..b99e933a4 100644 --- a/parser/src/structs/date.rs +++ b/_src/structs/date.rs @@ -44,16 +44,12 @@ impl Date { Self(chrono::offset::Utc::now().date_naive()) } - pub fn yesterday() -> Self { - Self(Self::today().checked_sub_days(Days::new(1)).unwrap()) - } - pub fn difference_in_days_between(&self, older: Self) -> u32 { (**self - *older).num_days() as u32 } pub fn to_timestamp(self) -> Timestamp { - Timestamp::wrap(NaiveDateTime::from(*self).and_utc().timestamp() as u32) + Timestamp::from(NaiveDateTime::from(*self).and_utc().timestamp() as u32) } /// Returns value between 0.0 and 1.0 depending on its completion @@ -74,17 +70,17 @@ impl Date { } } + pub fn is_january(&self) -> bool { + self.month() == 1 + } + + pub fn is_july(&self) -> bool { + self.month() == 7 + } + pub fn is_first_of_month(&self) -> bool { self.day() == 1 } - - pub fn is_first_of_january(&self) -> bool { - self.is_first_of_month() && self.month() == 1 - } - - pub fn is_first_of_june(&self) -> bool { - self.is_first_of_month() && self.month() == 6 - } } impl MapKey for Date { diff --git a/parser/src/structs/date_data.rs b/_src/structs/date_data.rs similarity index 100% rename from parser/src/structs/date_data.rs rename to _src/structs/date_data.rs diff --git a/parser/src/structs/date_map.rs b/_src/structs/date_map.rs similarity index 92% rename from parser/src/structs/date_map.rs rename to _src/structs/date_map.rs index 76e3c6bde..54b97530c 100644 --- a/parser/src/structs/date_map.rs +++ b/_src/structs/date_map.rs @@ -1,10 +1,10 @@ use std::iter::Sum; -use crate::{Date, HeightMap}; +use super::{ + AnyMap, Date, DateMapChunkId, GenericMap, Height, HeightMap, MapValue, SerializedDateMap, +}; -use super::{AnyMap, DateMapChunkId, GenericMap, Height, MapValue, SerializedBTreeMap}; - -pub type DateMap = GenericMap>; +pub type DateMap = GenericMap>; impl DateMap where diff --git a/parser/src/structs/date_map_chunk_id.rs b/_src/structs/date_map_chunk_id.rs similarity index 77% rename from parser/src/structs/date_map_chunk_id.rs rename to _src/structs/date_map_chunk_id.rs index ca3fdac32..07554e24a 100644 --- a/parser/src/structs/date_map_chunk_id.rs +++ b/_src/structs/date_map_chunk_id.rs @@ -3,9 +3,7 @@ use std::path::Path; use allocative::Allocative; use chrono::Datelike; -use crate::Date; - -use super::MapChunkId; +use super::{Date, MapChunkId}; #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Allocative)] pub struct DateMapChunkId(i32); @@ -17,7 +15,7 @@ impl DateMapChunkId { } impl MapChunkId for DateMapChunkId { - fn to_name(&self) -> String { + fn to_string(&self) -> String { self.0.to_string() } @@ -41,4 +39,12 @@ impl MapChunkId for DateMapChunkId { fn from_usize(id: usize) -> Self { Self(id as i32) } + + fn next(&self) -> Option { + self.0.checked_add(1).map(Self) + } + + fn previous(&self) -> Option { + self.0.checked_sub(1).map(Self) + } } diff --git a/parser/src/structs/empty_address_data.rs b/_src/structs/empty_address_data.rs similarity index 94% rename from parser/src/structs/empty_address_data.rs rename to _src/structs/empty_address_data.rs index a7613efcb..48aaa7784 100644 --- a/parser/src/structs/empty_address_data.rs +++ b/_src/structs/empty_address_data.rs @@ -1,5 +1,5 @@ use allocative::Allocative; -use sanakirja::{direct_repr, Storable, UnsizedStorable}; +use snkrj::{direct_repr, Storable, UnsizedStorable}; use super::{AddressData, AddressType, Amount}; diff --git a/parser/src/structs/epoch.rs b/_src/structs/epoch.rs similarity index 84% rename from parser/src/structs/epoch.rs rename to _src/structs/epoch.rs index ed7108a4c..0bc3aa303 100644 --- a/parser/src/structs/epoch.rs +++ b/_src/structs/epoch.rs @@ -9,7 +9,7 @@ impl Epoch { impl From for Epoch { fn from(height: Height) -> Self { - Self(((height.to_usize() / Self::BLOCKS_PER_EPOCH) + 1) as u16) + Self::from(&height) } } diff --git a/parser/src/structs/exit.rs b/_src/structs/exit.rs similarity index 92% rename from parser/src/structs/exit.rs rename to _src/structs/exit.rs index 5122ef361..00408a42e 100644 --- a/parser/src/structs/exit.rs +++ b/_src/structs/exit.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -use super::super::log; +use log::info; #[derive(Default, Clone)] pub struct Exit { @@ -29,12 +29,12 @@ impl Exit { let blocked = move || _blocked.load(Ordering::SeqCst); ctrlc::set_handler(move || { - log("Exitting..."); + info!("Exitting..."); active.store(true, Ordering::SeqCst); if blocked() { - log("Waiting to exit safely"); + info!("Waiting to exit safely"); while blocked() { sleep(Duration::from_millis(50)); diff --git a/parser/src/structs/generic_map.rs b/_src/structs/generic_map.rs similarity index 91% rename from parser/src/structs/generic_map.rs rename to _src/structs/generic_map.rs index a384f6619..f15c92e05 100644 --- a/parser/src/structs/generic_map.rs +++ b/_src/structs/generic_map.rs @@ -1,6 +1,6 @@ use std::{ collections::{BTreeMap, VecDeque}, - fmt::Debug, + fmt::{Debug, Display}, fs, iter::Sum, mem, @@ -15,12 +15,11 @@ use ordered_float::OrderedFloat; use serde::{de::DeserializeOwned, Serialize}; use crate::{ - log, + io::Serialization, utils::{get_percentile, LossyFrom}, - Serialization, }; -use super::{AnyMap, MapValue}; +use super::{AnyMap, Config, MapPath, MapValue}; #[derive(Debug, Clone, Copy, Allocative, PartialEq, Eq)] pub enum MapKind { @@ -30,7 +29,7 @@ pub enum MapKind { pub trait MapKey where - Self: Sized + PartialOrd + Ord + Clone + Copy + Debug, + Self: Sized + PartialOrd + Ord + Clone + Copy + Debug + Display, ChunkId: MapChunkId, { fn to_chunk_id(&self) -> ChunkId; @@ -62,16 +61,21 @@ where fn get(&self, serialized_key: &Key) -> Option<&Value>; fn last(&self) -> Option<&Value>; fn extend(&mut self, map: BTreeMap); + fn import_all(path: &Path, serialization: &Serialization) -> Self; + fn to_csv(self, id: &str) -> String; + fn map(&self) -> &impl Serialize; } pub trait MapChunkId where Self: Ord + Debug + Copy + Clone, { - fn to_name(&self) -> String; + fn to_string(&self) -> String; fn from_path(path: &Path) -> color_eyre::Result; fn to_usize(self) -> usize; fn from_usize(id: usize) -> Self; + fn previous(&self) -> Option; + fn next(&self) -> Option; } #[derive(Debug, Allocative)] @@ -79,8 +83,9 @@ pub struct GenericMap { version: u32, kind: MapKind, - path_all: PathBuf, - path_last: Option, + path_all: MapPath, + path_parent: MapPath, + path_last: Option, chunks_in_memory: usize, @@ -100,15 +105,15 @@ where Key: MapKey, Serialized: MapSerialized, { - pub fn new_bin(version: u32, kind: MapKind, path: &str) -> Self { + pub fn new_bin(version: u32, kind: MapKind, path: &MapPath) -> Self { Self::new(version, kind, path, Serialization::Binary, 1, true) } - pub fn _new_bin(version: u32, kind: MapKind, path: &str, export_last: bool) -> Self { + pub fn _new_bin(version: u32, kind: MapKind, path: &MapPath, export_last: bool) -> Self { Self::new(version, kind, path, Serialization::Binary, 1, export_last) } - pub fn new_json(version: u32, kind: MapKind, path: &str, export_last: bool) -> Self { + pub fn new_json(version: u32, kind: MapKind, path: &MapPath, export_last: bool) -> Self { Self::new( version, kind, @@ -122,7 +127,7 @@ where fn new( version: u32, kind: MapKind, - path: &str, + path: &MapPath, serialization: Serialization, chunks_in_memory: usize, export_last: bool, @@ -131,15 +136,16 @@ where panic!("Should always have at least the latest chunk in memory"); } - let path = path.replace(['-', '_', ' '], "/"); + let path_all = path.join(Key::map_name()); - let path_all = PathBuf::from(format!("{path}/{}", Key::map_name())); - - fs::create_dir_all(&path_all).unwrap(); + fs::create_dir_all(&*path_all).unwrap_or_else(|_| { + dbg!(&path_all); + panic!() + }); let path_last = { if export_last { - Some(PathBuf::from(format!("{path}/last"))) + Some(path.join("last")) } else { None } @@ -150,6 +156,7 @@ where kind, path_all, + path_parent: path.to_owned(), path_last, chunks_in_memory, @@ -177,21 +184,25 @@ where } }); - s.initial_last_key = s + s.set_initial_keys(); + + // if s.initial_first_unsafe_key.is_none() { + // log(&format!("Missing dataset: {path:?}/{}", Key::map_name())); + // } + + s + } + + fn set_initial_keys(&mut self) { + self.initial_last_key = self .imported .iter() .last() .and_then(|(last_chunk_id, serialized)| serialized.get_last_key(last_chunk_id)); - s.initial_first_unsafe_key = s + self.initial_first_unsafe_key = self .initial_last_key .and_then(|last_key| last_key.to_first_unsafe()); - - if s.initial_first_unsafe_key.is_none() { - log(&format!("Missing dataset: {path}/{}", Key::map_name())); - } - - s } fn read_dir(&self) -> BTreeMap { @@ -308,11 +319,27 @@ where Key: MapKey, Serialized: MapSerialized, { + fn id(&self, config: &Config) -> String { + let path_to_string = |p: &Path| p.to_str().unwrap().to_owned(); + path_to_string(self.path_parent()) + .replace(&format!("{}/", path_to_string(&config.path_kibodir())), "") + .replace(&format!("{}/", Config::DATASET_DIR_NAME), "") + .replace("/", "-") + } + + fn serialization(&self) -> Serialization { + self.serialization + } + fn path(&self) -> &Path { &self.path_all } - fn path_last(&self) -> &Option { + fn path_parent(&self) -> &Path { + &self.path_parent + } + + fn path_last(&self) -> &Option { &self.path_last } @@ -323,10 +350,14 @@ where .and_then(|v| serde_json::to_value(v).ok()) } - fn t_name(&self) -> &str { + fn type_name(&self) -> &str { std::any::type_name::() } + fn key_name(&self) -> &str { + Key::map_name() + } + fn pre_export(&mut self) { self.to_insert.iter_mut().for_each(|(chunk_id, map)| { if let Some((key, _)) = map.first_key_value() { @@ -351,6 +382,8 @@ where .or_insert(Serialized::new(self.version)) .extend(mem::take(map)); }); + + self.set_initial_keys(); } fn export(&self) -> color_eyre::Result<()> { @@ -367,15 +400,15 @@ where panic!(); }); - let path = self.path_all.join(chunk_id.to_name()); + let path = self.path_all.join(&chunk_id.to_string()); - self.serialization.export(Path::new(&path), serialized)?; + self.serialization.export(&path, serialized)?; // Export last if index == len - 1 { if let Some(path_last) = self.path_last.as_ref() { self.serialization - .export(Path::new(path_last), serialized.last().unwrap())?; + .export(path_last, serialized.last().unwrap())?; } } @@ -418,22 +451,22 @@ where Key: MapKey, Serialized: MapSerialized, { - pub fn sum_keys(&mut self, keys: &[Key]) -> Value - where - Value: Sum, - { - keys.iter() - .map(|key| self.get_or_import(key).unwrap()) - .sum::() - } + // pub fn sum_keys(&mut self, keys: &[Key]) -> Value + // where + // Value: Sum, + // { + // keys.iter() + // .map(|key| self.get_or_import(key).unwrap()) + // .sum::() + // } - pub fn average_keys(&mut self, keys: &[Key]) -> f32 - where - Value: Sum, - f32: LossyFrom, - { - f32::lossy_from(self.sum_keys(keys)) / keys.len() as f32 - } + // pub fn average_keys(&mut self, keys: &[Key]) -> f32 + // where + // Value: Sum, + // f32: LossyFrom, + // { + // f32::lossy_from(self.sum_keys(keys)) / keys.len() as f32 + // } pub fn multi_insert(&mut self, keys: &[Key], mut callback: F) where diff --git a/parser/src/structs/height.rs b/_src/structs/height.rs similarity index 90% rename from parser/src/structs/height.rs rename to _src/structs/height.rs index b3d811eff..c788e6afb 100644 --- a/parser/src/structs/height.rs +++ b/_src/structs/height.rs @@ -5,13 +5,11 @@ use std::{ use allocative::Allocative; use bincode::{Decode, Encode}; -use biter::NUMBER_OF_UNSAFE_BLOCKS; +use brk_parser::NUMBER_OF_UNSAFE_BLOCKS; use derive_deref::{Deref, DerefMut}; use serde::{Deserialize, Serialize}; -use crate::HEIGHT_MAP_CHUNK_SIZE; - -use super::{HeightMapChunkId, MapKey}; +use super::{HEIGHT_MAP_CHUNK_SIZE, HeightMapChunkId, MapKey}; #[derive( Debug, @@ -47,11 +45,11 @@ impl Height { **self < (block_count - NUMBER_OF_UNSAFE_BLOCKS) as u32 } - pub fn iter_range_inclusive(first: Height, last: Height) -> impl Iterator { - let range = (*first)..=(*last); + // pub fn iter_range_inclusive(first: Height, last: Height) -> impl Iterator { + // let range = (*first)..=(*last); - range.into_iter().map(Height::new) - } + // range.into_iter().map(Height::new) + // } } impl PartialEq for Height { diff --git a/parser/src/structs/height_map.rs b/_src/structs/height_map.rs similarity index 97% rename from parser/src/structs/height_map.rs rename to _src/structs/height_map.rs index bd2f36a73..f768f96b0 100644 --- a/parser/src/structs/height_map.rs +++ b/_src/structs/height_map.rs @@ -2,9 +2,7 @@ use std::{iter::Sum, ops::RangeInclusive}; use itertools::Itertools; -use crate::SerializedVec; - -use super::{AnyMap, GenericMap, Height, HeightMapChunkId, MapValue}; +use super::{AnyMap, GenericMap, Height, HeightMapChunkId, MapValue, SerializedVec}; pub const HEIGHT_MAP_CHUNK_SIZE: u32 = 10_000; diff --git a/parser/src/structs/height_map_chunk_id.rs b/_src/structs/height_map_chunk_id.rs similarity index 74% rename from parser/src/structs/height_map_chunk_id.rs rename to _src/structs/height_map_chunk_id.rs index 6f662529d..bfe82058a 100644 --- a/parser/src/structs/height_map_chunk_id.rs +++ b/_src/structs/height_map_chunk_id.rs @@ -3,9 +3,7 @@ use std::path::Path; use allocative::Allocative; use derive_deref::{Deref, DerefMut}; -use crate::HEIGHT_MAP_CHUNK_SIZE; - -use super::{Height, MapChunkId}; +use super::{Height, MapChunkId, HEIGHT_MAP_CHUNK_SIZE}; #[derive( Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Allocative, Deref, DerefMut, @@ -21,7 +19,7 @@ impl HeightMapChunkId { } impl MapChunkId for HeightMapChunkId { - fn to_name(&self) -> String { + fn to_string(&self) -> String { let start = ***self; let end = start + HEIGHT_MAP_CHUNK_SIZE; @@ -48,4 +46,14 @@ impl MapChunkId for HeightMapChunkId { fn from_usize(id: usize) -> Self { Self(Height::new(id as u32)) } + + fn next(&self) -> Option { + self.checked_add(HEIGHT_MAP_CHUNK_SIZE) + .map(|h| Self(Height::new(h))) + } + + fn previous(&self) -> Option { + self.checked_sub(HEIGHT_MAP_CHUNK_SIZE) + .map(|h| Self(Height::new(h))) + } } diff --git a/_src/structs/instant.rs b/_src/structs/instant.rs new file mode 100644 index 000000000..9495f71eb --- /dev/null +++ b/_src/structs/instant.rs @@ -0,0 +1,15 @@ +use std::time::Instant; + +use color_eyre::owo_colors::OwoColorize; + +pub trait DisplayInstant { + fn display(&self) -> String; +} + +impl DisplayInstant for Instant { + fn display(&self) -> String { + format!("{:.2}s", self.elapsed().as_secs_f32()) + .bright_black() + .to_string() + } +} diff --git a/parser/src/structs/liquidity.rs b/_src/structs/liquidity.rs similarity index 100% rename from parser/src/structs/liquidity.rs rename to _src/structs/liquidity.rs diff --git a/_src/structs/map_path.rs b/_src/structs/map_path.rs new file mode 100644 index 000000000..ebf056374 --- /dev/null +++ b/_src/structs/map_path.rs @@ -0,0 +1,24 @@ +use std::path::PathBuf; + +use allocative::Allocative; +use derive_deref::{Deref, DerefMut}; + +#[derive(Debug, Clone, Deref, DerefMut, Allocative)] +pub struct MapPath(PathBuf); + +impl MapPath { + pub fn join(&self, path: &str) -> Self { + let path = path.replace(['-', '_', ' '], "/"); + Self(self.0.join(path)) + } + + pub fn unwrap(&self) -> &PathBuf { + &self.0 + } +} + +impl From for MapPath { + fn from(value: PathBuf) -> Self { + Self(value) + } +} diff --git a/parser/src/structs/map_value.rs b/_src/structs/map_value.rs similarity index 93% rename from parser/src/structs/map_value.rs rename to _src/structs/map_value.rs index f9790c72d..4f1dcf9fa 100644 --- a/parser/src/structs/map_value.rs +++ b/_src/structs/map_value.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use allocative::Allocative; use bincode::{Decode, Encode}; @@ -18,6 +18,7 @@ pub trait MapValue: + Sync + Send + Allocative + + Display { } diff --git a/parser/src/structs/mod.rs b/_src/structs/mod.rs similarity index 94% rename from parser/src/structs/mod.rs rename to _src/structs/mod.rs index 1278f5844..f1f012319 100644 --- a/parser/src/structs/mod.rs +++ b/_src/structs/mod.rs @@ -24,11 +24,14 @@ mod generic_map; mod height; mod height_map; mod height_map_chunk_id; +mod instant; mod liquidity; +mod map_path; mod map_value; mod ohlc; mod partial_txout_data; mod price; +mod rpc; mod sent_data; mod serialized_btreemap; mod serialized_vec; @@ -62,7 +65,9 @@ pub use generic_map::*; pub use height::*; pub use height_map::*; pub use height_map_chunk_id::*; +pub use instant::*; pub use liquidity::*; +pub use map_path::*; pub use map_value::*; pub use ohlc::*; pub use partial_txout_data::*; diff --git a/parser/src/structs/ohlc.rs b/_src/structs/ohlc.rs similarity index 53% rename from parser/src/structs/ohlc.rs rename to _src/structs/ohlc.rs index 666a52b1f..16fc833a5 100644 --- a/parser/src/structs/ohlc.rs +++ b/_src/structs/ohlc.rs @@ -1,3 +1,5 @@ +use std::fmt::{self}; + use allocative::Allocative; use bincode::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -10,3 +12,13 @@ pub struct OHLC { pub low: f32, pub close: f32, } + +impl fmt::Display for OHLC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{{ open: {}, high: {}, low: {}, close: {} }}", + self.open, self.high, self.low, self.close + ) + } +} diff --git a/parser/src/structs/partial_txout_data.rs b/_src/structs/partial_txout_data.rs similarity index 100% rename from parser/src/structs/partial_txout_data.rs rename to _src/structs/partial_txout_data.rs diff --git a/parser/src/structs/price.rs b/_src/structs/price.rs similarity index 100% rename from parser/src/structs/price.rs rename to _src/structs/price.rs diff --git a/parser/src/structs/ram.rs b/_src/structs/ram.rs similarity index 100% rename from parser/src/structs/ram.rs rename to _src/structs/ram.rs diff --git a/_src/structs/rpc.rs b/_src/structs/rpc.rs new file mode 100644 index 000000000..885c6bb71 --- /dev/null +++ b/_src/structs/rpc.rs @@ -0,0 +1,17 @@ +use brk_parser::bitcoincore_rpc::Client; + +use crate::structs::Config; + +impl From<&Config> for Client { + fn from(config: &Config) -> Self { + Client::new( + &format!( + "http://{}:{}", + config.rpcconnect().unwrap_or(&"localhost".to_owned()), + config.rpcport().unwrap_or(8332) + ), + config.to_rpc_auth().unwrap(), + ) + .unwrap() + } +} diff --git a/parser/src/structs/sent_data.rs b/_src/structs/sent_data.rs similarity index 100% rename from parser/src/structs/sent_data.rs rename to _src/structs/sent_data.rs diff --git a/parser/src/structs/serialized_btreemap.rs b/_src/structs/serialized_btreemap.rs similarity index 72% rename from parser/src/structs/serialized_btreemap.rs rename to _src/structs/serialized_btreemap.rs index 2f1723ff1..3b5b730d4 100644 --- a/parser/src/structs/serialized_btreemap.rs +++ b/_src/structs/serialized_btreemap.rs @@ -4,9 +4,12 @@ use allocative::Allocative; use bincode::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use crate::Serialization; +use crate::io::Serialization; -use super::{DateMap, MapChunkId, MapKey, MapSerialized, MapValue}; +use super::{Date, DateMap, MapChunkId, MapKey, MapSerialized, MapValue, Timestamp}; + +pub type SerializedDateMap = SerializedBTreeMap; +pub type SerializedTimeMap = SerializedBTreeMap; #[derive(Debug, Default, Serialize, Deserialize, Encode, Decode, Allocative)] pub struct SerializedBTreeMap @@ -17,41 +20,11 @@ where pub map: BTreeMap, } -impl SerializedBTreeMap -where - Key: Ord, -{ - pub fn import_all(path: &Path, serialization: &Serialization) -> Self - where - Self: Debug + Serialize + DeserializeOwned + Encode + Decode, - ChunkId: MapChunkId, - Key: MapKey, - Value: MapValue, - { - let mut s = None; - - DateMap::::_read_dir(path, serialization) - .iter() - .for_each(|(_, path)| { - let map = serialization.import::(path).unwrap(); - - if s.is_none() { - s.replace(map); - } else { - #[allow(clippy::unnecessary_unwrap)] - s.as_mut().unwrap().map.extend(map.map); - } - }); - - s.unwrap() - } -} - impl MapSerialized for SerializedBTreeMap where Self: Debug + Serialize + DeserializeOwned + Encode + Decode, ChunkId: MapChunkId, - Key: MapKey, + Key: MapKey + Serialize, Value: MapValue, { fn new(version: u32) -> Self { @@ -80,4 +53,35 @@ where fn extend(&mut self, map: BTreeMap) { self.map.extend(map) } + + fn import_all(path: &Path, serialization: &Serialization) -> Self { + let mut s = None; + + DateMap::::_read_dir(path, serialization) + .iter() + .for_each(|(_, path)| { + let map = serialization.import::(path).unwrap(); + + if s.is_none() { + s.replace(map); + } else { + #[allow(clippy::unnecessary_unwrap)] + s.as_mut().unwrap().map.extend(map.map); + } + }); + + s.unwrap() + } + + fn to_csv(self, id: &str) -> String { + let mut csv = format!("{},{}\n", Key::map_name(), id); + self.map.iter().for_each(|(k, v)| { + csv += &format!("{},{}\n", k, v); + }); + csv + } + + fn map(&self) -> &impl Serialize { + &self.map + } } diff --git a/parser/src/structs/serialized_vec.rs b/_src/structs/serialized_vec.rs similarity index 82% rename from parser/src/structs/serialized_vec.rs rename to _src/structs/serialized_vec.rs index 3d61a9738..73efd3f3e 100644 --- a/parser/src/structs/serialized_vec.rs +++ b/_src/structs/serialized_vec.rs @@ -4,7 +4,7 @@ use allocative::Allocative; use bincode::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use crate::Serialization; +use crate::io::Serialization; use super::{HeightMap, MapChunkId, MapKey, MapSerialized, MapValue}; @@ -15,27 +15,8 @@ pub struct SerializedVec { } impl SerializedVec { - pub fn import_all(path: &Path, serialization: &Serialization) -> Self - where - Self: Debug + Serialize + DeserializeOwned + Encode + Decode, - Value: MapValue, - { - let mut s = None; - - HeightMap::::_read_dir(path, serialization) - .iter() - .for_each(|(_, path)| { - let mut map = serialization.import::(path).unwrap(); - - if s.is_none() { - s.replace(map); - } else { - #[allow(clippy::unnecessary_unwrap)] - s.as_mut().unwrap().map.append(&mut map.map); - } - }); - - s.unwrap() + pub fn get_index(&self, index: usize) -> Option<&Value> { + self.map.get(index) } } @@ -87,4 +68,39 @@ where } }); } + + fn import_all(path: &Path, serialization: &Serialization) -> Self + where + Self: Debug + Serialize + DeserializeOwned + Encode + Decode, + Value: MapValue, + { + let mut s = None; + + HeightMap::::_read_dir(path, serialization) + .iter() + .for_each(|(_, path)| { + let mut map = serialization.import::(path).unwrap(); + + if s.is_none() { + s.replace(map); + } else { + #[allow(clippy::unnecessary_unwrap)] + s.as_mut().unwrap().map.append(&mut map.map); + } + }); + + s.unwrap() + } + + fn to_csv(self, id: &str) -> String { + let mut csv = format!("{},{}\n", Key::map_name(), id); + self.map.iter().enumerate().for_each(|(k, v)| { + csv += &format!("{:?},{:?}\n", k, v); + }); + csv + } + + fn map(&self) -> &impl Serialize { + &self.map + } } diff --git a/parser/src/structs/timestamp.rs b/_src/structs/timestamp.rs similarity index 52% rename from parser/src/structs/timestamp.rs rename to _src/structs/timestamp.rs index 04ce1b1ad..0d8cc88df 100644 --- a/parser/src/structs/timestamp.rs +++ b/_src/structs/timestamp.rs @@ -1,14 +1,14 @@ -use std::ops::Sub; +use std::{fmt, ops::Sub}; use allocative::Allocative; use bincode::{Decode, Encode}; -use chrono::{Datelike, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc}; +use chrono::{NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc}; use derive_deref::{Deref, DerefMut}; use serde::{Deserialize, Serialize}; use crate::utils::{ONE_DAY_IN_S, ONE_HOUR_IN_S}; -use super::Date; +use super::{Date, HeightMapChunkId, MapKey}; #[derive( Debug, @@ -32,10 +32,6 @@ pub struct Timestamp(u32); impl Timestamp { pub const ZERO: Self = Self(0); - pub fn wrap(timestamp: u32) -> Self { - Self(timestamp) - } - pub fn now() -> Self { Self(chrono::offset::Utc::now().timestamp() as u32) } @@ -48,14 +44,10 @@ impl Timestamp { ) } - pub fn to_year(self) -> u32 { - self.to_date().year() as u32 - } - pub fn to_floored_seconds(self) -> Self { let date_time = Utc.timestamp_opt(i64::from(self.0), 0).unwrap(); - Self::wrap( + Self::from( NaiveDateTime::new( date_time.date_naive(), NaiveTime::from_hms_opt(date_time.hour(), date_time.minute(), 0).unwrap(), @@ -74,7 +66,7 @@ impl Timestamp { } pub fn older_by_1h_plus_than(&self, younger: Self) -> bool { - younger.checked_sub(**self).unwrap_or_default() > ONE_HOUR_IN_S as u32 + (*younger).checked_sub(**self).unwrap_or_default() > ONE_HOUR_IN_S as u32 } } @@ -82,6 +74,64 @@ impl Sub for Timestamp { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { - Self::wrap(self.0 - rhs.0) + Self::from(self.0 - rhs.0) + } +} + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", **self) + } +} + +impl From for Timestamp { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl MapKey for Timestamp { + fn to_chunk_id(&self) -> HeightMapChunkId { + unreachable!(); + } + + fn to_first_unsafe(&self) -> Option { + unreachable!(); + } + + fn to_serialized_key(&self) -> Self { + unreachable!(); + } + + fn is_out_of_bounds(&self) -> bool { + unreachable!(); + } + + fn is_first(&self) -> bool { + unreachable!(); + } + + fn checked_sub(&self, _: usize) -> Option { + unreachable!(); + } + + fn min_percentile_key() -> Self { + unreachable!(); + } + + fn iter_up_to(&self, other: &Self) -> impl Iterator { + (**self..=**other).map(Timestamp::from) + } + + fn map_name<'a>() -> &'a str { + "timestamp" + } + + fn to_usize(&self) -> usize { + (**self) as usize + } + + fn from_usize(t: usize) -> Self { + Self(t as u32) } } diff --git a/parser/src/structs/tx_data.rs b/_src/structs/tx_data.rs similarity index 90% rename from parser/src/structs/tx_data.rs rename to _src/structs/tx_data.rs index 1a577e8be..162ed456f 100644 --- a/parser/src/structs/tx_data.rs +++ b/_src/structs/tx_data.rs @@ -1,5 +1,5 @@ use allocative::Allocative; -use sanakirja::{direct_repr, Storable, UnsizedStorable}; +use snkrj::{direct_repr, Storable, UnsizedStorable}; use super::BlockPath; diff --git a/parser/src/structs/txout_index.rs b/_src/structs/txout_index.rs similarity index 92% rename from parser/src/structs/txout_index.rs rename to _src/structs/txout_index.rs index 6da7f588d..ebb36775f 100644 --- a/parser/src/structs/txout_index.rs +++ b/_src/structs/txout_index.rs @@ -1,6 +1,6 @@ use allocative::Allocative; use bincode::{Decode, Encode}; -use sanakirja::{direct_repr, Storable, UnsizedStorable}; +use snkrj::{direct_repr, Storable, UnsizedStorable}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Encode, Decode, Allocative)] pub struct TxoutIndex { diff --git a/parser/src/utils/consts.rs b/_src/utils/consts.rs similarity index 100% rename from parser/src/utils/consts.rs rename to _src/utils/consts.rs diff --git a/parser/src/utils/flamegraph.rs b/_src/utils/flamegraph.rs similarity index 84% rename from parser/src/utils/flamegraph.rs rename to _src/utils/flamegraph.rs index 2a40ab398..2e96ce6e3 100644 --- a/parser/src/utils/flamegraph.rs +++ b/_src/utils/flamegraph.rs @@ -2,10 +2,15 @@ use std::{fs, path::PathBuf}; use chrono::Local; -use crate::{databases::Databases, datasets::AllDatasets, states::States, structs::Height}; +use crate::{ + parser::{Databases, Datasets, States}, + structs::Height, +}; + +// use crate::{databases::Databases, datasets::AllDatasets, states::States, structs::Height}; pub fn generate_allocation_files( - datasets: &AllDatasets, + datasets: &Datasets, databases: &Databases, states: &States, last_height: Height, diff --git a/_src/utils/log.rs b/_src/utils/log.rs new file mode 100644 index 000000000..4679d3339 --- /dev/null +++ b/_src/utils/log.rs @@ -0,0 +1,82 @@ +use std::{ + fmt::Display, + fs::{self, OpenOptions}, + io::Write, +}; + +use chrono::Local; +use color_eyre::owo_colors::OwoColorize; +use env_logger::Env; + +use crate::structs::Config; + +#[inline(always)] +pub fn init_log() { + let _ = fs::remove_file(Config::path_log()); + + let file = Box::new( + OpenOptions::new() + .create(true) + .append(true) + .open(Config::path_log()) + .unwrap(), + ); + + env_logger::Builder::from_env( + Env::default().default_filter_or(format!("{}=info", env!("CARGO_PKG_NAME"))), + ) + .format(move |buf, record| { + let date_time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S")); + let level = record.level().as_str().to_lowercase(); + let level = format!("{:5}", level); + let target = record.target(); + let dash = "-"; + let args = record.args(); + + let _ = write( + file.try_clone().unwrap(), + &date_time, + target, + &level, + dash, + args, + ); + + let colored_date_time = date_time.bright_black(); + let colored_level = match level.chars().next().unwrap() { + 'e' => level.red().to_string(), + 'w' => level.yellow().to_string(), + 'i' => level.green().to_string(), + 'd' => level.blue().to_string(), + 't' => level.cyan().to_string(), + _ => panic!(), + }; + let colored_dash = dash.bright_black(); + + write( + buf, + colored_date_time, + target, + colored_level, + colored_dash, + args, + ) + }) + .init(); +} + +fn write( + mut buf: impl Write, + date_time: impl Display, + _target: impl Display, + level: impl Display, + dash: impl Display, + args: impl Display, +) -> Result<(), std::io::Error> { + writeln!(buf, "{} {} {} {}", date_time, dash, level, args) + // writeln!( + // buf, + // "{} {} {} {} {}", + // date_time, _target, level, dash, args + // ) +} diff --git a/parser/src/utils/lossy.rs b/_src/utils/lossy.rs similarity index 100% rename from parser/src/utils/lossy.rs rename to _src/utils/lossy.rs diff --git a/parser/src/utils/mod.rs b/_src/utils/mod.rs similarity index 89% rename from parser/src/utils/mod.rs rename to _src/utils/mod.rs index 18c6f8d80..59742bfe6 100644 --- a/parser/src/utils/mod.rs +++ b/_src/utils/mod.rs @@ -4,7 +4,6 @@ mod log; mod lossy; mod percentile; mod retry; -mod rpc; mod time; pub use consts::*; @@ -13,5 +12,4 @@ pub use log::*; pub use lossy::*; pub use percentile::*; pub use retry::*; -pub use rpc::*; pub use time::*; diff --git a/parser/src/utils/percentile.rs b/_src/utils/percentile.rs similarity index 100% rename from parser/src/utils/percentile.rs rename to _src/utils/percentile.rs diff --git a/parser/src/utils/retry.rs b/_src/utils/retry.rs similarity index 89% rename from parser/src/utils/retry.rs rename to _src/utils/retry.rs index 6f22e968c..f200ced47 100644 --- a/parser/src/utils/retry.rs +++ b/_src/utils/retry.rs @@ -5,10 +5,6 @@ pub fn retry( sleep_in_s: u64, retries: usize, ) -> color_eyre::Result { - if retries < 1 { - unreachable!() - } - let mut i = 0; loop { diff --git a/_src/utils/time.rs b/_src/utils/time.rs new file mode 100644 index 000000000..2c6df6103 --- /dev/null +++ b/_src/utils/time.rs @@ -0,0 +1,18 @@ +use std::time::Instant; + +use log::info; + +use crate::structs::DisplayInstant; + +pub fn time(text: &str, function: F) -> T +where + F: FnOnce() -> T, +{ + let time = Instant::now(); + + let returned = function(); + + info!("{text} {}", time.display()); + + returned +} diff --git a/biter/Cargo.toml b/biter/Cargo.toml deleted file mode 100644 index f01e3608d..000000000 --- a/biter/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "biter" -description = "A very fast Bitcoin block iterator" -version = "0.1.1" -license = "MIT" -repository = "https://github.com/kibo-money/kibo/tree/main/biter" -keywords = ["bitcoin", "block", "iterator"] -categories = ["cryptography::cryptocurrencies", "encoding"] -edition = "2021" - -[dependencies] -bitcoin = { version = "0.32.2", features = ["serde"] } -rayon = "1.10.0" -crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } -serde = { version = "1.0.204", features = ["derive"] } -serde_json = "1.0.122" -derived-deref = "2.1.0" -bitcoincore-rpc = "0.19.0" -# tokio = { version = "1.39.2", features = ["rt-multi-thread"] } diff --git a/biter/README.md b/biter/README.md deleted file mode 100644 index 932108dac..000000000 --- a/biter/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Biter - -Biter (Bitcoin Block Iterator) is a very fast and simple Rust library which reads raw block files (*blkXXXXX.dat*) from Bitcoin Core Node and creates an iterator over all the requested blocks in sequential order (0, 1, 2, ...). - -The element returned by the iterator is a tuple which includes the: -- Height: `usize` -- Block: `Block` (from `bitcoin-rust`) -- Block's Hash: `BlockHash` (also from `bitcoin-rust`) - -## Example - -```rust -use bitcoincore_rpc::{Auth, Client}; - -fn main() { - let i = std::time::Instant::now(); - - // Path to the Bitcoin data directory - let data_dir = "../../bitcoin"; - - // Path to the export directory where a mini blk indexer will be exported - let export_dir = "./target"; - - // Inclusive starting height of the blocks received, `None` for 0 - let start = Some(850_000); - - // Inclusive ending height of the blocks received, `None` for the last one - let end = None; - - // RPC client to filter out forks - let url = "http://localhost:8332"; - let auth = Auth::UserPass("satoshi".to_string(), "nakamoto".to_string()); - let rpc = Client::new(url, auth).unwrap(); - - // Create channel receiver then iterate over the blocks - biter::new(data_dir, export_dir, start, end, rpc) - .iter() - .for_each(|(height, _block, hash)| { - println!("{height}: {hash}"); - }); - - dbg!(i.elapsed()); -} - -``` - -## Requirements - -Even though it reads *blkXXXXX.dat* files, it **needs** `bitcoind` to run with the RPC server to filter out block forks. - -Peak memory should be around 500MB. - -## Comparaison - -| | [biter](https://crates.io/crates/biter) | [bitcoin-explorer](https://crates.io/crates/bitcoin-explorer) | [blocks_iterator](https://crates.io/crates/blocks_iterator) | -| --- | --- | --- | --- | -| Run **with** `bitcoind` | Yes ✅ | No ❌ | Yes ✅ | -| Run **without** `bitcoind` | No ❌ | Yes ✅ | Yes ✅ | -| `0..=855_000` | 16mn40s | 17mn 46s | > 2h | -| `800_000..=855_000` | 2mn 53s (16mn40s if first run) | 3mn 2s | > 2h | - -*Benchmarked on a Macbook Pro M3 Pro* diff --git a/biter/src/blk_index_to_blk_recap.rs b/biter/src/blk_index_to_blk_recap.rs deleted file mode 100644 index 9c9d53c53..000000000 --- a/biter/src/blk_index_to_blk_recap.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::{ - cmp::Ordering, - collections::BTreeMap, - fs::{self, File}, - io::{BufReader, BufWriter}, - path::PathBuf, -}; - -use derived_deref::{Deref, DerefMut}; - -use crate::{blk_recap::BlkRecap, BlkMetadataAndBlock}; - -#[derive(Deref, DerefMut, Debug)] -pub struct BlkIndexToBlkRecap { - path: String, - #[target] - tree: BTreeMap, -} - -impl BlkIndexToBlkRecap { - pub fn import(blocks_dir: &BTreeMap, export_dir: &str) -> Self { - let path = format!("{export_dir}/blk_index_to_blk_recap.json"); - - let tree = { - fs::create_dir_all(export_dir).unwrap(); - - if let Ok(file) = File::open(&path) { - let reader = BufReader::new(file); - serde_json::from_reader(reader).unwrap_or_default() - } else { - BTreeMap::default() - } - }; - - let mut this = Self { path, tree }; - - this.clean_outdated(blocks_dir); - - this - } - - pub fn clean_outdated(&mut self, blocks_dir: &BTreeMap) { - blocks_dir.iter().for_each(|(blk_index, blk_path)| { - if let Some(blk_recap) = self.get(blk_index) { - if blk_recap.has_different_modified_time(blk_path) { - self.remove(blk_index); - } - } - }); - } - - pub fn get_start_recap(&self, start: Option) -> Option<(usize, BlkRecap)> { - if let Some(start) = start { - let (last_key, last_value) = self.last_key_value()?; - - if last_value.height() < start { - return Some((*last_key, *last_value)); - } else if let Some((blk_index, _)) = self - .iter() - .find(|(_, blk_recap)| blk_recap.is_younger_than(start)) - { - if *blk_index != 0 { - let blk_index = *blk_index - 1; - return Some((blk_index, *self.get(&blk_index).unwrap())); - } - } - } - - None - } - - pub fn update(&mut self, blk_metadata_and_block: &BlkMetadataAndBlock, height: usize) { - let blk_index = blk_metadata_and_block.blk_metadata.index; - - if let Some(last_entry) = self.last_entry() { - // if last_entry.get().is_older_than(height) { - match last_entry.key().cmp(&blk_index) { - Ordering::Greater => { - last_entry.remove_entry(); - } - Ordering::Less => { - self.insert(blk_index, BlkRecap::from(height, blk_metadata_and_block)); - } - Ordering::Equal => {} - }; - // } - } else { - if blk_index != 0 || height != 0 { - // dbg!(blk_index, height); - unreachable!(); - } - - self.insert(blk_index, BlkRecap::first(blk_metadata_and_block)); - } - } - - pub fn export(&self) { - let file = File::create(&self.path).unwrap_or_else(|_| { - dbg!(&self.path); - panic!("No such file or directory") - }); - - serde_json::to_writer_pretty(&mut BufWriter::new(file), &self.tree).unwrap(); - } -} diff --git a/biter/src/blk_metadata_and_block.rs b/biter/src/blk_metadata_and_block.rs deleted file mode 100644 index 1cff5e055..000000000 --- a/biter/src/blk_metadata_and_block.rs +++ /dev/null @@ -1,17 +0,0 @@ -use bitcoin::Block; - -use crate::BlkMetadata; - -pub struct BlkMetadataAndBlock { - pub blk_metadata: BlkMetadata, - pub block: Block, -} - -impl BlkMetadataAndBlock { - pub fn new(blk_metadata: BlkMetadata, block: Block) -> Self { - Self { - blk_metadata, - block, - } - } -} diff --git a/biter/src/blk_recap.rs b/biter/src/blk_recap.rs deleted file mode 100644 index 26e35daa9..000000000 --- a/biter/src/blk_recap.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::path::PathBuf; - -use bitcoin::{hashes::Hash, BlockHash}; -use serde::{Deserialize, Serialize}; - -use crate::{path_to_modified_time, BlkMetadataAndBlock}; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct BlkRecap { - min_continuous_height: usize, - min_continuous_prev_hash: BlockHash, - modified_time: u64, -} - -impl BlkRecap { - pub fn first(blk_metadata_and_block: &BlkMetadataAndBlock) -> Self { - Self { - min_continuous_height: 0, - min_continuous_prev_hash: BlockHash::all_zeros(), - modified_time: blk_metadata_and_block.blk_metadata.modified_time, - } - } - - pub fn from(height: usize, blk_metadata_and_block: &BlkMetadataAndBlock) -> Self { - Self { - min_continuous_height: height, - min_continuous_prev_hash: blk_metadata_and_block.block.header.prev_blockhash, - modified_time: blk_metadata_and_block.blk_metadata.modified_time, - } - } - - pub fn has_different_modified_time(&self, blk_path: &PathBuf) -> bool { - self.modified_time != path_to_modified_time(blk_path) - } - - pub fn is_younger_than(&self, height: usize) -> bool { - self.min_continuous_height > height - } - - pub fn height(&self) -> usize { - self.min_continuous_height - } - - pub fn prev_hash(&self) -> &BlockHash { - &self.min_continuous_prev_hash - } -} diff --git a/biter/src/lib.rs b/biter/src/lib.rs deleted file mode 100644 index a064fa1cb..000000000 --- a/biter/src/lib.rs +++ /dev/null @@ -1,384 +0,0 @@ -use std::{ - collections::{BTreeMap, BTreeSet, VecDeque}, - fs::{self}, - ops::ControlFlow, - thread, -}; - -use bitcoin::{ - consensus::{Decodable, ReadExt}, - hashes::Hash, - io::{Cursor, Read}, - Block, BlockHash, -}; -use bitcoincore_rpc::RpcApi; -use crossbeam::channel::{bounded, Receiver}; -use rayon::prelude::*; - -pub use bitcoin; -pub use bitcoincore_rpc; - -mod blk_index_to_blk_recap; -mod blk_metadata; -mod blk_metadata_and_block; -mod blk_recap; -mod utils; - -use blk_index_to_blk_recap::*; -use blk_metadata::*; -use blk_metadata_and_block::*; -use utils::*; - -pub const NUMBER_OF_UNSAFE_BLOCKS: usize = 100; -const MAGIC_BYTES: [u8; 4] = [249, 190, 180, 217]; -const BOUND_CAP: usize = 210; - -enum BlockState { - Raw(Vec), - Decoded(Block), -} - -/// -/// Returns a crossbeam channel receiver that receives `(usize, Block, BlockHash)` tuples (with `usize` being the height) in sequential order. -/// -/// # Arguments -/// -/// * `data_dir` - Path to the Bitcoin data directory -/// * `export_dir` - Path to the export directory where a mini blk indexer will be exported -/// * `start` - Inclusive starting height of the blocks received, `None` for 0 -/// * `end` - Inclusive ending height of the blocks received, `None` for the last one -/// * `rpc` - RPC client to filter out forks -/// -/// # Example -/// -/// ```rust -/// use bitcoincore_rpc::{Auth, Client}; -/// -/// fn main() { -/// let i = std::time::Instant::now(); -/// -/// let url = "http://localhost:8332"; -/// let auth = Auth::UserPass("satoshi".to_string(), "nakamoto".to_string()); -/// let rpc = Client::new(url, auth).unwrap(); -/// -/// let data_dir = "../../bitcoin"; -/// let export_dir = "./target"; -/// let start = Some(850_000); -/// let end = None; -/// -/// biter::new(data_dir, export_dir, start, end, rpc) -/// .iter() -/// .for_each(|(height, _block, hash)| { -/// println!("{height}: {hash}"); -/// }); -/// -/// dbg!(i.elapsed()); -///} -/// ``` -/// -pub fn new( - data_dir: &str, - export_dir: &str, - start: Option, - end: Option, - rpc: bitcoincore_rpc::Client, -) -> Receiver<(usize, Block, BlockHash)> { - let (send_block_reader, recv_block_reader) = bounded(BOUND_CAP); - let (send_block, recv_block) = bounded(BOUND_CAP); - let (send_height_block_hash, recv_height_block_hash) = bounded(BOUND_CAP); - - let blocks_dir = scan_blocks_dir(data_dir); - - let mut blk_index_to_blk_recap = BlkIndexToBlkRecap::import(&blocks_dir, export_dir); - - let start_recap = blk_index_to_blk_recap.get_start_recap(start); - let starting_blk_index = start_recap.as_ref().map_or(0, |(index, _)| *index); - - thread::spawn(move || { - blocks_dir - .into_iter() - .filter(|(blk_index, _)| blk_index >= &starting_blk_index) - .try_for_each(move |(blk_index, blk_path)| { - let blk_metadata = BlkMetadata::new(blk_index, &blk_path); - - let blk_bytes = fs::read(&blk_path).unwrap(); - let blk_bytes_len = blk_bytes.len() as u64; - - let mut cursor = Cursor::new(blk_bytes.as_slice()); - - let mut current_4bytes = [0; 4]; - - 'parent: loop { - if cursor.position() == blk_bytes_len { - break; - } - - // Read until we find a valid suite of MAGIC_BYTES - loop { - current_4bytes.rotate_left(1); - - if let Ok(byte) = cursor.read_u8() { - current_4bytes[3] = byte; - } else { - break 'parent; - } - - if current_4bytes == MAGIC_BYTES { - break; - } - } - - let block_size = cursor.read_u32().unwrap(); - - let mut raw_block = vec![0u8; block_size as usize]; - - cursor.read_exact(&mut raw_block).unwrap(); - - if send_block_reader - .send((blk_metadata, BlockState::Raw(raw_block))) - .is_err() - { - return ControlFlow::Break(()); - } - } - - ControlFlow::Continue(()) - }) - }); - - // thread::spawn(move || { - // recv_block_reader.iter().par_bridge().try_for_each( - // move |(blk_metadata, mut block_state)| { - // let raw_block = match block_state { - // BlockState::Raw(vec) => vec, - // _ => unreachable!(), - // }; - - // let mut cursor = Cursor::new(raw_block); - - // block_state = BlockState::Decoded(Block::consensus_decode(&mut cursor).unwrap()); - - // if send_block - // .send(BlkMetadataAndBlock::new( - // blk_metadata, - // match block_state { - // BlockState::Decoded(block) => block, - // _ => unreachable!(), - // }, - // )) - // .is_err() - // { - // return ControlFlow::Break(()); - // } - - // ControlFlow::Continue(()) - // }, - // ); - // }); - - // Can't use the previous code because .send() blocks all the threads if full - // And other .par_iter() are also stuck because of that - thread::spawn(move || { - let mut bulk = vec![]; - - let drain_and_send = |bulk: &mut Vec<_>| { - // Using a vec and sending after to not end up with stuck threads in par iter - bulk.par_iter_mut().for_each(|(_, block_state)| { - let raw_block = match block_state { - BlockState::Raw(vec) => vec, - _ => unreachable!(), - }; - - let mut cursor = Cursor::new(raw_block); - - *block_state = BlockState::Decoded(Block::consensus_decode(&mut cursor).unwrap()); - }); - - bulk.drain(..).try_for_each(|(blk_metadata, block_state)| { - let block = match block_state { - BlockState::Decoded(block) => block, - _ => unreachable!(), - }; - - if send_block - .send(BlkMetadataAndBlock::new(blk_metadata, block)) - .is_err() - { - return ControlFlow::Break(()); - } - - ControlFlow::Continue(()) - }) - }; - - recv_block_reader.iter().try_for_each(|tuple| { - bulk.push(tuple); - - if bulk.len() < BOUND_CAP / 2 { - return ControlFlow::Continue(()); - } - - drain_and_send(&mut bulk) - }); - - drain_and_send(&mut bulk) - }); - - // Tokio version: 1022s - // Slighlty slower than rayon version - // thread::spawn(move || { - // let rt = tokio::runtime::Runtime::new().unwrap(); - // let _guard = rt.enter(); - - // let mut tasks = VecDeque::with_capacity(BOUND); - - // recv_block_reader - // .iter() - // .try_for_each(move |(blk_metadata, block_state)| { - // let raw_block = match block_state { - // BlockState::Raw(vec) => vec, - // _ => unreachable!(), - // }; - - // tasks.push_back(tokio::task::spawn(async move { - // let block = Block::consensus_decode(&mut Cursor::new(raw_block)).unwrap(); - - // (blk_metadata, block) - // })); - - // while tasks.len() > BOUND { - // let (blk_metadata, block) = rt.block_on(tasks.pop_front().unwrap()).unwrap(); - - // if send_block - // .send(BlkMetadataAndBlock::new(blk_metadata, block)) - // .is_err() - // { - // return ControlFlow::Break(()); - // } - // } - - // ControlFlow::Continue(()) - // }); - // - // todo!("Send the rest") - // }); - - thread::spawn(move || { - let mut height = start_recap.map_or(0, |(_, recap)| recap.height()); - - let mut future_blocks = BTreeMap::default(); - let mut recent_chain: VecDeque<(BlockHash, BlkMetadataAndBlock)> = VecDeque::default(); - let mut recent_hashes: BTreeSet = BTreeSet::default(); - - let mut prev_hash = - start_recap.map_or_else(BlockHash::all_zeros, |(_, recap)| *recap.prev_hash()); - - let mut prepare_and_send = |(hash, tuple): (BlockHash, BlkMetadataAndBlock)| { - blk_index_to_blk_recap.update(&tuple, height); - - if start.map_or(true, |start| start <= height) { - send_height_block_hash - .send((height, tuple.block, hash)) - .unwrap(); - } - - if end.map_or(false, |end| height == end) { - return ControlFlow::Break(()); - } - - height += 1; - - ControlFlow::Continue(()) - }; - - let mut update_tip = |prev_hash: &mut BlockHash, - recent_hashes: &mut BTreeSet, - recent_chain: &mut VecDeque<(BlockHash, BlkMetadataAndBlock)>, - future_blocks: &mut BTreeMap, - tuple: BlkMetadataAndBlock| { - let mut tuple = Some(tuple); - - while let Some(tuple) = tuple.take().or_else(|| future_blocks.remove(prev_hash)) { - let hash = tuple.block.block_hash(); - - *prev_hash = hash; - recent_hashes.insert(hash); - recent_chain.push_back((hash, tuple)); - } - - while recent_chain.len() > NUMBER_OF_UNSAFE_BLOCKS { - let (hash, tuple) = recent_chain.pop_front().unwrap(); - - recent_hashes.remove(&hash); - - if prepare_and_send((hash, tuple)).is_break() { - return ControlFlow::Break(()); - } - } - - ControlFlow::Continue(()) - }; - - let flow = recv_block.iter().try_for_each(|tuple| { - // block isn't next after current tip - if prev_hash != tuple.block.header.prev_blockhash { - let is_block_active = - |hash| rpc.get_block_header_info(hash).unwrap().confirmations > 0; - - // block prev has already been processed - if recent_hashes.contains(&tuple.block.header.prev_blockhash) { - let hash = tuple.block.block_hash(); - - if is_block_active(&hash) { - let prev_index = recent_chain - .iter() - .position(|(hash, ..)| hash == &tuple.block.header.prev_blockhash) - .unwrap(); - - let bad_index_start = prev_index + 1; - - recent_chain.drain(bad_index_start..).for_each(|(hash, _)| { - recent_hashes.remove(&hash); - }); - - return update_tip( - &mut prev_hash, - &mut recent_hashes, - &mut recent_chain, - &mut future_blocks, - tuple, - ); - } - // Check if there was already a future block with the same prev hash - } else if let Some(prev_tuple) = - future_blocks.insert(tuple.block.header.prev_blockhash, tuple) - { - // If the previous was the active one - if is_block_active(&prev_tuple.block.block_hash()) { - // Rollback the insert - future_blocks.insert(prev_tuple.block.header.prev_blockhash, prev_tuple); - } - } - } else { - return update_tip( - &mut prev_hash, - &mut recent_hashes, - &mut recent_chain, - &mut future_blocks, - tuple, - ); - } - - ControlFlow::Continue(()) - }); - - if flow.is_continue() { - // Send the last (up to 100) blocks - recent_chain.into_iter().try_for_each(prepare_and_send); - } - - blk_index_to_blk_recap.export(); - }); - - recv_height_block_hash -} diff --git a/biter/src/main.rs b/biter/src/main.rs deleted file mode 100644 index 57ac1f020..000000000 --- a/biter/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -use bitcoincore_rpc::{Auth, Client}; - -fn main() { - let i = std::time::Instant::now(); - - let url = "http://localhost:8332"; - let auth = Auth::UserPass("satoshi".to_string(), "nakamoto".to_string()); - let rpc = Client::new(url, auth).unwrap(); - - let data_dir = "../bitcoin"; - let export_dir = "./target"; - let start = None; - let end = None; - - biter::new(data_dir, export_dir, start, end, rpc) - .iter() - .for_each(|(height, _block, hash)| { - println!("{height}: {hash}"); - }); - - dbg!(i.elapsed()); -} diff --git a/biter/src/utils.rs b/biter/src/utils.rs deleted file mode 100644 index 9b1bdb945..000000000 --- a/biter/src/utils.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::{collections::BTreeMap, fs, path::PathBuf, time::UNIX_EPOCH}; - -const BLK: &str = "blk"; -const DAT: &str = ".dat"; - -pub fn scan_blocks_dir(data_dir_path: &str) -> BTreeMap { - let blocks_dir_path = &format!("{data_dir_path}/blocks"); - - fs::read_dir(blocks_dir_path) - .unwrap() - .map(|entry| entry.unwrap().path()) - .filter(|path| { - let is_file = path.is_file(); - - if is_file { - let file_name = path.file_name().unwrap().to_str().unwrap(); - - file_name.starts_with(BLK) && file_name.ends_with(DAT) - } else { - false - } - }) - .map(|path| { - let file_name = path.file_name().unwrap().to_str().unwrap(); - - let blk_index = file_name[BLK.len()..(file_name.len() - DAT.len())] - .parse::() - .unwrap(); - - (blk_index, path) - }) - .collect::>() -} - -pub fn path_to_modified_time(path: &PathBuf) -> u64 { - fs::metadata(path) - .unwrap() - .modified() - .unwrap() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() -} diff --git a/crates/brk/Cargo.toml b/crates/brk/Cargo.toml new file mode 100644 index 000000000..9dd70b5dc --- /dev/null +++ b/crates/brk/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "brk" +description = "The Bitcoin Research Kit is a suite of tools designed to help you extract, explore and analyze the data stored from your node" +license.workspace = true +edition.workspace = true +version.workspace = true + +[features] +full = ["computer", "fetcher", "indexer", "logger", "parser", "server"] +computer = ["brk_computer"] +fetcher = ["brk_fetcher"] +indexer = ["brk_indexer"] +logger = ["brk_logger"] +parser = ["brk_parser"] +server = ["brk_server"] + +[dependencies] +brk_computer = { workspace = true, optional = true } +brk_fetcher = { workspace = true, optional = true } +brk_indexer = { workspace = true, optional = true } +brk_logger = { workspace = true, optional = true } +brk_parser = { workspace = true, optional = true } +brk_server = { workspace = true, optional = true } + +[package.metadata.docs.rs] +all-features = true diff --git a/crates/brk/src/lib.rs b/crates/brk/src/lib.rs new file mode 100644 index 000000000..458340dca --- /dev/null +++ b/crates/brk/src/lib.rs @@ -0,0 +1,35 @@ +#[cfg(feature = "computer")] +pub mod computer { + #[doc(inline)] + pub use brk_computer::*; +} + +#[cfg(feature = "fetcher")] +pub mod fetcher { + #[doc(inline)] + pub use brk_fetcher::*; +} + +#[cfg(feature = "indexer")] +pub mod indexer { + #[doc(inline)] + pub use brk_indexer::*; +} + +#[cfg(feature = "logger")] +pub mod logger { + #[doc(inline)] + pub use brk_logger::*; +} + +#[cfg(feature = "parser")] +pub mod parser { + #[doc(inline)] + pub use brk_parser::*; +} + +#[cfg(feature = "server")] +pub mod server { + #[doc(inline)] + pub use brk_server::*; +} diff --git a/crates/brk/src/main.rs b/crates/brk/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/crates/brk/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/crates/brk_cli/Cargo.toml b/crates/brk_cli/Cargo.toml new file mode 100644 index 000000000..f4ca6df65 --- /dev/null +++ b/crates/brk_cli/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "brk_cli" +description = "A command line interface to interact with the Bitcoin Research Kit" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] diff --git a/crates/brk_cli/src/main.rs b/crates/brk_cli/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/crates/brk_cli/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/crates/brk_computer/Cargo.toml b/crates/brk_computer/Cargo.toml new file mode 100644 index 000000000..add4956ff --- /dev/null +++ b/crates/brk_computer/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "brk_computer" +description = "A Bitcoin dataset computer built on top of brk_indexer and brk_fetcher" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +brk_fetcher = { workspace = true } +brk_indexer = { workspace = true } +brk_parser = { workspace = true } +byteview = { workspace = true } +color-eyre = { workspace = true } +derive_deref = { workspace = true } +fjall = { workspace = true } +hodor = { workspace = true } +storable_vec = { workspace = true } +zerocopy = { workspace = true } diff --git a/crates/brk_computer/src/lib.rs b/crates/brk_computer/src/lib.rs new file mode 100644 index 000000000..5ae619db4 --- /dev/null +++ b/crates/brk_computer/src/lib.rs @@ -0,0 +1,114 @@ +use std::path::{Path, PathBuf}; + +use brk_indexer::Indexer; +pub use brk_parser::rpc; +use hodor::Exit; + +mod storage; +mod structs; + +use brk_fetcher::Date; +use storable_vec::SINGLE_THREAD; +use storage::{Fjalls, StorableVecs}; +pub use structs::*; + +pub struct Computer { + path: PathBuf, + pub vecs: StorableVecs, + pub trees: Fjalls, +} + +impl Computer { + pub fn import(computed_dir: &Path) -> color_eyre::Result { + let vecs = StorableVecs::import(&computed_dir.join("vecs"))?; + let trees = Fjalls::import(&computed_dir.join("fjall"))?; + Ok(Self { + path: computed_dir.to_owned(), + vecs, + trees, + }) + } +} + +impl Computer { + pub fn compute(&mut self, mut indexer: Indexer, exit: &Exit) -> color_eyre::Result<()> { + let height_count = indexer.vecs.height_to_size.len(); + let txindexes_count = indexer.vecs.txindex_to_txid.len(); + let txinindexes_count = indexer.vecs.txinindex_to_txoutindex.len(); + let txoutindexes_count = indexer.vecs.txoutindex_to_addressindex.len(); + + // TODO: Remove all outdated + + self.vecs + .txindex_to_last_txinindex + .compute_last_index_from_first(&mut indexer.vecs.txindex_to_first_txinindex, txinindexes_count)?; + + self.vecs.txindex_to_inputs_count.compute_count_from_indexes( + &mut indexer.vecs.txindex_to_first_txinindex, + &mut self.vecs.txindex_to_last_txinindex, + )?; + + self.vecs + .txindex_to_last_txoutindex + .compute_last_index_from_first(&mut indexer.vecs.txindex_to_first_txoutindex, txoutindexes_count)?; + + self.vecs.txindex_to_outputs_count.compute_count_from_indexes( + &mut indexer.vecs.txindex_to_first_txoutindex, + &mut self.vecs.txindex_to_last_txoutindex, + )?; + + self.vecs + .height_to_date + .compute_transform(&mut indexer.vecs.height_to_timestamp, |timestamp| { + Date::from(*timestamp) + })?; + + self.vecs + .height_to_last_txindex + .compute_last_index_from_first(&mut indexer.vecs.height_to_first_txindex, height_count)?; + + self.vecs.txindex_to_height.compute_inverse_less_to_more( + &mut indexer.vecs.height_to_first_txindex, + &mut self.vecs.height_to_last_txindex, + )?; + + self.vecs.txindex_to_is_coinbase.compute_is_first_ordered( + &mut self.vecs.txindex_to_height, + &mut indexer.vecs.height_to_first_txindex, + )?; + + // self.vecs.txindex_to_fee.compute_transform( + // &mut self.vecs.txindex_to_height, + // &mut indexer.vecs.height_to_first_txindex, + // )?; + + let date_count = self.vecs.height_to_date.len(); + + // self.vecs.height_to_dateindex.compute(...) + + self.vecs + .dateindex_to_first_height + .compute_inverse_more_to_less(&mut self.vecs.height_to_dateindex)?; + + // --- + // Date to X + // --- + // ... + + // --- + // Month to X + // --- + // ... + + // --- + // Year to X + // --- + // ... + + Ok(()) + } + + pub fn path(&self) -> &Path { + &self.path + } +} diff --git a/crates/brk_computer/src/main.rs b/crates/brk_computer/src/main.rs new file mode 100644 index 000000000..acf96bc03 --- /dev/null +++ b/crates/brk_computer/src/main.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use brk_computer::Computer; +use brk_indexer::Indexer; +use hodor::Exit; + +mod structs; + +pub fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + let exit = Exit::new(); + + let i = std::time::Instant::now(); + + let outputs_dir = Path::new("../_outputs"); + + Computer::import(&outputs_dir.join("computed"))?.compute(Indexer::import(&outputs_dir.join("indexes"))?, &exit)?; + + dbg!(i.elapsed()); + + Ok(()) +} diff --git a/crates/brk_computer/src/storage/fjalls.rs b/crates/brk_computer/src/storage/fjalls.rs new file mode 100644 index 000000000..38221d0be --- /dev/null +++ b/crates/brk_computer/src/storage/fjalls.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use brk_indexer::Store; +use storable_vec::Version; + +use crate::structs::{AddressindexTxoutindex, Unit}; + +pub struct Fjalls { + pub address_to_utxos_received: Store, + pub address_to_utxos_spent: Store, +} + +impl Fjalls { + pub fn import(path: &Path) -> color_eyre::Result { + let address_to_utxos_received = Store::import(&path.join("address_to_utxos_received"), Version::from(1))?; + let address_to_utxos_spent = Store::import(&path.join("address_to_utxos_spent"), Version::from(1))?; + + Ok(Self { + address_to_utxos_received, + address_to_utxos_spent, + }) + } +} diff --git a/crates/brk_computer/src/storage/mod.rs b/crates/brk_computer/src/storage/mod.rs new file mode 100644 index 000000000..15d757c6a --- /dev/null +++ b/crates/brk_computer/src/storage/mod.rs @@ -0,0 +1,5 @@ +mod fjalls; +mod storable_vecs; + +pub use fjalls::*; +pub use storable_vecs::*; diff --git a/crates/brk_computer/src/storage/storable_vecs.rs b/crates/brk_computer/src/storage/storable_vecs.rs new file mode 100644 index 000000000..a39ec0de4 --- /dev/null +++ b/crates/brk_computer/src/storage/storable_vecs.rs @@ -0,0 +1,102 @@ +use std::{fs, path::Path}; + +use brk_fetcher::{Date, Dateindex}; +use brk_indexer::{Addressindex, Height, Sats, Timestamp, Txindex, Txinindex, Txoutindex}; +use storable_vec::{StorableVec, Version}; + +use crate::structs::Feerate; + +// mod base; + +// use base::*; + +pub struct StorableVecs { + pub dateindex_to_first_height: StorableVec, + // pub dateindex_to_last_height: StorableVec, + // pub height_to_block_interval: StorableVec, + pub height_to_date: StorableVec, + pub height_to_dateindex: StorableVec, + // pub height_to_fee: StorableVec, + // pub height_to_inputcount: StorableVec, + // pub height_to_last_addressindex: StorableVec, + pub height_to_last_txindex: StorableVec, + // pub height_to_last_txoutindex: StorableVec, + // pub height_to_maxfeerate: StorableVec, + // pub height_to_medianfeerate: StorableVec, + // pub height_to_minfeerate: StorableVec, + // pub height_to_outputcount: StorableVec, + // pub height_to_subsidy: StorableVec, + // pub height_to_totalfees: StorableVec, + // pub height_to_txcount: StorableVec, + pub txindex_to_fee: StorableVec, + pub txindex_to_height: StorableVec, + pub txindex_to_is_coinbase: StorableVec, + // pub txindex_to_feerate: StorableVec, + pub txindex_to_inputs_count: StorableVec, + pub txindex_to_inputs_sum: StorableVec, + pub txindex_to_last_txinindex: StorableVec, + pub txindex_to_last_txoutindex: StorableVec, + pub txindex_to_outputs_count: StorableVec, + pub txindex_to_outputs_sum: StorableVec, +} + +impl StorableVecs { + pub fn import(path: &Path) -> color_eyre::Result { + fs::create_dir_all(path)?; + + Ok(Self { + dateindex_to_first_height: StorableVec::forced_import( + &path.join("dateindex_to_first_height"), + Version::from(1), + )?, + // height_to_block_interval: StorableVec::forced_import(&path.join("height_to_block_interval"), Version::from(1))?, + height_to_date: StorableVec::forced_import(&path.join("height_to_date"), Version::from(1))?, + height_to_dateindex: StorableVec::forced_import(&path.join("height_to_dateindex"), Version::from(1))?, + // height_to_fee: StorableVec::forced_import(&path.join("height_to_fee"), Version::from(1))?, + // height_to_inputcount: StorableVec::forced_import(&path.join("height_to_inputcount"), Version::from(1))?, + // height_to_last_addressindex: StorableVec::forced_import( + // &path.join("height_to_last_addressindex"), + // Version::from(1), + // )?, + height_to_last_txindex: StorableVec::forced_import(&path.join("height_to_last_txindex"), Version::from(1))?, + // height_to_last_txoutindex: StorableVec::forced_import(&path.join("height_to_last_txoutindex"), Version::from(1))?, + // height_to_maxfeerate: StorableVec::forced_import(&path.join("height_to_maxfeerate"), Version::from(1))?, + // height_to_medianfeerate: StorableVec::forced_import(&path.join("height_to_medianfeerate"), Version::from(1))?, + // height_to_minfeerate: StorableVec::forced_import(&path.join("height_to_minfeerate"), Version::from(1))?, + // height_to_outputcount: StorableVec::forced_import(&path.join("height_to_outputcount"), Version::from(1))?, + // height_to_subsidy: StorableVec::forced_import(&path.join("height_to_subsidy"), Version::from(1))?, + // height_to_totalfees: StorableVec::forced_import(&path.join("height_to_totalfees"), Version::from(1))?, + // height_to_txcount: StorableVec::forced_import(&path.join("height_to_txcount"), Version::from(1))?, + txindex_to_fee: StorableVec::forced_import(&path.join("txindex_to_fee"), Version::from(1))?, + txindex_to_height: StorableVec::forced_import(&path.join("txindex_to_height"), Version::from(1))?, + txindex_to_is_coinbase: StorableVec::forced_import(&path.join("txindex_to_is_coinbase"), Version::from(1))?, + // txindex_to_feerate: StorableVec::forced_import(&path.join("txindex_to_feerate"), Version::from(1))?, + txindex_to_inputs_count: StorableVec::forced_import( + &path.join("txindex_to_inputs_count"), + Version::from(1), + )?, + txindex_to_inputs_sum: StorableVec::forced_import(&path.join("txindex_to_inputs_sum"), Version::from(1))?, + txindex_to_last_txinindex: StorableVec::forced_import( + &path.join("txindex_to_last_txinindex"), + Version::from(1), + )?, + txindex_to_last_txoutindex: StorableVec::forced_import( + &path.join("txindex_to_last_txoutindex"), + Version::from(1), + )?, + txindex_to_outputs_count: StorableVec::forced_import( + &path.join("txindex_to_outputs_count"), + Version::from(1), + )?, + txindex_to_outputs_sum: StorableVec::forced_import(&path.join("txindex_to_outputs_sum"), Version::from(1))?, + }) + } + + // pub fn as_slice(&self) -> [&dyn AnyComputedStorableVec; 1] { + // [&self.dateindex_to_first_height] + // } + + // pub fn as_mut_slice(&mut self) -> [&mut dyn AnyComputedStorableVec; 1] { + // [&mut self.dateindex_to_first_height] + // } +} diff --git a/crates/brk_computer/src/structs/addressindextxoutindex.rs b/crates/brk_computer/src/structs/addressindextxoutindex.rs new file mode 100644 index 000000000..11a743b50 --- /dev/null +++ b/crates/brk_computer/src/structs/addressindextxoutindex.rs @@ -0,0 +1,22 @@ +use brk_indexer::{Addressindex, Txoutindex}; +use byteview::ByteView; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Immutable, IntoBytes, KnownLayout, FromBytes)] +pub struct AddressindexTxoutindex { + addressindex: Addressindex, + _padding: u32, + txoutindex: Txoutindex, +} + +impl TryFrom for AddressindexTxoutindex { + type Error = storable_vec::Error; + fn try_from(value: ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} +impl From for ByteView { + fn from(value: AddressindexTxoutindex) -> Self { + Self::new(value.as_bytes()) + } +} diff --git a/crates/brk_computer/src/structs/bitcoin.rs b/crates/brk_computer/src/structs/bitcoin.rs new file mode 100644 index 000000000..36deb7800 --- /dev/null +++ b/crates/brk_computer/src/structs/bitcoin.rs @@ -0,0 +1,14 @@ +use brk_indexer::Sats; + +#[derive(Debug, Default, Clone, Copy)] +pub struct Bitcoin(f64); + +impl Bitcoin { + const ONE: Self = Self(100_000_000.0); +} + +impl From for Bitcoin { + fn from(value: Sats) -> Self { + Self((*value as f64) / Self::ONE.0) + } +} diff --git a/crates/brk_computer/src/structs/feerate.rs b/crates/brk_computer/src/structs/feerate.rs new file mode 100644 index 000000000..52ed2fad9 --- /dev/null +++ b/crates/brk_computer/src/structs/feerate.rs @@ -0,0 +1,4 @@ +use derive_deref::Deref; + +#[derive(Debug, Deref, Clone, Copy)] +pub struct Feerate(f32); diff --git a/crates/brk_computer/src/structs/mod.rs b/crates/brk_computer/src/structs/mod.rs new file mode 100644 index 000000000..7edf1230f --- /dev/null +++ b/crates/brk_computer/src/structs/mod.rs @@ -0,0 +1,11 @@ +mod addressindextxoutindex; +mod bitcoin; +mod feerate; +// mod ohlc; +mod unit; + +pub use addressindextxoutindex::*; +pub use bitcoin::*; +pub use feerate::*; +// pub use ohlc::*; +pub use unit::*; diff --git a/crates/brk_computer/src/structs/ohlc.rs b/crates/brk_computer/src/structs/ohlc.rs new file mode 100644 index 000000000..a37e5ba2d --- /dev/null +++ b/crates/brk_computer/src/structs/ohlc.rs @@ -0,0 +1,36 @@ +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::{Cents, Close, High, Low, Open}; + +// #[derive(Debug, Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout, Serialize)] +// #[repr(C)] +// pub struct OHLCCents(OHLC); + +#[derive(Debug, Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout, Serialize)] +#[repr(C)] +pub struct OHLCCents(Open, High, Low, Close); + +impl OHLCCents { + pub fn open(&self) -> Open { + self.0 + } + + pub fn high(&self) -> High { + self.1 + } + + pub fn low(&self) -> Low { + self.2 + } + + pub fn close(&self) -> Close { + self.3 + } +} + +impl From<(Open, High, Low, Close)> for OHLCCents { + fn from(value: (Open, High, Low, Close)) -> Self { + Self(value.0, value.1, value.2, value.3) + } +} diff --git a/crates/brk_computer/src/structs/unit.rs b/crates/brk_computer/src/structs/unit.rs new file mode 100644 index 000000000..e9754fb93 --- /dev/null +++ b/crates/brk_computer/src/structs/unit.rs @@ -0,0 +1,13 @@ +use byteview::ByteView; + +pub struct Unit(); +impl From for Unit { + fn from(_: ByteView) -> Self { + Self() + } +} +impl From for ByteView { + fn from(_: Unit) -> Self { + Self::new(&[]) + } +} diff --git a/crates/brk_fetcher/Cargo.toml b/crates/brk_fetcher/Cargo.toml new file mode 100644 index 000000000..78349ea11 --- /dev/null +++ b/crates/brk_fetcher/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "brk_fetcher" +description = "A Bitcoin price fetcher built on top of brk_indexer" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +brk_indexer = { workspace = true } +brk_logger = { workspace = true } +color-eyre = { workspace = true } +derive_deref = { workspace = true } +jiff = { workspace = true } +log = { workspace = true } +minreq = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +storable_vec = { workspace = true } +zerocopy = { workspace = true } diff --git a/crates/brk_fetcher/src/fetchers/binance.rs b/crates/brk_fetcher/src/fetchers/binance.rs new file mode 100644 index 000000000..6bd444b60 --- /dev/null +++ b/crates/brk_fetcher/src/fetchers/binance.rs @@ -0,0 +1,216 @@ +use std::{ + collections::BTreeMap, + fs::{self, File}, + io::BufReader, + path::{Path, PathBuf}, + str::FromStr, +}; + +use brk_indexer::Timestamp; +use color_eyre::eyre::{ContextCompat, eyre}; +use log::info; +use serde_json::Value; +use storable_vec::STATELESS; + +use crate::{ + Close, Date, Dollars, High, Low, Open, Pricer, + fetchers::retry, + structs::{Cents, OHLC}, +}; + +pub struct Binance { + path: PathBuf, + _1mn: Option>, + _1d: Option>, + har: Option>, +} + +impl Binance { + pub fn init(path: &Path) -> Self { + Self { + path: path.to_owned(), + _1mn: None, + _1d: None, + har: None, + } + } + + pub fn get_from_1mn( + &mut self, + timestamp: Timestamp, + previous_timestamp: Option, + ) -> color_eyre::Result { + if self._1mn.is_none() || self._1mn.as_ref().unwrap().last_key_value().unwrap().0 <= ×tamp { + self._1mn.replace(Self::fetch_1mn()?); + } + Pricer::::find_height_ohlc( + self._1mn.as_ref().unwrap(), + timestamp, + previous_timestamp, + "binance 1mn", + ) + } + + pub fn fetch_1mn() -> color_eyre::Result> { + info!("Fetching 1mn prices from Binance..."); + + retry( + |_| Self::json_to_timestamp_to_ohlc(&minreq::get(Self::url("interval=1m&limit=1000")).send()?.json()?), + 30, + 10, + ) + } + + pub fn get_from_1d(&mut self, date: &Date) -> color_eyre::Result { + if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 < date { + self._1d.replace(Self::fetch_1d()?); + } + + self._1d + .as_ref() + .unwrap() + .get(date) + .cloned() + .ok_or(color_eyre::eyre::Error::msg("Couldn't find date")) + } + + pub fn fetch_1d() -> color_eyre::Result> { + info!("Fetching daily prices from Kraken..."); + + dbg!(&Self::url("interval=1d")); + + retry( + |_| Self::json_to_date_to_ohlc(&minreq::get(Self::url("interval=1d")).send()?.json()?), + 30, + 10, + ) + } + + pub fn get_from_har_binance( + &mut self, + timestamp: Timestamp, + previous_timestamp: Option, + ) -> color_eyre::Result { + if self.har.is_none() { + self.har.replace(self.read_har().unwrap_or_default()); + } + Pricer::::find_height_ohlc(self.har.as_ref().unwrap(), timestamp, previous_timestamp, "binance har") + } + + fn read_har(&self) -> color_eyre::Result> { + info!("Reading Binance har file..."); + + let path = &self.path; + + fs::create_dir_all(path)?; + + let path_binance_har = path.join("binance.har"); + + let file = if let Ok(file) = File::open(path_binance_har) { + file + } else { + return Err(eyre!("Missing binance file")); + }; + + let reader = BufReader::new(file); + + let json: BTreeMap = if let Ok(json) = serde_json::from_reader(reader) { + json + } else { + return Ok(Default::default()); + }; + + json.get("log") + .context("Expect object to have log attribute")? + .as_object() + .context("Expect to be an object")? + .get("entries") + .context("Expect object to have entries")? + .as_array() + .context("Expect to be an array")? + .iter() + .filter(|entry| { + entry + .as_object() + .unwrap() + .get("request") + .unwrap() + .as_object() + .unwrap() + .get("url") + .unwrap() + .as_str() + .unwrap() + .contains("/uiKlines") + }) + .map(|entry| { + let response = entry.as_object().unwrap().get("response").unwrap().as_object().unwrap(); + + let content = response.get("content").unwrap().as_object().unwrap(); + + let text = content.get("text"); + + if text.is_none() { + return Ok(BTreeMap::new()); + } + + let text = text.unwrap().as_str().unwrap(); + + Self::json_to_timestamp_to_ohlc(&serde_json::Value::from_str(text).unwrap()) + }) + .try_fold(BTreeMap::default(), |mut all, res| { + all.append(&mut res?); + Ok(all) + }) + } + + fn json_to_timestamp_to_ohlc(json: &Value) -> color_eyre::Result> { + Self::json_to_btree(json, Self::array_to_timestamp_and_ohlc) + } + + fn json_to_date_to_ohlc(json: &Value) -> color_eyre::Result> { + Self::json_to_btree(json, Self::array_to_date_and_ohlc) + } + + fn json_to_btree(json: &Value, fun: F) -> color_eyre::Result> + where + F: Fn(&Value) -> color_eyre::Result<(K, V)>, + K: Ord, + { + json.as_array() + .context("Expect to be an array")? + .iter() + .map(fun) + .collect::, _>>() + } + + fn array_to_timestamp_and_ohlc(array: &Value) -> color_eyre::Result<(Timestamp, OHLC)> { + let array = array.as_array().context("Expect to be array")?; + + let timestamp = Timestamp::from((array.first().unwrap().as_u64().unwrap() / 1_000) as u32); + + let get_cents = |index: usize| { + Cents::from(Dollars::from( + array.get(index).unwrap().as_str().unwrap().parse::().unwrap(), + )) + }; + + Ok(( + timestamp, + OHLC::from(( + Open::from(get_cents(1)), + High::from(get_cents(2)), + Low::from(get_cents(3)), + Close::from(get_cents(4)), + )), + )) + } + + fn array_to_date_and_ohlc(array: &Value) -> color_eyre::Result<(Date, OHLC)> { + Self::array_to_timestamp_and_ohlc(array).map(|(t, ohlc)| (Date::from(t), ohlc)) + } + + fn url(query: &str) -> String { + format!("https://api.binance.com/api/v3/uiKlines?symbol=BTCUSDT&{query}") + } +} diff --git a/crates/brk_fetcher/src/fetchers/kibo.rs b/crates/brk_fetcher/src/fetchers/kibo.rs new file mode 100644 index 000000000..b5327375b --- /dev/null +++ b/crates/brk_fetcher/src/fetchers/kibo.rs @@ -0,0 +1,161 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use brk_indexer::Height; +use color_eyre::eyre::ContextCompat; +use log::info; +use serde_json::Value; + +use crate::{ + Cents, Close, Dollars, High, Low, Open, + fetchers::retry, + structs::{Date, OHLC}, +}; + +#[derive(Default)] +pub struct Kibo { + height_to_ohlc_vec: BTreeMap>, + year_to_date_to_ohlc: BTreeMap>, +} + +const KIBO_OFFICIAL_URL: &str = "https://kibo.money/api"; +const KIBO_OFFICIAL_BACKUP_URL: &str = "https://backup.kibo.money/api"; + +const RETRIES: usize = 10; + +impl Kibo { + fn get_base_url(try_index: usize) -> &'static str { + if try_index < RETRIES / 2 { + KIBO_OFFICIAL_URL + } else { + KIBO_OFFICIAL_BACKUP_URL + } + } + + pub fn get_from_height_kibo(&mut self, height: Height) -> color_eyre::Result { + #[allow(clippy::map_entry)] + if !self.height_to_ohlc_vec.contains_key(&height) + || ((usize::from(height) + self.height_to_ohlc_vec.get(&height).unwrap().len()) <= usize::from(height)) + { + self.height_to_ohlc_vec + .insert(height, Self::fetch_height_prices(height)?); + } + + self.height_to_ohlc_vec + .get(&height) + .unwrap() + .get(usize::from(height)) + .cloned() + .ok_or(color_eyre::eyre::Error::msg("Couldn't find height in kibo")) + } + + fn fetch_height_prices(height: Height) -> color_eyre::Result> { + info!("Fetching Kibo height prices..."); + + retry( + |try_index| { + let base_url = Self::get_base_url(try_index); + + let body: Value = minreq::get(format!("{base_url}/height-to-price?chunk={}", height)) + .send()? + .json()?; + + let vec = body + .as_object() + .context("Expect to be an object")? + .get("dataset") + .context("Expect object to have dataset")? + .as_object() + .context("Expect to be an object")? + .get("map") + .context("Expect to have map")? + .as_array() + .context("Expect to be an array")? + .iter() + .map(Self::value_to_ohlc) + .collect::, _>>()?; + + Ok(vec) + }, + 30, + RETRIES, + ) + } + + pub fn get_from_date_kibo(&mut self, date: &Date) -> color_eyre::Result { + let year = date.year(); + + #[allow(clippy::map_entry)] + if !self.year_to_date_to_ohlc.contains_key(&year) + || self + .year_to_date_to_ohlc + .get(&year) + .unwrap() + .last_key_value() + .unwrap() + .0 + < date + { + self.year_to_date_to_ohlc.insert(year, Self::fetch_date_prices(year)?); + } + + self.year_to_date_to_ohlc + .get(&year) + .unwrap() + .get(date) + .cloned() + .ok_or(color_eyre::eyre::Error::msg("Couldn't find date in kibo")) + } + + fn fetch_date_prices(year: u16) -> color_eyre::Result> { + info!("Fetching Kibo date prices..."); + + retry( + |try_index| { + let base_url = Self::get_base_url(try_index); + + let body: Value = minreq::get(format!("{base_url}/date-to-price?chunk={}", year)) + .send()? + .json()?; + + body.as_object() + .context("Expect to be an object")? + .get("dataset") + .context("Expect object to have dataset")? + .as_object() + .context("Expect to be an object")? + .get("map") + .context("Expect to have map")? + .as_object() + .context("Expect to be an object")? + .iter() + .map(|(serialized_date, value)| -> color_eyre::Result<_> { + let date = Date::from(jiff::civil::Date::from_str(serialized_date).unwrap()); + Ok((date, Self::value_to_ohlc(value)?)) + }) + .collect::, _>>() + }, + 30, + RETRIES, + ) + } + + fn value_to_ohlc(value: &Value) -> color_eyre::Result { + let ohlc = value.as_object().context("Expect as_object to work")?; + + let get_value = |key: &str| -> color_eyre::Result<_> { + Ok(Cents::from(Dollars::from( + ohlc.get(key) + .context("Expect get key to work")? + .as_f64() + .context("Expect as_f64 to work")?, + ))) + }; + + Ok(( + Open::from(get_value("open")?), + High::from(get_value("high")?), + Low::from(get_value("low")?), + Close::from(get_value("close")?), + )) + } +} diff --git a/crates/brk_fetcher/src/fetchers/kraken.rs b/crates/brk_fetcher/src/fetchers/kraken.rs new file mode 100644 index 000000000..49618fcee --- /dev/null +++ b/crates/brk_fetcher/src/fetchers/kraken.rs @@ -0,0 +1,118 @@ +use std::collections::BTreeMap; + +use brk_indexer::Timestamp; +use color_eyre::eyre::ContextCompat; +use log::info; +use serde_json::Value; +use storable_vec::STATELESS; + +use crate::{Cents, Close, Dollars, High, Low, OHLC, Open, Pricer, fetchers::retry, structs::Date}; + +#[derive(Default)] +pub struct Kraken { + _1mn: Option>, + _1d: Option>, +} + +impl Kraken { + pub fn get_from_1mn( + &mut self, + timestamp: Timestamp, + previous_timestamp: Option, + ) -> color_eyre::Result { + if self._1mn.is_none() || self._1mn.as_ref().unwrap().last_key_value().unwrap().0 <= ×tamp { + self._1mn.replace(Self::fetch_1mn()?); + } + Pricer::::find_height_ohlc(self._1mn.as_ref().unwrap(), timestamp, previous_timestamp, "kraken 1m") + } + + fn fetch_1mn() -> color_eyre::Result> { + info!("Fetching 1mn prices from Kraken..."); + + retry( + |_| Self::json_to_timestamp_to_ohlc(&minreq::get(Self::url(1)).send()?.json()?), + 30, + 10, + ) + } + + pub fn get_from_1d(&mut self, date: &Date) -> color_eyre::Result { + if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 < date { + self._1d.replace(Kraken::fetch_1d()?); + } + self._1d + .as_ref() + .unwrap() + .get(date) + .cloned() + .ok_or(color_eyre::eyre::Error::msg("Couldn't find date")) + } + + fn fetch_1d() -> color_eyre::Result> { + info!("Fetching daily prices from Kraken..."); + + retry( + |_| Self::json_to_date_to_ohlc(&minreq::get(Self::url(1440)).send()?.json()?), + 30, + 10, + ) + } + + fn json_to_timestamp_to_ohlc(json: &Value) -> color_eyre::Result> { + Self::json_to_btree(json, Self::array_to_timestamp_and_ohlc) + } + + fn json_to_date_to_ohlc(json: &Value) -> color_eyre::Result> { + Self::json_to_btree(json, Self::array_to_date_and_ohlc) + } + + fn json_to_btree(json: &Value, fun: F) -> color_eyre::Result> + where + F: Fn(&Value) -> color_eyre::Result<(K, V)>, + K: Ord, + { + json.as_object() + .context("Expect to be an object")? + .get("result") + .context("Expect object to have result")? + .as_object() + .context("Expect to be an object")? + .get("XXBTZUSD") + .context("Expect to have XXBTZUSD")? + .as_array() + .context("Expect to be an array")? + .iter() + .map(fun) + .collect::, _>>() + } + + fn array_to_timestamp_and_ohlc(array: &Value) -> color_eyre::Result<(Timestamp, OHLC)> { + let array = array.as_array().context("Expect to be array")?; + + let timestamp = Timestamp::from(array.first().unwrap().as_u64().unwrap() as u32); + + let get_cents = |index: usize| { + Cents::from(Dollars::from( + array.get(index).unwrap().as_str().unwrap().parse::().unwrap(), + )) + }; + + Ok(( + timestamp, + OHLC::from(( + Open::from(get_cents(1)), + High::from(get_cents(2)), + Low::from(get_cents(3)), + Close::from(get_cents(4)), + )), + )) + } + + fn array_to_date_and_ohlc(array: &Value) -> color_eyre::Result<(Date, OHLC)> { + Self::array_to_timestamp_and_ohlc(array).map(|(t, ohlc)| (Date::from(t), ohlc)) + } + + fn url(interval: usize) -> String { + format!("https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval={interval}") + } +} diff --git a/crates/brk_fetcher/src/fetchers/mod.rs b/crates/brk_fetcher/src/fetchers/mod.rs new file mode 100644 index 000000000..c6704727b --- /dev/null +++ b/crates/brk_fetcher/src/fetchers/mod.rs @@ -0,0 +1,9 @@ +mod binance; +mod kibo; +mod kraken; +mod retry; + +pub use binance::*; +pub use kibo::*; +pub use kraken::*; +use retry::*; diff --git a/crates/brk_fetcher/src/fetchers/retry.rs b/crates/brk_fetcher/src/fetchers/retry.rs new file mode 100644 index 000000000..3e1ab4675 --- /dev/null +++ b/crates/brk_fetcher/src/fetchers/retry.rs @@ -0,0 +1,24 @@ +use std::{thread::sleep, time::Duration}; + +use log::info; + +pub fn retry( + function: impl Fn(usize) -> color_eyre::Result, + sleep_in_s: u64, + retries: usize, +) -> color_eyre::Result { + let mut i = 0; + + loop { + let res = function(i); + + if i == retries || res.is_ok() { + return res; + } else { + info!("Failed, waiting {sleep_in_s} seconds..."); + sleep(Duration::from_secs(sleep_in_s)); + } + + i += 1; + } +} diff --git a/crates/brk_fetcher/src/lib.rs b/crates/brk_fetcher/src/lib.rs new file mode 100644 index 000000000..7de87c79c --- /dev/null +++ b/crates/brk_fetcher/src/lib.rs @@ -0,0 +1,280 @@ +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, +}; + +use color_eyre::eyre::Error; + +mod fetchers; +mod structs; + +use brk_indexer::{Height, Indexer, Timestamp}; +pub use fetchers::*; +use storable_vec::{AnyJsonStorableVec, AnyStorableVec, SINGLE_THREAD, StorableVec, Version}; +pub use structs::*; + +pub struct Pricer { + path: PathBuf, + binance: Binance, + kraken: Kraken, + kibo: Kibo, + + pub dateindex_to_close_in_cents: StorableVec, MODE>, + pub dateindex_to_close_in_dollars: StorableVec, MODE>, + pub dateindex_to_high_in_cents: StorableVec, MODE>, + pub dateindex_to_high_in_dollars: StorableVec, MODE>, + pub dateindex_to_low_in_cents: StorableVec, MODE>, + pub dateindex_to_low_in_dollars: StorableVec, MODE>, + pub dateindex_to_open_in_cents: StorableVec, MODE>, + pub dateindex_to_open_in_dollars: StorableVec, MODE>, + pub height_to_close_in_cents: StorableVec, MODE>, + pub height_to_close_in_dollars: StorableVec, MODE>, + pub height_to_high_in_cents: StorableVec, MODE>, + pub height_to_high_in_dollars: StorableVec, MODE>, + pub height_to_low_in_cents: StorableVec, MODE>, + pub height_to_low_in_dollars: StorableVec, MODE>, + pub height_to_open_in_cents: StorableVec, MODE>, + pub height_to_open_in_dollars: StorableVec, MODE>, +} + +impl Pricer { + pub fn import(path: &Path) -> color_eyre::Result { + fs::create_dir_all(path)?; + + Ok(Self { + path: path.to_owned(), + binance: Binance::init(path), + kraken: Kraken::default(), + kibo: Kibo::default(), + + // binance_1mn: None, + // binance_daily: None, + // binance_har: None, + // kraken_1mn: None, + // kraken_daily: None, + // kibo_by_height: BTreeMap::default(), + // kibo_by_date: BTreeMap::default(), + dateindex_to_close_in_cents: StorableVec::import( + &path.join("dateindex_to_close_in_cents"), + Version::from(1), + )?, + dateindex_to_close_in_dollars: StorableVec::import( + &path.join("dateindex_to_close_in_dollars"), + Version::from(1), + )?, + dateindex_to_high_in_cents: StorableVec::import( + &path.join("dateindex_to_high_in_cents"), + Version::from(1), + )?, + dateindex_to_high_in_dollars: StorableVec::import( + &path.join("dateindex_to_high_in_dollars"), + Version::from(1), + )?, + dateindex_to_low_in_cents: StorableVec::import(&path.join("dateindex_to_low_in_cents"), Version::from(1))?, + dateindex_to_low_in_dollars: StorableVec::import( + &path.join("dateindex_to_low_in_dollars"), + Version::from(1), + )?, + dateindex_to_open_in_cents: StorableVec::import( + &path.join("dateindex_to_open_in_cents"), + Version::from(1), + )?, + dateindex_to_open_in_dollars: StorableVec::import( + &path.join("dateindex_to_open_in_dollars"), + Version::from(1), + )?, + height_to_close_in_cents: StorableVec::import(&path.join("height_to_close_in_cents"), Version::from(1))?, + height_to_close_in_dollars: StorableVec::import( + &path.join("height_to_close_in_dollars"), + Version::from(1), + )?, + height_to_high_in_cents: StorableVec::import(&path.join("height_to_high_in_cents"), Version::from(1))?, + height_to_high_in_dollars: StorableVec::import(&path.join("height_to_high_in_dollars"), Version::from(1))?, + height_to_low_in_cents: StorableVec::import(&path.join("height_to_low_in_cents"), Version::from(1))?, + height_to_low_in_dollars: StorableVec::import(&path.join("height_to_low_in_dollars"), Version::from(1))?, + height_to_open_in_cents: StorableVec::import(&path.join("height_to_open_in_cents"), Version::from(1))?, + height_to_open_in_dollars: StorableVec::import(&path.join("height_to_open_in_dollars"), Version::from(1))?, + }) + } + + pub fn compute_if_needed(&mut self, indexer: &mut Indexer) { + // TODO: Remove all outdated + + indexer + .vecs + .height_to_timestamp + .iter_from(Height::default(), |v| Ok(())); + + // self.open + // .multi_insert_simple_transform(heights, dates, &mut self.ohlc, &|ohlc| ohlc.open); + + // self.high + // .multi_insert_simple_transform(heights, dates, &mut self.ohlc, &|ohlc| ohlc.high); + + // self.low + // .multi_insert_simple_transform(heights, dates, &mut self.ohlc, &|ohlc| ohlc.low); + + // self.close + // .multi_insert_simple_transform(heights, dates, &mut self.ohlc, &|ohlc| ohlc.close); + } + + fn get_date_ohlc(&mut self, date: Date) -> color_eyre::Result { + todo!(); + // if self.ohlc.date.is_key_safe(date) { + // Ok(self.ohlc.date.get_or_import(&date).unwrap().to_owned()) + // } else { + // let ohlc = self + // .get_from_daily_kraken(&date) + // .or_else(|_| self.get_from_daily_binance(&date)) + // .or_else(|_| self.get_from_date_kibo(&date))?; + + // self.ohlc.date.insert(date, ohlc); + + // Ok(ohlc) + // } + } + + fn get_height_ohlc( + &mut self, + height: Height, + timestamp: Timestamp, + previous_timestamp: Option, + ) -> color_eyre::Result { + todo!(); + + // if let Some(ohlc) = self.ohlc.height.get_or_import(&height) { + // return Ok(ohlc); + // } + + // let timestamp = timestamp.to_floored_seconds(); + + // if previous_timestamp.is_none() && !height.is_first() { + // panic!("Shouldn't be possible"); + // } + + // let previous_timestamp = previous_timestamp.map(|t| t.to_floored_seconds()); + + // let ohlc = self + // .get_from_1mn_kraken(timestamp, previous_timestamp) + // .unwrap_or_else(|_| { + // self.get_from_1mn_binance(timestamp, previous_timestamp) + // .unwrap_or_else(|_| { + // self.get_from_har_binance(timestamp, previous_timestamp, config) + // .unwrap_or_else(|_| { + // self.get_from_height_kibo(&height).unwrap_or_else(|_| { + // let date = timestamp.to_date(); + + // panic!( + // "Can't find the price for: height: {height} - date: {date} + // 1mn APIs are limited to the last 16 hours for Binance's and the last 10 hours for Kraken's + // How to fix this: + // 1. Go to https://www.binance.com/en/trade/BTC_USDT?type=spot + // 2. Select 1mn interval + // 3. Open the inspector/dev tools + // 4. Go to the Network Tab + // 5. Filter URLs by 'uiKlines' + // 6. Go back to the chart and scroll until you pass the date mentioned few lines ago + // 7. Go back to the dev tools + // 8. Export to a har file (if there is no explicit button, click on the cog button) + // 9. Move the file to 'parser/imports/binance.har' + // " + // ) + // }) + // }) + // }) + // }); + + // // self.ohlc.height.insert(height, ohlc); + + // Ok(ohlc) + } + + fn find_height_ohlc( + tree: &BTreeMap, + timestamp: Timestamp, + previous_timestamp: Option, + name: &str, + ) -> color_eyre::Result { + let previous_ohlc = previous_timestamp.map_or(Some(OHLC::default()), |previous_timestamp| { + tree.get(&previous_timestamp).cloned() + }); + + let last_ohlc = tree.get(×tamp); + + if previous_ohlc.is_none() || last_ohlc.is_none() { + return Err(Error::msg(format!("Couldn't find timestamp in {name}"))); + } + + let previous_ohlc = previous_ohlc.unwrap(); + + let mut final_ohlc = ( + Open::from(previous_ohlc.3), + High::from(previous_ohlc.3), + Low::from(previous_ohlc.3), + previous_ohlc.3, + ); + + let start = previous_timestamp.unwrap_or(Timestamp::from(0)); + let end = timestamp; + + // Otherwise it's a re-org + if start < end { + tree.range(start..=end).skip(1).for_each(|(_, ohlc)| { + if ohlc.1 > final_ohlc.1 { + final_ohlc.1 = ohlc.1 + } + + if ohlc.2 < final_ohlc.2 { + final_ohlc.2 = ohlc.2 + } + + final_ohlc.3 = ohlc.3; + }); + } + + Ok(final_ohlc) + } + + pub fn as_any_json_vec_slice(&self) -> [&dyn AnyJsonStorableVec; 16] { + [ + &self.dateindex_to_close_in_cents as &dyn AnyJsonStorableVec, + &self.dateindex_to_close_in_dollars, + &self.dateindex_to_high_in_cents, + &self.dateindex_to_high_in_dollars, + &self.dateindex_to_low_in_cents, + &self.dateindex_to_low_in_dollars, + &self.dateindex_to_open_in_cents, + &self.dateindex_to_open_in_dollars, + &self.height_to_close_in_cents, + &self.height_to_close_in_dollars, + &self.height_to_high_in_cents, + &self.height_to_high_in_dollars, + &self.height_to_low_in_cents, + &self.height_to_low_in_dollars, + &self.height_to_open_in_cents, + &self.height_to_open_in_dollars, + ] + } + + pub fn as_mut_any_vec_slice(&mut self) -> [&mut dyn AnyStorableVec; 16] { + [ + &mut self.dateindex_to_close_in_cents as &mut dyn AnyStorableVec, + &mut self.dateindex_to_close_in_dollars, + &mut self.dateindex_to_high_in_cents, + &mut self.dateindex_to_high_in_dollars, + &mut self.dateindex_to_low_in_cents, + &mut self.dateindex_to_low_in_dollars, + &mut self.dateindex_to_open_in_cents, + &mut self.dateindex_to_open_in_dollars, + &mut self.height_to_close_in_cents, + &mut self.height_to_close_in_dollars, + &mut self.height_to_high_in_cents, + &mut self.height_to_high_in_dollars, + &mut self.height_to_low_in_cents, + &mut self.height_to_low_in_dollars, + &mut self.height_to_open_in_cents, + &mut self.height_to_open_in_dollars, + ] + } +} diff --git a/crates/brk_fetcher/src/main.rs b/crates/brk_fetcher/src/main.rs new file mode 100644 index 000000000..fd589f72a --- /dev/null +++ b/crates/brk_fetcher/src/main.rs @@ -0,0 +1,18 @@ +use brk_fetcher::{Binance, Kibo, Kraken}; +use brk_indexer::Height; +use serde_json::Value; + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + brk_logger::init(None); + + dbg!(Binance::fetch_1d()?); + // dbg!(Binance::fetch_1mn_prices()); + // dbg!(Kraken::fetch_1d()?); + // dbg!(Kraken::fetch_1mn_prices()?); + // dbg!(Kibo::fetch_date_prices(2025)?); + // dbg!(Kibo::fetch_height_prices(Height::from(880_000_u32))?); + + Ok(()) +} diff --git a/crates/brk_fetcher/src/structs/cents.rs b/crates/brk_fetcher/src/structs/cents.rs new file mode 100644 index 000000000..6130b14f8 --- /dev/null +++ b/crates/brk_fetcher/src/structs/cents.rs @@ -0,0 +1,29 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Dollars; + +#[derive( + Debug, + Default, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Deref, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Cents(u64); + +impl From for Cents { + fn from(value: Dollars) -> Self { + Self((*value * 100.0).floor() as u64) + } +} diff --git a/crates/brk_fetcher/src/structs/close.rs b/crates/brk_fetcher/src/structs/close.rs new file mode 100644 index 000000000..a87318ea2 --- /dev/null +++ b/crates/brk_fetcher/src/structs/close.rs @@ -0,0 +1,27 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive( + Debug, + Default, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Deref, + Serialize, +)] +#[repr(C)] +pub struct Close(T); +impl From for Close { + fn from(value: T) -> Self { + Self(value) + } +} diff --git a/crates/brk_fetcher/src/structs/date.rs b/crates/brk_fetcher/src/structs/date.rs new file mode 100644 index 000000000..21e7f112d --- /dev/null +++ b/crates/brk_fetcher/src/structs/date.rs @@ -0,0 +1,61 @@ +use brk_indexer::Timestamp; +use jiff::{Span, civil::Date as Date_, tz::TimeZone}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Dateindex; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, Immutable, IntoBytes, KnownLayout)] +pub struct Date(u32); + +impl Date { + pub const INDEX_ZERO: Self = Self(20090103); + pub const INDEX_ZERO_: Date_ = Date_::constant(2009, 1, 3); + pub const INDEX_ONE: Self = Self(20090109); + pub const INDEX_ONE_: Date_ = Date_::constant(2009, 1, 9); + + pub fn year(&self) -> u16 { + (self.0 / 1_00_00) as u16 + } + + pub fn month(&self) -> u8 { + ((self.0 % 1_00_00) / 1_00) as u8 + } + + pub fn day(&self) -> u8 { + (self.0 % 1_00) as u8 + } +} + +impl Default for Date { + fn default() -> Self { + Self::INDEX_ZERO + } +} + +impl From for Date { + fn from(value: Date_) -> Self { + Self(value.year() as u32 * 1_00_00 + value.month() as u32 * 1_00 + value.day() as u32) + } +} + +impl From for Date_ { + fn from(value: Date) -> Self { + Self::new(value.year() as i16, value.month() as i8, value.day() as i8).unwrap() + } +} + +impl From for Date { + fn from(value: Timestamp) -> Self { + Self::from(Date_::from(jiff::Timestamp::from(value).to_zoned(TimeZone::UTC))) + } +} + +impl From for Date { + fn from(value: Dateindex) -> Self { + Self::from( + Self::INDEX_ZERO_ + .checked_add(Span::new().days(i64::from(value))) + .unwrap(), + ) + } +} diff --git a/crates/brk_fetcher/src/structs/dateindex.rs b/crates/brk_fetcher/src/structs/dateindex.rs new file mode 100644 index 000000000..fc314e42e --- /dev/null +++ b/crates/brk_fetcher/src/structs/dateindex.rs @@ -0,0 +1,52 @@ +use std::ops::Add; + +use color_eyre::eyre::eyre; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Date; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, Immutable, IntoBytes, KnownLayout)] +pub struct Dateindex(u16); + +impl From for usize { + fn from(value: Dateindex) -> Self { + value.0 as usize + } +} + +impl From for Dateindex { + fn from(value: usize) -> Self { + Self(value as u16) + } +} + +impl From for i64 { + fn from(value: Dateindex) -> Self { + value.0 as i64 + } +} + +impl Add for Dateindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs as u16) + } +} + +impl TryFrom for Dateindex { + type Error = color_eyre::Report; + fn try_from(value: Date) -> Result { + let value_ = jiff::civil::Date::from(value); + if value_ < Date::INDEX_ZERO_ { + Err(eyre!("Date is too early")) + } else if value == Date::INDEX_ZERO { + Ok(Self(0)) + } else if value_ < Date::INDEX_ONE_ { + Err(eyre!("Date is between first and second")) + } else if value == Date::INDEX_ONE { + Ok(Self(1)) + } else { + Ok(Self(Date::INDEX_ONE_.until(value_)?.get_days() as u16 + 1)) + } + } +} diff --git a/crates/brk_fetcher/src/structs/dollars.rs b/crates/brk_fetcher/src/structs/dollars.rs new file mode 100644 index 000000000..4ea1892b3 --- /dev/null +++ b/crates/brk_fetcher/src/structs/dollars.rs @@ -0,0 +1,20 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Cents; + +#[derive(Debug, Default, Clone, Copy, Deref, FromBytes, Immutable, IntoBytes, KnownLayout, Serialize)] +pub struct Dollars(f64); + +impl From for Dollars { + fn from(value: f64) -> Self { + Self(value) + } +} + +impl From for Dollars { + fn from(value: Cents) -> Self { + Self((*value as f64) / 100.0) + } +} diff --git a/crates/brk_fetcher/src/structs/high.rs b/crates/brk_fetcher/src/structs/high.rs new file mode 100644 index 000000000..53fa506cd --- /dev/null +++ b/crates/brk_fetcher/src/structs/high.rs @@ -0,0 +1,38 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Close; + +#[derive( + Debug, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Deref, + Serialize, +)] +#[repr(C)] +pub struct High(T); +impl From for High { + fn from(value: T) -> Self { + Self(value) + } +} + +impl From> for High +where + T: Copy, +{ + fn from(value: Close) -> Self { + Self(*value) + } +} diff --git a/crates/brk_fetcher/src/structs/low.rs b/crates/brk_fetcher/src/structs/low.rs new file mode 100644 index 000000000..39f8c52ea --- /dev/null +++ b/crates/brk_fetcher/src/structs/low.rs @@ -0,0 +1,38 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Close; + +#[derive( + Debug, + Default, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Deref, + Serialize, +)] +#[repr(C)] +pub struct Low(T); +impl From for Low { + fn from(value: T) -> Self { + Self(value) + } +} + +impl From> for Low +where + T: Copy, +{ + fn from(value: Close) -> Self { + Self(*value) + } +} diff --git a/crates/brk_fetcher/src/structs/mod.rs b/crates/brk_fetcher/src/structs/mod.rs new file mode 100644 index 000000000..5d1dfcf2b --- /dev/null +++ b/crates/brk_fetcher/src/structs/mod.rs @@ -0,0 +1,19 @@ +mod cents; +mod close; +mod date; +mod dateindex; +mod dollars; +mod high; +mod low; +mod open; + +pub use cents::*; +pub use close::*; +pub use date::*; +pub use dateindex::*; +pub use dollars::*; +pub use high::*; +pub use low::*; +pub use open::*; + +pub type OHLC = (Open, High, Low, Close); diff --git a/crates/brk_fetcher/src/structs/open.rs b/crates/brk_fetcher/src/structs/open.rs new file mode 100644 index 000000000..fe2843268 --- /dev/null +++ b/crates/brk_fetcher/src/structs/open.rs @@ -0,0 +1,38 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Close; + +#[derive( + Debug, + Default, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Deref, + Serialize, +)] +#[repr(C)] +pub struct Open(T); +impl From for Open { + fn from(value: T) -> Self { + Self(value) + } +} + +impl From> for Open +where + T: Copy, +{ + fn from(value: Close) -> Self { + Self(*value) + } +} diff --git a/crates/brk_indexer/Cargo.toml b/crates/brk_indexer/Cargo.toml new file mode 100644 index 000000000..39fb037a5 --- /dev/null +++ b/crates/brk_indexer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "brk_indexer" +description = "A Bitcoin Core indexer built on top of brk_parser" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +bitcoin = { workspace = true } +brk_parser = { workspace = true } +brk_logger = { workspace = true } +byteview = { workspace = true } +color-eyre = { workspace = true } +derive_deref = { workspace = true } +fjall = { workspace = true } +hodor = { workspace = true } +jiff = { workspace = true } +log = { workspace = true } +rapidhash = "1.4.0" +rayon = { workspace = true } +rlimit = { version = "0.10.2" } +serde = { workspace = true } +serde_bytes = { workspace = true } +storable_vec = { workspace = true } +zerocopy = { workspace = true } diff --git a/crates/brk_indexer/README.md b/crates/brk_indexer/README.md new file mode 100644 index 000000000..74de013f0 --- /dev/null +++ b/crates/brk_indexer/README.md @@ -0,0 +1,29 @@ +# Indexer + +A [Bitcoin Core](https://bitcoincore.org/en/about/) node indexer which iterates over the chain (via `../iterator`) and creates a database of the vecs (`../storable_vec`) and key/value stores ([`fjall`](https://crates.io/crates/fjall)) that can be used in your Rust code. + +The crate only stores the bare minimum to be self sufficient and not have to use an RPC client (except for scripts which are not stored). If you need more data, checkout `../computer` which uses the outputs from the indexer to compute a whole range of datasets. + +Vecs are used sparingly instead of stores for multiple reasons: + +- Only stores the relevant data since the key is an index +- Saved as uncompressed bytes and thus can be parsed manually (with any programming language) without relying on a server or library +- Easy to work with and predictable + +## Usage + +Storage wise, the expected overhead should be around 30% of the chain itself. + +Peaks at 11-13 GB of RAM + +## Outputs + +Vecs: `src/storage/storable_vecs/mod.rs` + +Stores: `src/storage/fjalls/mod.rs` + +## Examples + +Rust: `src/main.rs` + +Python: `../python/parse.py` diff --git a/crates/brk_indexer/src/lib.rs b/crates/brk_indexer/src/lib.rs new file mode 100644 index 000000000..ac7fab13a --- /dev/null +++ b/crates/brk_indexer/src/lib.rs @@ -0,0 +1,635 @@ +use std::{ + collections::BTreeMap, + path::Path, + str::FromStr, + thread::{self, sleep}, + time::Duration, +}; + +pub use brk_parser::*; + +use bitcoin::{Transaction, TxIn, TxOut}; +use color_eyre::eyre::{ContextCompat, eyre}; +use hodor::Exit; +use log::info; +use rayon::prelude::*; +use storable_vec::CACHED_GETS; + +mod storage; +mod structs; + +pub use storage::{AnyStorableVec, StorableVec, Store, StoreMeta}; +use storage::{Fjalls, StorableVecs}; +pub use structs::*; + +const SNAPSHOT_BLOCK_RANGE: usize = 1000; + +pub struct Indexer { + pub vecs: StorableVecs, + pub stores: Fjalls, +} + +impl Indexer { + pub fn import(indexes_dir: &Path) -> color_eyre::Result { + // info!("Increasing limit of opened files to 210_000..."); + rlimit::setrlimit( + rlimit::Resource::NOFILE, + 210_000, + rlimit::getrlimit(rlimit::Resource::NOFILE).unwrap().1, + )?; + + info!("Importing indexes..."); + let vecs = StorableVecs::import(&indexes_dir.join("vecs"))?; + let stores = Fjalls::import(&indexes_dir.join("fjall"))?; + + Ok(Self { vecs, stores }) + } +} + +impl Indexer { + pub fn index(&mut self, parser: &Parser, rpc: &'static rpc::Client, exit: &Exit) -> color_eyre::Result<()> { + info!("Started indexing..."); + + let check_collisions = true; + + let starting_indexes = Indexes::try_from((&mut self.vecs, &self.stores, rpc)).unwrap_or_else(|_| { + let indexes = Indexes::default(); + indexes.push_if_needed(&mut self.vecs).unwrap(); + indexes + }); + + exit.block(); + self.stores.rollback(&self.vecs, &starting_indexes)?; + self.vecs.rollback(&starting_indexes)?; + exit.unblock(); + + let export = + |stores: &mut Fjalls, vecs: &mut StorableVecs, height: Height| -> color_eyre::Result<()> { + info!("Exporting..."); + exit.block(); + stores.commit(height)?; + info!("Exported stores"); + vecs.flush(height)?; + info!("Exported vecs"); + exit.unblock(); + Ok(()) + }; + + let vecs = &mut self.vecs; + let stores = &mut self.stores; + + let mut idxs = starting_indexes; + + parser.parse(Some(idxs.height), None) + .iter() + .try_for_each(|(height, block, blockhash)| -> color_eyre::Result<()> { + info!("Indexing block {height}..."); + + idxs.height = height; + + let blockhash = BlockHash::from(blockhash); + let blockhash_prefix = BlockHashPrefix::from(&blockhash); + + if stores + .blockhash_prefix_to_height + .get(&blockhash_prefix)? + .is_some_and(|prev_height| *prev_height != height) + { + dbg!(blockhash); + return Err(eyre!("Collision, expect prefix to need be set yet")); + } + + stores + .blockhash_prefix_to_height + .insert_if_needed(blockhash_prefix, height, height); + + vecs.height_to_blockhash.push_if_needed(height, blockhash)?; + vecs.height_to_difficulty.push_if_needed(height, block.header.difficulty_float())?; + vecs.height_to_timestamp.push_if_needed(height, Timestamp::from(block.header.time))?; + vecs.height_to_size.push_if_needed(height, block.total_size())?; + vecs.height_to_weight.push_if_needed(height, block.weight().into())?; + + let inputs = block + .txdata + .iter() + .enumerate() + .flat_map(|(index, tx)| { + tx.input + .iter() + .enumerate() + .map(move |(vin, txin)| (Txindex::from(index), Vin::from(vin), txin, tx)) + }) + .collect::>(); + + let outputs = block + .txdata + .iter() + .enumerate() + .flat_map(|(index, tx)| { + tx.output + .iter() + .enumerate() + .map(move |(vout, txout)| (Txindex::from(index), Vout::from(vout), txout, tx)) + }) + .collect::>(); + + let tx_len = block.txdata.len(); + let outputs_len = outputs.len(); + let inputs_len = inputs.len(); + + let ( + txid_prefix_to_txid_and_block_txindex_and_prev_txindex_join_handle, + input_source_vec_handle, + txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt_handle, + ) = thread::scope(|scope| { + let txid_prefix_to_txid_and_block_txindex_and_prev_txindex_handle = + scope.spawn(|| -> color_eyre::Result<_> { + block + .txdata + .par_iter() + .enumerate() + .map(|(index, tx)| -> color_eyre::Result<_> { + let txid = Txid::from(tx.compute_txid()); + + let txid_prefix = TxidPrefix::from(&txid); + + let prev_txindex_opt = + if check_collisions && stores.txid_prefix_to_txindex.needs(height) { + // Should only find collisions for two txids (duplicates), see below + stores.txid_prefix_to_txindex.get(&txid_prefix)?.map(|v| *v) + } else { + None + }; + + Ok((txid_prefix, (tx, txid, Txindex::from(index), prev_txindex_opt))) + }) + .try_fold(BTreeMap::new, |mut map, tuple| { + let (key, value) = tuple?; + map.insert(key, value); + Ok(map) + }) + .try_reduce(BTreeMap::new, |mut map, mut map2| { + if map.len() > map2.len() { + map.append(&mut map2); + Ok(map) + } else { + map2.append(&mut map); + Ok(map2) + } + }) + }); + + let input_source_vec_handle = scope.spawn(|| { + inputs + .into_par_iter() + .enumerate() + .map(|(block_txinindex, (block_txindex, vin, txin, tx))| -> color_eyre::Result<(Txinindex, InputSource)> { + let txindex = idxs.txindex + block_txindex; + let txinindex = idxs.txinindex + Txinindex::from(block_txinindex); + + // dbg!((txindex, txinindex, vin)); + + let outpoint = txin.previous_output; + let txid = Txid::from(outpoint.txid); + + if tx.is_coinbase() { + return Ok((txinindex, InputSource::SameBlock((tx, txindex, txin, vin)))); + } + + let prev_txindex = if let Some(txindex) = stores + .txid_prefix_to_txindex + .get(&TxidPrefix::from(&txid))? + .map(|v| *v) + .and_then(|txindex| { + // Checking if not finding txindex from the future + (txindex < idxs.txindex).then_some(txindex) + }) { + txindex + } else { + // dbg!(indexes.txindex + block_txindex, txindex, txin, vin); + return Ok((txinindex, InputSource::SameBlock((tx, txindex, txin, vin)))); + }; + + let vout = Vout::from(outpoint.vout); + + let txoutindex = *vecs + .txindex_to_first_txoutindex + .get(prev_txindex)? + .context("Expect txoutindex to not be none") + .inspect_err(|_| { + dbg!(outpoint.txid, prev_txindex, vout); + })? + + vout; + + Ok((txinindex, InputSource::PreviousBlock(( + vin, + txindex, + txoutindex, + )))) + }) + .try_fold(BTreeMap::new, |mut map, tuple| -> color_eyre::Result<_> { + let (key, value) = tuple?; + map.insert(key, value); + Ok(map) + }) + .try_reduce(BTreeMap::new, |mut map, mut map2| { + if map.len() > map2.len() { + map.append(&mut map2); + Ok(map) + } else { + map2.append(&mut map); + Ok(map2) + } + }) + }); + + let txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt_handle = scope.spawn(|| { + outputs + .into_par_iter() + .enumerate() + .map( + #[allow(clippy::type_complexity)] + |(block_txoutindex, (block_txindex, vout, txout, tx))| -> color_eyre::Result<( + Txoutindex, + ( + &TxOut, + Txindex, + Vout, + Addresstype, + color_eyre::Result, + Option, + &Transaction, + ), + )> { + let txindex = idxs.txindex + block_txindex; + let txoutindex = idxs.txoutindex + Txoutindex::from(block_txoutindex); + + let script = &txout.script_pubkey; + + let addresstype = Addresstype::from(script); + + let addressbytes_res = + Addressbytes::try_from((script, addresstype)).inspect_err(|_| { + // dbg!(&txout, height, txi, &tx.compute_txid()); + }); + + let addressindex_opt = addressbytes_res.as_ref().ok().and_then(|addressbytes| { + stores + .addresshash_to_addressindex + .get(&AddressHash::from((addressbytes, addresstype))) + .unwrap() + .map(|v| *v) + // Checking if not in the future + .and_then(|addressindex_local| { + (addressindex_local < idxs.addressindex).then_some(addressindex_local) + }) + }); + + if let Some(Some(addressindex)) = check_collisions.then_some(addressindex_opt) { + let addressbytes = addressbytes_res.as_ref().unwrap(); + + let prev_addresstype = *vecs + .addressindex_to_addresstype + .get(addressindex)? + .context("Expect to have address type")?; + + let addresstypeindex = *vecs + .addressindex_to_addresstypeindex + .get(addressindex)? + .context("Expect to have address type index")?; + + let prev_addressbytes_opt = + vecs.get_addressbytes(prev_addresstype, addresstypeindex)?; + + let prev_addressbytes = + prev_addressbytes_opt.as_ref().context("Expect to have addressbytes")?; + + if (vecs.addressindex_to_addresstype.hasnt(addressindex)? + && addresstype != prev_addresstype) + || (stores.addresshash_to_addressindex.needs(height) + && prev_addressbytes != addressbytes) + { + let txid = tx.compute_txid(); + dbg!( + height, + txid, + vout, + block_txindex, + addresstype, + prev_addresstype, + prev_addressbytes, + addressbytes, + idxs.addressindex, + addressindex, + addresstypeindex, + txout, + AddressHash::from((addressbytes, addresstype)), + AddressHash::from((prev_addressbytes, prev_addresstype)) + ); + panic!() + } + } + + Ok(( + txoutindex, + ( + txout, + txindex, + vout, + addresstype, + addressbytes_res, + addressindex_opt, + tx, + ), + )) + }, + ) + .try_fold(BTreeMap::new, |mut map, tuple| -> color_eyre::Result<_> { + let (key, value) = tuple?; + map.insert(key, value); + Ok(map) + }) + .try_reduce(BTreeMap::new, |mut map, mut map2| { + if map.len() > map2.len() { + map.append(&mut map2); + Ok(map) + } else { + map2.append(&mut map); + Ok(map2) + } + }) + }); + + ( + txid_prefix_to_txid_and_block_txindex_and_prev_txindex_handle.join(), + input_source_vec_handle.join(), + txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt_handle.join(), + ) + }); + + let txid_prefix_to_txid_and_block_txindex_and_prev_txindex = + txid_prefix_to_txid_and_block_txindex_and_prev_txindex_join_handle + .ok() + .context( + "Expect txid_prefix_to_txid_and_block_txindex_and_prev_txindex_join_handle to join", + )??; + + let input_source_vec = input_source_vec_handle + .ok() + .context("Export input_source_vec_handle to join")??; + + let txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt = + txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt_handle + .ok() + .context( + "Expect txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt_handle to join", + )??; + + let mut new_txindexvout_to_txoutindex: BTreeMap< + (Txindex, Vout), + Txoutindex, + > = BTreeMap::new(); + + let mut already_added_addresshash: BTreeMap = BTreeMap::new(); + + txoutindex_to_txout_addresstype_addressbytes_res_addressindex_opt + .into_iter() + .try_for_each( + |( + txoutindex, + (txout, txindex, vout, addresstype, addressbytes_res, addressindex_opt, _tx), + )| + -> color_eyre::Result<()> { + let sats = Sats::from(txout.value); + + if vout.is_zero() { + vecs.txindex_to_first_txoutindex.push_if_needed(txindex, txoutindex)?; + } + + vecs.txoutindex_to_value.push_if_needed(txoutindex, sats)?; + + let mut addressindex = idxs.addressindex; + + let mut addresshash = None; + + if let Some(addressindex_local) = addressindex_opt.or_else(|| { + addressbytes_res.as_ref().ok().and_then(|addressbytes| { + // Check if address was first seen before in this iterator + // Example: https://mempool.space/address/046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0c + addresshash.replace(AddressHash::from((addressbytes, addresstype))); + already_added_addresshash + .get(addresshash.as_ref().unwrap()) + .cloned() + }) + }) { + addressindex = addressindex_local; + } else { + idxs.addressindex.increment(); + + let addresstypeindex = match addresstype { + Addresstype::Empty => idxs.emptyindex.copy_then_increment(), + Addresstype::Multisig => idxs.multisigindex.copy_then_increment(), + Addresstype::OpReturn => idxs.opreturnindex.copy_then_increment(), + Addresstype::PushOnly => idxs.pushonlyindex.copy_then_increment(), + Addresstype::Unknown => idxs.unknownindex.copy_then_increment(), + Addresstype::P2PK65 => idxs.p2pk65index.copy_then_increment(), + Addresstype::P2PK33 => idxs.p2pk33index.copy_then_increment(), + Addresstype::P2PKH => idxs.p2pkhindex.copy_then_increment(), + Addresstype::P2SH => idxs.p2shindex.copy_then_increment(), + Addresstype::P2WPKH => idxs.p2wpkhindex.copy_then_increment(), + Addresstype::P2WSH => idxs.p2wshindex.copy_then_increment(), + Addresstype::P2TR => idxs.p2trindex.copy_then_increment(), + }; + + vecs.addressindex_to_addresstype + .push_if_needed(addressindex, addresstype)?; + + vecs.addressindex_to_addresstypeindex + .push_if_needed(addressindex, addresstypeindex)?; + + vecs.addressindex_to_height + .push_if_needed(addressindex, height)?; + + if let Ok(addressbytes) = addressbytes_res { + let addresshash = addresshash.unwrap(); + + already_added_addresshash + .insert(addresshash, addressindex); + + stores.addresshash_to_addressindex.insert_if_needed( + addresshash, + addressindex, + height, + ); + + vecs.push_addressbytes_if_needed(addresstypeindex, addressbytes)?; + } + } + + new_txindexvout_to_txoutindex + .insert((txindex, vout), txoutindex); + + vecs.txoutindex_to_addressindex + .push_if_needed(txoutindex, addressindex)?; + + Ok(()) + }, + )?; + + drop(already_added_addresshash); + + input_source_vec + .into_iter() + .map( + #[allow(clippy::type_complexity)] + |(txinindex, input_source)| -> color_eyre::Result<( + Txinindex, Vin, Txindex, Txoutindex + )> { + match input_source { + InputSource::PreviousBlock((vin, txindex, txoutindex)) => Ok((txinindex, vin, txindex, txoutindex)), + InputSource::SameBlock((tx, txindex, txin, vin)) => { + if tx.is_coinbase() { + return Ok((txinindex, vin, txindex, Txoutindex::COINBASE)); + } + + let outpoint = txin.previous_output; + let txid = Txid::from(outpoint.txid); + let vout = Vout::from(outpoint.vout); + + let block_txindex = txid_prefix_to_txid_and_block_txindex_and_prev_txindex + .get(&TxidPrefix::from(&txid)) + .context("txid should be in same block").inspect_err(|_| { + dbg!(&txid_prefix_to_txid_and_block_txindex_and_prev_txindex); + // panic!(); + })? + .2; + let prev_txindex = idxs.txindex + block_txindex; + + let prev_txoutindex = new_txindexvout_to_txoutindex + .remove(&(prev_txindex, vout)) + .context("should have found addressindex from same block") + .inspect_err(|_| { + dbg!(&new_txindexvout_to_txoutindex, txin, prev_txindex, vout, txid); + })?; + + Ok((txinindex, vin, txindex, prev_txoutindex)) + } + } + }, + ) + .try_for_each(|res| -> color_eyre::Result<()> { + let (txinindex, vin, txindex, txoutindex) = res?; + + if vin.is_zero() { + vecs.txindex_to_first_txinindex.push_if_needed(txindex, txinindex)?; + } + + vecs.txinindex_to_txoutindex.push_if_needed(txinindex, txoutindex)?; + + Ok(()) + })?; + + drop(new_txindexvout_to_txoutindex); + + let mut txindex_to_tx_and_txid: BTreeMap = BTreeMap::default(); + + txid_prefix_to_txid_and_block_txindex_and_prev_txindex + .into_iter() + .try_for_each( + |(txid_prefix, (tx, txid, index, prev_txindex_opt))| -> color_eyre::Result<()> { + let txindex = idxs.txindex + index; + + txindex_to_tx_and_txid.insert(txindex, (tx, txid)); + + match prev_txindex_opt { + None => { + stores + .txid_prefix_to_txindex + .insert_if_needed(txid_prefix, txindex, height); + } + Some(prev_txindex) => { + // In case if we start at an already parsed height + if txindex == prev_txindex { + return Ok(()); + } + + let len = vecs.txindex_to_txid.len(); + // Ok if `get` is not par as should happen only twice + let prev_txid = vecs + .txindex_to_txid + .get(prev_txindex)? + .context("To have txid for txindex") + .inspect_err(|_| { + dbg!(txindex, len); + })?; + + // #[allow(clippy::redundant_locals)] + // let prev_txid = prev_txid; + let prev_txid = prev_txid.as_ref(); + + // If another Txid needs to be added to the list + // We need to check that it's also a coinbase tx otherwise par_iter inputs needs to be updated + let only_known_dup_txids = [ + bitcoin::Txid::from_str( + "d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599", + )?.into(), + bitcoin::Txid::from_str( + "e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468", + )?.into(), + ]; + + let is_dup = only_known_dup_txids.contains(prev_txid); + + if !is_dup { + let prev_height = + vecs.txindex_to_height.get(prev_txindex)?.expect("To have height"); + dbg!(height, txindex, prev_height, prev_txid, prev_txindex); + return Err(eyre!("Expect none")); + } + } + } + + Ok(()) + }, + )?; + + txindex_to_tx_and_txid + .into_iter() + .try_for_each(|(txindex, (tx, txid))| -> color_eyre::Result<()> { + vecs.txindex_to_txversion.push_if_needed(txindex, tx.version.into())?; + vecs.txindex_to_txid.push_if_needed(txindex, txid)?; + vecs.txindex_to_height.push_if_needed(txindex, height)?; + vecs.txindex_to_locktime.push_if_needed(txindex, tx.lock_time.into())?; + vecs.txindex_to_base_size.push_if_needed(txindex, tx.base_size())?; + vecs.txindex_to_total_size.push_if_needed(txindex, tx.total_size())?; + vecs.txindex_to_is_explicitly_rbf.push_if_needed(txindex, tx.is_explicitly_rbf())?; + Ok(()) + })?; + + idxs.txindex += Txindex::from(tx_len); + idxs.txinindex += Txinindex::from(inputs_len); + idxs.txoutindex += Txoutindex::from(outputs_len); + + idxs.push_future_if_needed(vecs)?; + + let should_snapshot = height != 0 && height % SNAPSHOT_BLOCK_RANGE == 0 && !exit.blocked(); + if should_snapshot { + export(stores, vecs, height)?; + } + + Ok(()) + })?; + + export(stores, vecs, idxs.height)?; + + sleep(Duration::from_millis(100)); + + Ok(()) + } +} + +#[derive(Debug)] +enum InputSource<'a> { + PreviousBlock((Vin, Txindex, Txoutindex)), + SameBlock((&'a Transaction, Txindex, &'a TxIn, Vin)), +} diff --git a/crates/brk_indexer/src/main.rs b/crates/brk_indexer/src/main.rs new file mode 100644 index 000000000..eb3d9d5a5 --- /dev/null +++ b/crates/brk_indexer/src/main.rs @@ -0,0 +1,45 @@ +use std::{path::Path, thread::sleep, time::Duration}; + +use brk_indexer::{Indexer, rpc::RpcApi}; +use brk_parser::{ + Parser, + rpc::{self}, +}; +use hodor::Exit; +use log::info; +use storable_vec::CACHED_GETS; + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + brk_logger::init(None); + + let data_dir = Path::new("../../../bitcoin"); + let rpc = Box::leak(Box::new(rpc::Client::new( + "http://localhost:8332", + rpc::Auth::CookieFile(Path::new(data_dir).join(".cookie")), + )?)); + let exit = Exit::new(); + + let parser = Parser::new(data_dir, rpc); + + loop { + let block_count = rpc.get_blockchain_info().unwrap().blocks as usize; + + info!("{block_count} blocks found."); + + let i = std::time::Instant::now(); + + let mut indexer: Indexer = Indexer::import(Path::new("../../_outputs/indexes"))?; + + indexer.index(&parser, rpc, &exit)?; + + dbg!(i.elapsed()); + + info!("Waiting for a new block..."); + + while block_count == rpc.get_blockchain_info().unwrap().blocks as usize { + sleep(Duration::from_secs(1)) + } + } +} diff --git a/crates/brk_indexer/src/storage/fjalls/base.rs b/crates/brk_indexer/src/storage/fjalls/base.rs new file mode 100644 index 000000000..6f9e1cafd --- /dev/null +++ b/crates/brk_indexer/src/storage/fjalls/base.rs @@ -0,0 +1,149 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + error, mem, + path::Path, +}; + +use brk_parser::Height; +use byteview::ByteView; +use fjall::{ + PartitionCreateOptions, PersistMode, ReadTransaction, Result, TransactionalKeyspace, TransactionalPartitionHandle, +}; +use storable_vec::{Value, Version}; +use zerocopy::{Immutable, IntoBytes}; + +use super::StoreMeta; + +pub struct Store { + meta: StoreMeta, + keyspace: TransactionalKeyspace, + part: TransactionalPartitionHandle, + rtx: ReadTransaction, + puts: BTreeMap, + dels: BTreeSet, +} + +const CHECK_COLLISISONS: bool = true; + +impl Store +where + K: Into + Ord + Immutable + IntoBytes, + V: Into + TryFrom, + >::Error: error::Error + Send + Sync + 'static, +{ + pub fn import(path: &Path, version: Version) -> color_eyre::Result { + let meta = StoreMeta::checked_open(path, version)?; + let keyspace = if let Ok(keyspace) = Self::open_keyspace(path) { + keyspace + } else { + meta.reset()?; + return Self::import(path, version); + }; + let part = if let Ok(part) = Self::open_partition_handle(&keyspace) { + part + } else { + drop(keyspace); + meta.reset()?; + return Self::import(path, version); + }; + let rtx = keyspace.read_tx(); + + Ok(Self { + meta, + keyspace, + part, + rtx, + puts: BTreeMap::new(), + dels: BTreeSet::new(), + }) + } + + pub fn get(&self, key: &K) -> color_eyre::Result>> { + if let Some(v) = self.puts.get(key) { + Ok(Some(Value::Ref(v))) + } else if let Some(slice) = self.rtx.get(&self.part, key.as_bytes())? { + Ok(Some(Value::Owned(V::try_from(slice.into())?))) + } else { + Ok(None) + } + } + + pub fn insert_if_needed(&mut self, key: K, value: V, height: Height) { + if self.needs(height) { + if !self.dels.is_empty() { + unreachable!("Shouldn't reach this"); + // self.dels.remove(&key); + } + self.puts.insert(key, value); + } + } + + pub fn remove(&mut self, key: K) { + if !self.puts.is_empty() { + unreachable!("Shouldn't reach this"); + // self.puts.remove(&key); + } + self.dels.insert(key); + } + + pub fn commit(&mut self, height: Height) -> Result<()> { + if self.has(height) && self.puts.is_empty() && self.dels.is_empty() { + return Ok(()); + } + + self.meta.export(self.len(), height)?; + + let mut wtx = self.keyspace.write_tx(); + + mem::take(&mut self.dels) + .into_iter() + .for_each(|key| wtx.remove(&self.part, key.into())); + + mem::take(&mut self.puts).into_iter().for_each(|(key, value)| { + if CHECK_COLLISISONS { + if let Ok(Some(value)) = wtx.get(&self.part, key.as_bytes()) { + dbg!(value, &self.meta); + unreachable!(); + } + } + wtx.insert(&self.part, key.into(), value.into()) + }); + + wtx.commit()?; + + self.keyspace.persist(PersistMode::SyncAll)?; + + self.rtx = self.keyspace.read_tx(); + + Ok(()) + } + + pub fn height(&self) -> Option { + self.meta.height() + } + + pub fn len(&self) -> usize { + self.meta.len() + self.puts.len() - self.dels.len() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn has(&self, height: Height) -> bool { + self.meta.has(height) + } + pub fn needs(&self, height: Height) -> bool { + self.meta.needs(height) + } + + fn open_keyspace(path: &Path) -> Result { + fjall::Config::new(path.join("fjall")).open_transactional() + } + + fn open_partition_handle(keyspace: &TransactionalKeyspace) -> Result { + keyspace.open_partition( + "partition", + PartitionCreateOptions::default().manual_journal_persist(true), + ) + } +} diff --git a/crates/brk_indexer/src/storage/fjalls/meta.rs b/crates/brk_indexer/src/storage/fjalls/meta.rs new file mode 100644 index 000000000..5c0bfdf56 --- /dev/null +++ b/crates/brk_indexer/src/storage/fjalls/meta.rs @@ -0,0 +1,104 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +use storable_vec::Version; +use zerocopy::{FromBytes, IntoBytes}; + +use super::Height; + +#[derive(Debug)] +pub struct StoreMeta { + pathbuf: PathBuf, + version: Version, + height: Option, + len: usize, +} + +impl StoreMeta { + pub fn checked_open(path: &Path, version: Version) -> color_eyre::Result { + fs::create_dir_all(path)?; + + let is_same_version = + Version::try_from(Self::path_version_(path).as_path()).is_ok_and(|prev_version| version == prev_version); + + if !is_same_version { + Self::reset_(path)?; + } + + let this = Self { + pathbuf: path.to_owned(), + version, + height: Height::try_from(Self::path_height_(path).as_path()).ok(), + len: Self::read_length_(path)?, + }; + + this.version.write(&this.path_version())?; + + Ok(this) + } + + pub fn len(&self) -> usize { + self.len + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn export(&mut self, len: usize, height: Height) -> io::Result<()> { + self.len = len; + self.write_length()?; + self.height = Some(height); + height.write(&self.path_height()) + } + + pub fn reset(&self) -> io::Result<()> { + Self::reset_(self.pathbuf.as_path()) + } + fn reset_(path: &Path) -> io::Result<()> { + fs::remove_dir_all(path)?; + fs::create_dir(path) + } + + fn path_version(&self) -> PathBuf { + Self::path_version_(&self.pathbuf) + } + fn path_version_(path: &Path) -> PathBuf { + path.join("version") + } + + pub fn height(&self) -> Option { + self.height + } + pub fn needs(&self, height: Height) -> bool { + self.height.is_none_or(|self_height| height > self_height) + } + pub fn has(&self, height: Height) -> bool { + !self.needs(height) + } + fn path_height(&self) -> PathBuf { + Self::path_height_(&self.pathbuf) + } + fn path_height_(path: &Path) -> PathBuf { + path.join("height") + } + + // fn read_length(&self) -> color_eyre::Result { + // Self::read_length_(&self.pathbuf) + // } + fn read_length_(path: &Path) -> color_eyre::Result { + Ok(fs::read(Self::path_length(path)) + .map(|v| usize::read_from_bytes(v.as_slice()).unwrap_or_default()) + .unwrap_or_default()) + } + fn write_length(&self) -> io::Result<()> { + Self::write_length_(&self.pathbuf, self.len) + } + fn write_length_(path: &Path, len: usize) -> Result<(), io::Error> { + fs::write(Self::path_length(path), len.as_bytes()) + } + fn path_length(path: &Path) -> PathBuf { + path.join("length") + } +} diff --git a/crates/brk_indexer/src/storage/fjalls/mod.rs b/crates/brk_indexer/src/storage/fjalls/mod.rs new file mode 100644 index 000000000..7b2a0c029 --- /dev/null +++ b/crates/brk_indexer/src/storage/fjalls/mod.rs @@ -0,0 +1,182 @@ +use std::{path::Path, thread}; + +use storable_vec::{Value, Version, CACHED_GETS}; + +use crate::{ + AddressHash, Addressbytes, Addressindex, Addresstype, BlockHashPrefix, Height, Indexes, TxidPrefix, Txindex, +}; + +mod base; +mod meta; + +pub use base::*; +pub use meta::*; + +use super::StorableVecs; + +pub struct Fjalls { + pub addresshash_to_addressindex: Store, + pub blockhash_prefix_to_height: Store, + pub txid_prefix_to_txindex: Store, +} + +impl Fjalls { + pub fn import(path: &Path) -> color_eyre::Result { + let addresshash_to_addressindex = Store::import(&path.join("addresshash_to_addressindex"), Version::from(1))?; + let blockhash_prefix_to_height = Store::import(&path.join("blockhash_prefix_to_height"), Version::from(1))?; + let txid_prefix_to_txindex = Store::import(&path.join("txid_prefix_to_txindex"), Version::from(1))?; + + Ok(Self { + addresshash_to_addressindex, + blockhash_prefix_to_height, + txid_prefix_to_txindex, + }) + } + + pub fn rollback(&mut self, vecs: &StorableVecs, starting_indexes: &Indexes) -> color_eyre::Result<()> { + vecs.height_to_blockhash + .iter_from(starting_indexes.height, |(_, blockhash)| { + let blockhash = blockhash.as_ref(); + let blockhash_prefix = BlockHashPrefix::from(blockhash); + self.blockhash_prefix_to_height.remove(blockhash_prefix); + Ok(()) + })?; + + vecs.txindex_to_txid.iter_from(starting_indexes.txindex, |(_, txid)| { + let txid = txid.as_ref(); + let txid_prefix = TxidPrefix::from(txid); + self.txid_prefix_to_txindex.remove(txid_prefix); + Ok(()) + })?; + + vecs.height_to_first_p2pk65index + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2pk65index_to_p2pk65addressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2PK65)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + vecs.height_to_first_p2pk33index + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2pk33index_to_p2pk33addressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2PK33)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + vecs.height_to_first_p2pkhindex + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2pkhindex_to_p2pkhaddressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2PKH)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + vecs.height_to_first_p2shindex + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2shindex_to_p2shaddressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2SH)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + vecs.height_to_first_p2trindex + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2trindex_to_p2traddressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2TR)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + vecs.height_to_first_p2wpkhindex + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2wpkhindex_to_p2wpkhaddressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2WPKH)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + vecs.height_to_first_p2wshindex + .iter_from(starting_indexes.height, |(_, index)| { + if let Some(typedbytes) = vecs + .p2wshindex_to_p2wshaddressbytes + .get(index.into_inner())? + .map(Value::into_inner) + { + let bytes = Addressbytes::from(typedbytes); + let hash = AddressHash::from((&bytes, Addresstype::P2WSH)); + self.addresshash_to_addressindex.remove(hash); + } + Ok(()) + })?; + + self.commit(starting_indexes.height.decremented())?; + + Ok(()) + } + + pub fn starting_height(&self) -> Height { + [ + self.addresshash_to_addressindex.height(), + self.blockhash_prefix_to_height.height(), + self.txid_prefix_to_txindex.height(), + ] + .into_iter() + .map(|height| height.map(Height::incremented).unwrap_or_default()) + .min() + .unwrap() + } + + pub fn commit(&mut self, height: Height) -> fjall::Result<()> { + thread::scope(|scope| { + let addresshash_to_addressindex_commit_handle = + scope.spawn(|| self.addresshash_to_addressindex.commit(height)); + let blockhash_prefix_to_height_commit_handle = + scope.spawn(|| self.blockhash_prefix_to_height.commit(height)); + let txid_prefix_to_txindex_commit_handle = scope.spawn(|| self.txid_prefix_to_txindex.commit(height)); + + addresshash_to_addressindex_commit_handle.join().unwrap()?; + blockhash_prefix_to_height_commit_handle.join().unwrap()?; + txid_prefix_to_txindex_commit_handle.join().unwrap()?; + + Ok(()) + }) + } +} diff --git a/crates/brk_indexer/src/storage/mod.rs b/crates/brk_indexer/src/storage/mod.rs new file mode 100644 index 000000000..15d757c6a --- /dev/null +++ b/crates/brk_indexer/src/storage/mod.rs @@ -0,0 +1,5 @@ +mod fjalls; +mod storable_vecs; + +pub use fjalls::*; +pub use storable_vecs::*; diff --git a/crates/brk_indexer/src/storage/storable_vecs/base.rs b/crates/brk_indexer/src/storage/storable_vecs/base.rs new file mode 100644 index 000000000..f762bb2b5 --- /dev/null +++ b/crates/brk_indexer/src/storage/storable_vecs/base.rs @@ -0,0 +1,90 @@ +use std::{ + fmt::Debug, + io, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; + +use storable_vec::{StoredIndex, StoredType, Version}; + +use super::Height; + +#[derive(Debug)] +pub struct StorableVec { + height: Option, + vec: storable_vec::StorableVec, +} + +impl StorableVec +where + I: StoredIndex, + T: StoredType, +{ + pub fn import(path: &Path, version: Version) -> storable_vec::Result { + Ok(Self { + height: Height::try_from(Self::path_height_(path).as_path()).ok(), + vec: storable_vec::StorableVec::forced_import(path, version)?, + }) + } + + pub fn flush(&mut self, height: Height) -> io::Result<()> { + height.write(&self.path_height())?; + self.vec.flush() + } + + pub fn truncate_if_needed(&mut self, index: I, height: Height) -> storable_vec::Result> { + if self.height.is_none_or(|self_height| self_height != height) { + height.write(&self.path_height())?; + } + self.vec.truncate_if_needed(index) + } + + pub fn height(&self) -> brk_parser::Result { + Height::try_from(self.path_height().as_path()) + } + fn path_height(&self) -> PathBuf { + Self::path_height_(self.vec.path()) + } + fn path_height_(path: &Path) -> PathBuf { + path.join("height") + } + + pub fn needs(&self, height: Height) -> bool { + self.height.is_none_or(|self_height| height > self_height) + } + #[allow(unused)] + pub fn has(&self, height: Height) -> bool { + !self.needs(height) + } +} + +impl Deref for StorableVec { + type Target = storable_vec::StorableVec; + fn deref(&self) -> &Self::Target { + &self.vec + } +} +impl DerefMut for StorableVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.vec + } +} + +pub trait AnyStorableVec: Send + Sync { + fn height(&self) -> brk_parser::Result; + fn flush(&mut self, height: Height) -> io::Result<()>; +} + +impl AnyStorableVec for StorableVec +where + I: StoredIndex, + T: StoredType, +{ + fn height(&self) -> brk_parser::Result { + self.height() + } + + fn flush(&mut self, height: Height) -> io::Result<()> { + self.flush(height) + } +} diff --git a/crates/brk_indexer/src/storage/storable_vecs/mod.rs b/crates/brk_indexer/src/storage/storable_vecs/mod.rs new file mode 100644 index 000000000..481f92af2 --- /dev/null +++ b/crates/brk_indexer/src/storage/storable_vecs/mod.rs @@ -0,0 +1,478 @@ +use std::{fs, io, path::Path}; + +use brk_parser::Height; +use rayon::prelude::*; +use storable_vec::{AnyJsonStorableVec, CACHED_GETS, Version}; + +use crate::{ + Indexes, + structs::{ + Addressbytes, Addressindex, Addresstype, Addresstypeindex, BlockHash, Emptyindex, LockTime, Multisigindex, + Opreturnindex, P2PK33AddressBytes, P2PK33index, P2PK65AddressBytes, P2PK65index, P2PKHAddressBytes, P2PKHindex, + P2SHAddressBytes, P2SHindex, P2TRAddressBytes, P2TRindex, P2WPKHAddressBytes, P2WPKHindex, P2WSHAddressBytes, + P2WSHindex, Pushonlyindex, Sats, Timestamp, TxVersion, Txid, Txindex, Txinindex, Txoutindex, Unknownindex, + Weight, + }, +}; + +mod base; + +pub use base::*; + +pub struct StorableVecs { + pub addressindex_to_addresstype: StorableVec, + pub addressindex_to_addresstypeindex: StorableVec, + pub addressindex_to_height: StorableVec, + pub height_to_blockhash: StorableVec, + pub height_to_difficulty: StorableVec, + pub height_to_first_addressindex: StorableVec, + pub height_to_first_emptyindex: StorableVec, + pub height_to_first_multisigindex: StorableVec, + pub height_to_first_opreturnindex: StorableVec, + pub height_to_first_pushonlyindex: StorableVec, + pub height_to_first_txindex: StorableVec, + pub height_to_first_txinindex: StorableVec, + pub height_to_first_txoutindex: StorableVec, + pub height_to_first_unknownindex: StorableVec, + pub height_to_first_p2pk33index: StorableVec, + pub height_to_first_p2pk65index: StorableVec, + pub height_to_first_p2pkhindex: StorableVec, + pub height_to_first_p2shindex: StorableVec, + pub height_to_first_p2trindex: StorableVec, + pub height_to_first_p2wpkhindex: StorableVec, + pub height_to_first_p2wshindex: StorableVec, + pub height_to_size: StorableVec, + pub height_to_timestamp: StorableVec, + pub height_to_weight: StorableVec, + pub p2pk33index_to_p2pk33addressbytes: StorableVec, + pub p2pk65index_to_p2pk65addressbytes: StorableVec, + pub p2pkhindex_to_p2pkhaddressbytes: StorableVec, + pub p2shindex_to_p2shaddressbytes: StorableVec, + pub p2trindex_to_p2traddressbytes: StorableVec, + pub p2wpkhindex_to_p2wpkhaddressbytes: StorableVec, + pub p2wshindex_to_p2wshaddressbytes: StorableVec, + pub txindex_to_first_txinindex: StorableVec, + pub txindex_to_first_txoutindex: StorableVec, + pub txindex_to_height: StorableVec, + pub txindex_to_locktime: StorableVec, + pub txindex_to_txid: StorableVec, + pub txindex_to_base_size: StorableVec, + pub txindex_to_total_size: StorableVec, + pub txindex_to_is_explicitly_rbf: StorableVec, + pub txindex_to_txversion: StorableVec, + pub txinindex_to_txoutindex: StorableVec, + pub txoutindex_to_addressindex: StorableVec, + pub txoutindex_to_value: StorableVec, +} + +impl StorableVecs { + pub fn import(path: &Path) -> color_eyre::Result { + fs::create_dir_all(path)?; + + Ok(Self { + addressindex_to_addresstype: StorableVec::import( + &path.join("addressindex_to_addresstype"), + Version::from(1), + )?, + addressindex_to_addresstypeindex: StorableVec::import( + &path.join("addressindex_to_addresstypeindex"), + Version::from(1), + )?, + addressindex_to_height: StorableVec::import(&path.join("addressindex_to_height"), Version::from(1))?, + height_to_blockhash: StorableVec::import(&path.join("height_to_blockhash"), Version::from(1))?, + height_to_difficulty: StorableVec::import(&path.join("height_to_difficulty"), Version::from(1))?, + height_to_first_addressindex: StorableVec::import( + &path.join("height_to_first_addressindex"), + Version::from(1), + )?, + height_to_first_emptyindex: StorableVec::import( + &path.join("height_to_first_emptyindex"), + Version::from(1), + )?, + height_to_first_multisigindex: StorableVec::import( + &path.join("height_to_first_multisigindex"), + Version::from(1), + )?, + height_to_first_opreturnindex: StorableVec::import( + &path.join("height_to_first_opreturnindex"), + Version::from(1), + )?, + height_to_first_pushonlyindex: StorableVec::import( + &path.join("height_to_first_pushonlyindex"), + Version::from(1), + )?, + height_to_first_txindex: StorableVec::import(&path.join("height_to_first_txindex"), Version::from(1))?, + height_to_first_txinindex: StorableVec::import(&path.join("height_to_first_txinindex"), Version::from(1))?, + height_to_first_txoutindex: StorableVec::import( + &path.join("height_to_first_txoutindex"), + Version::from(1), + )?, + height_to_first_unknownindex: StorableVec::import( + &path.join("height_to_first_unkownindex"), + Version::from(1), + )?, + height_to_first_p2pk33index: StorableVec::import( + &path.join("height_to_first_p2pk33index"), + Version::from(1), + )?, + height_to_first_p2pk65index: StorableVec::import( + &path.join("height_to_first_p2pk65index"), + Version::from(1), + )?, + height_to_first_p2pkhindex: StorableVec::import( + &path.join("height_to_first_p2pkhindex"), + Version::from(1), + )?, + height_to_first_p2shindex: StorableVec::import(&path.join("height_to_first_p2shindex"), Version::from(1))?, + height_to_first_p2trindex: StorableVec::import(&path.join("height_to_first_p2trindex"), Version::from(1))?, + height_to_first_p2wpkhindex: StorableVec::import( + &path.join("height_to_first_p2wpkhindex"), + Version::from(1), + )?, + height_to_first_p2wshindex: StorableVec::import( + &path.join("height_to_first_p2wshindex"), + Version::from(1), + )?, + height_to_size: StorableVec::import(&path.join("height_to_size"), Version::from(1))?, + height_to_timestamp: StorableVec::import(&path.join("height_to_timestamp"), Version::from(1))?, + height_to_weight: StorableVec::import(&path.join("height_to_weight"), Version::from(1))?, + p2pk33index_to_p2pk33addressbytes: StorableVec::import( + &path.join("p2pk33index_to_p2pk33addressbytes"), + Version::from(1), + )?, + p2pk65index_to_p2pk65addressbytes: StorableVec::import( + &path.join("p2pk65index_to_p2pk65addressbytes"), + Version::from(1), + )?, + p2pkhindex_to_p2pkhaddressbytes: StorableVec::import( + &path.join("p2pkhindex_to_p2pkhaddressbytes"), + Version::from(1), + )?, + p2shindex_to_p2shaddressbytes: StorableVec::import( + &path.join("p2shindex_to_p2shaddressbytes"), + Version::from(1), + )?, + p2trindex_to_p2traddressbytes: StorableVec::import( + &path.join("p2trindex_to_p2traddressbytes"), + Version::from(1), + )?, + p2wpkhindex_to_p2wpkhaddressbytes: StorableVec::import( + &path.join("p2wpkhindex_to_p2wpkhaddressbytes"), + Version::from(1), + )?, + p2wshindex_to_p2wshaddressbytes: StorableVec::import( + &path.join("p2wshindex_to_p2wshaddressbytes"), + Version::from(1), + )?, + txindex_to_first_txinindex: StorableVec::import( + &path.join("txindex_to_first_txinindex"), + Version::from(1), + )?, + txindex_to_first_txoutindex: StorableVec::import( + &path.join("txindex_to_first_txoutindex"), + Version::from(1), + )?, + txindex_to_height: StorableVec::import(&path.join("txindex_to_height"), Version::from(1))?, + txindex_to_locktime: StorableVec::import(&path.join("txindex_to_locktime"), Version::from(1))?, + txindex_to_txid: StorableVec::import(&path.join("txindex_to_txid"), Version::from(1))?, + txindex_to_base_size: StorableVec::import(&path.join("txindex_to_base_size"), Version::from(1))?, + txindex_to_total_size: StorableVec::import(&path.join("txindex_to_total_size"), Version::from(1))?, + txindex_to_is_explicitly_rbf: StorableVec::import( + &path.join("txindex_to_is_explicitly_rbf"), + Version::from(1), + )?, + txindex_to_txversion: StorableVec::import(&path.join("txindex_to_txversion"), Version::from(1))?, + txinindex_to_txoutindex: StorableVec::import(&path.join("txinindex_to_txoutindex"), Version::from(1))?, + txoutindex_to_addressindex: StorableVec::import( + &path.join("txoutindex_to_addressindex"), + Version::from(1), + )?, + txoutindex_to_value: StorableVec::import(&path.join("txoutindex_to_value"), Version::from(1))?, + }) + } + + pub fn rollback(&mut self, starting_indexes: &Indexes) -> storable_vec::Result<()> { + let saved_height = starting_indexes.height.decremented(); + + // We don't want to override the starting indexes so we cut from n + 1 + let height = starting_indexes.height.incremented(); + + self.height_to_first_addressindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_emptyindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_multisigindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_opreturnindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2pk33index + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2pk65index + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2pkhindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2shindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2trindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2wpkhindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_p2wshindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_pushonlyindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_txindex.truncate_if_needed(height, saved_height)?; + self.height_to_first_txinindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_txoutindex + .truncate_if_needed(height, saved_height)?; + self.height_to_first_unknownindex + .truncate_if_needed(height, saved_height)?; + + // Now we can cut everything that's out of date + let &Indexes { + addressindex, + height, + p2pk33index, + p2pk65index, + p2pkhindex, + p2shindex, + p2trindex, + p2wpkhindex, + p2wshindex, + txindex, + txinindex, + txoutindex, + .. + } = starting_indexes; + + self.height_to_blockhash.truncate_if_needed(height, saved_height)?; + self.height_to_difficulty.truncate_if_needed(height, saved_height)?; + self.height_to_size.truncate_if_needed(height, saved_height)?; + self.height_to_timestamp.truncate_if_needed(height, saved_height)?; + self.height_to_weight.truncate_if_needed(height, saved_height)?; + + self.addressindex_to_addresstype + .truncate_if_needed(addressindex, saved_height)?; + self.addressindex_to_addresstypeindex + .truncate_if_needed(addressindex, saved_height)?; + self.addressindex_to_height + .truncate_if_needed(addressindex, saved_height)?; + + self.p2pk33index_to_p2pk33addressbytes + .truncate_if_needed(p2pk33index, saved_height)?; + self.p2pk65index_to_p2pk65addressbytes + .truncate_if_needed(p2pk65index, saved_height)?; + self.p2pkhindex_to_p2pkhaddressbytes + .truncate_if_needed(p2pkhindex, saved_height)?; + self.p2shindex_to_p2shaddressbytes + .truncate_if_needed(p2shindex, saved_height)?; + self.p2trindex_to_p2traddressbytes + .truncate_if_needed(p2trindex, saved_height)?; + self.p2wpkhindex_to_p2wpkhaddressbytes + .truncate_if_needed(p2wpkhindex, saved_height)?; + self.p2wshindex_to_p2wshaddressbytes + .truncate_if_needed(p2wshindex, saved_height)?; + + self.txindex_to_first_txinindex + .truncate_if_needed(txindex, saved_height)?; + self.txindex_to_first_txoutindex + .truncate_if_needed(txindex, saved_height)?; + self.txindex_to_height.truncate_if_needed(txindex, saved_height)?; + self.txindex_to_locktime.truncate_if_needed(txindex, saved_height)?; + self.txindex_to_txid.truncate_if_needed(txindex, saved_height)?; + self.txindex_to_txversion.truncate_if_needed(txindex, saved_height)?; + self.txindex_to_base_size.truncate_if_needed(txindex, saved_height)?; + self.txindex_to_total_size.truncate_if_needed(txindex, saved_height)?; + self.txindex_to_is_explicitly_rbf + .truncate_if_needed(txindex, saved_height)?; + + self.txinindex_to_txoutindex + .truncate_if_needed(txinindex, saved_height)?; + + self.txoutindex_to_addressindex + .truncate_if_needed(txoutindex, saved_height)?; + self.txoutindex_to_value.truncate_if_needed(txoutindex, saved_height)?; + + Ok(()) + } + + pub fn flush(&mut self, height: Height) -> io::Result<()> { + self.as_mut_any_vec_slice() + .into_par_iter() + .try_for_each(|vec| vec.flush(height)) + } + + pub fn starting_height(&mut self) -> Height { + self.as_mut_any_vec_slice() + .into_iter() + .map(|vec| vec.height().map(Height::incremented).unwrap_or_default()) + .min() + .unwrap() + } + + pub fn as_any_json_vec_slice(&self) -> [&dyn AnyJsonStorableVec; 43] { + [ + &*self.addressindex_to_addresstype as &dyn AnyJsonStorableVec, + &*self.addressindex_to_addresstypeindex, + &*self.addressindex_to_height, + &*self.height_to_blockhash, + &*self.height_to_difficulty, + &*self.height_to_first_addressindex, + &*self.height_to_first_emptyindex, + &*self.height_to_first_multisigindex, + &*self.height_to_first_opreturnindex, + &*self.height_to_first_pushonlyindex, + &*self.height_to_first_txindex, + &*self.height_to_first_txinindex, + &*self.height_to_first_txoutindex, + &*self.height_to_first_unknownindex, + &*self.height_to_first_p2pk33index, + &*self.height_to_first_p2pk65index, + &*self.height_to_first_p2pkhindex, + &*self.height_to_first_p2shindex, + &*self.height_to_first_p2trindex, + &*self.height_to_first_p2wpkhindex, + &*self.height_to_first_p2wshindex, + &*self.height_to_size, + &*self.height_to_timestamp, + &*self.height_to_weight, + &*self.p2pk33index_to_p2pk33addressbytes, + &*self.p2pk65index_to_p2pk65addressbytes, + &*self.p2pkhindex_to_p2pkhaddressbytes, + &*self.p2shindex_to_p2shaddressbytes, + &*self.p2trindex_to_p2traddressbytes, + &*self.p2wpkhindex_to_p2wpkhaddressbytes, + &*self.p2wshindex_to_p2wshaddressbytes, + &*self.txindex_to_first_txinindex, + &*self.txindex_to_first_txoutindex, + &*self.txindex_to_height, + &*self.txindex_to_locktime, + &*self.txindex_to_txid, + &*self.txindex_to_base_size, + &*self.txindex_to_total_size, + &*self.txindex_to_is_explicitly_rbf, + &*self.txindex_to_txversion, + &*self.txinindex_to_txoutindex, + &*self.txoutindex_to_addressindex, + &*self.txoutindex_to_value, + ] + } + + pub fn as_mut_any_vec_slice(&mut self) -> [&mut dyn AnyStorableVec; 43] { + [ + &mut self.addressindex_to_addresstype as &mut dyn AnyStorableVec, + &mut self.addressindex_to_addresstypeindex, + &mut self.addressindex_to_height, + &mut self.height_to_blockhash, + &mut self.height_to_difficulty, + &mut self.height_to_first_addressindex, + &mut self.height_to_first_emptyindex, + &mut self.height_to_first_multisigindex, + &mut self.height_to_first_opreturnindex, + &mut self.height_to_first_pushonlyindex, + &mut self.height_to_first_txindex, + &mut self.height_to_first_txinindex, + &mut self.height_to_first_txoutindex, + &mut self.height_to_first_unknownindex, + &mut self.height_to_first_p2pk33index, + &mut self.height_to_first_p2pk65index, + &mut self.height_to_first_p2pkhindex, + &mut self.height_to_first_p2shindex, + &mut self.height_to_first_p2trindex, + &mut self.height_to_first_p2wpkhindex, + &mut self.height_to_first_p2wshindex, + &mut self.height_to_size, + &mut self.height_to_timestamp, + &mut self.height_to_weight, + &mut self.p2pk33index_to_p2pk33addressbytes, + &mut self.p2pk65index_to_p2pk65addressbytes, + &mut self.p2pkhindex_to_p2pkhaddressbytes, + &mut self.p2shindex_to_p2shaddressbytes, + &mut self.p2trindex_to_p2traddressbytes, + &mut self.p2wpkhindex_to_p2wpkhaddressbytes, + &mut self.p2wshindex_to_p2wshaddressbytes, + &mut self.txindex_to_first_txinindex, + &mut self.txindex_to_first_txoutindex, + &mut self.txindex_to_height, + &mut self.txindex_to_locktime, + &mut self.txindex_to_txid, + &mut self.txindex_to_base_size, + &mut self.txindex_to_total_size, + &mut self.txindex_to_is_explicitly_rbf, + &mut self.txindex_to_txversion, + &mut self.txinindex_to_txoutindex, + &mut self.txoutindex_to_addressindex, + &mut self.txoutindex_to_value, + ] + } +} + +impl StorableVecs { + pub fn get_addressbytes( + &self, + addresstype: Addresstype, + addresstypeindex: Addresstypeindex, + ) -> storable_vec::Result> { + Ok(match addresstype { + Addresstype::P2PK65 => self + .p2pk65index_to_p2pk65addressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + Addresstype::P2PK33 => self + .p2pk33index_to_p2pk33addressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + Addresstype::P2PKH => self + .p2pkhindex_to_p2pkhaddressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + Addresstype::P2SH => self + .p2shindex_to_p2shaddressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + Addresstype::P2WPKH => self + .p2wpkhindex_to_p2wpkhaddressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + Addresstype::P2WSH => self + .p2wshindex_to_p2wshaddressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + Addresstype::P2TR => self + .p2trindex_to_p2traddressbytes + .get(addresstypeindex.into())? + // .map(|v| Addressbytes::from(v.clone())), + .map(|v| Addressbytes::from(v.into_inner())), + _ => unreachable!(), + }) + } + + pub fn push_addressbytes_if_needed( + &mut self, + index: Addresstypeindex, + addressbytes: Addressbytes, + ) -> storable_vec::Result<()> { + match addressbytes { + Addressbytes::P2PK65(bytes) => self + .p2pk65index_to_p2pk65addressbytes + .push_if_needed(index.into(), bytes), + Addressbytes::P2PK33(bytes) => self + .p2pk33index_to_p2pk33addressbytes + .push_if_needed(index.into(), bytes), + Addressbytes::P2PKH(bytes) => self.p2pkhindex_to_p2pkhaddressbytes.push_if_needed(index.into(), bytes), + Addressbytes::P2SH(bytes) => self.p2shindex_to_p2shaddressbytes.push_if_needed(index.into(), bytes), + Addressbytes::P2WPKH(bytes) => self + .p2wpkhindex_to_p2wpkhaddressbytes + .push_if_needed(index.into(), bytes), + Addressbytes::P2WSH(bytes) => self.p2wshindex_to_p2wshaddressbytes.push_if_needed(index.into(), bytes), + Addressbytes::P2TR(bytes) => self.p2trindex_to_p2traddressbytes.push_if_needed(index.into(), bytes), + } + } +} diff --git a/crates/brk_indexer/src/structs/addressbytes.rs b/crates/brk_indexer/src/structs/addressbytes.rs new file mode 100644 index 000000000..2d679a3cd --- /dev/null +++ b/crates/brk_indexer/src/structs/addressbytes.rs @@ -0,0 +1,196 @@ +use brk_parser::bitcoin::ScriptBuf; +use color_eyre::eyre::eyre; +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Addresstype; + +#[derive(Debug, PartialEq, Eq)] +pub enum Addressbytes { + P2PK65(P2PK65AddressBytes), + P2PK33(P2PK33AddressBytes), + P2PKH(P2PKHAddressBytes), + P2SH(P2SHAddressBytes), + P2WPKH(P2WPKHAddressBytes), + P2WSH(P2WSHAddressBytes), + P2TR(P2TRAddressBytes), +} + +impl Addressbytes { + pub fn as_slice(&self) -> &[u8] { + match self { + Addressbytes::P2PK65(bytes) => &bytes[..], + Addressbytes::P2PK33(bytes) => &bytes[..], + Addressbytes::P2PKH(bytes) => &bytes[..], + Addressbytes::P2SH(bytes) => &bytes[..], + Addressbytes::P2WPKH(bytes) => &bytes[..], + Addressbytes::P2WSH(bytes) => &bytes[..], + Addressbytes::P2TR(bytes) => &bytes[..], + } + } +} + +impl TryFrom<(&ScriptBuf, Addresstype)> for Addressbytes { + type Error = color_eyre::Report; + fn try_from(tuple: (&ScriptBuf, Addresstype)) -> Result { + let (script, addresstype) = tuple; + + match addresstype { + Addresstype::P2PK65 => { + let bytes = script.as_bytes(); + let bytes = match bytes.len() { + 67 => &bytes[1..66], + _ => { + dbg!(bytes); + return Err(eyre!("Wrong len")); + } + }; + Ok(Self::P2PK65(P2PK65AddressBytes(U8x65::from(bytes)))) + } + Addresstype::P2PK33 => { + let bytes = script.as_bytes(); + let bytes = match bytes.len() { + 35 => &bytes[1..34], + _ => { + dbg!(bytes); + return Err(eyre!("Wrong len")); + } + }; + Ok(Self::P2PK33(P2PK33AddressBytes(U8x33::from(bytes)))) + } + Addresstype::P2PKH => { + let bytes = &script.as_bytes()[3..23]; + Ok(Self::P2PKH(P2PKHAddressBytes(U8x20::from(bytes)))) + } + Addresstype::P2SH => { + let bytes = &script.as_bytes()[2..22]; + Ok(Self::P2SH(P2SHAddressBytes(U8x20::from(bytes)))) + } + Addresstype::P2WPKH => { + let bytes = &script.as_bytes()[2..]; + Ok(Self::P2WPKH(P2WPKHAddressBytes(U8x20::from(bytes)))) + } + Addresstype::P2WSH => { + let bytes = &script.as_bytes()[2..]; + Ok(Self::P2WSH(P2WSHAddressBytes(U8x32::from(bytes)))) + } + Addresstype::P2TR => { + let bytes = &script.as_bytes()[2..]; + Ok(Self::P2TR(P2TRAddressBytes(U8x32::from(bytes)))) + } + Addresstype::Multisig => Err(eyre!("multisig address type")), + Addresstype::PushOnly => Err(eyre!("push_only address type")), + Addresstype::Unknown => Err(eyre!("unknown address type")), + Addresstype::Empty => Err(eyre!("empty address type")), + Addresstype::OpReturn => Err(eyre!("op_return address type")), + } + } +} + +impl From for Addressbytes { + fn from(value: P2PK65AddressBytes) -> Self { + Self::P2PK65(value) + } +} +impl From for Addressbytes { + fn from(value: P2PK33AddressBytes) -> Self { + Self::P2PK33(value) + } +} +impl From for Addressbytes { + fn from(value: P2PKHAddressBytes) -> Self { + Self::P2PKH(value) + } +} +impl From for Addressbytes { + fn from(value: P2SHAddressBytes) -> Self { + Self::P2SH(value) + } +} +impl From for Addressbytes { + fn from(value: P2WPKHAddressBytes) -> Self { + Self::P2WPKH(value) + } +} +impl From for Addressbytes { + fn from(value: P2WSHAddressBytes) -> Self { + Self::P2WSH(value) + } +} +impl From for Addressbytes { + fn from(value: P2TRAddressBytes) -> Self { + Self::P2TR(value) + } +} + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2PK65AddressBytes(U8x65); + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2PK33AddressBytes(U8x33); + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2PKHAddressBytes(U8x20); + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2SHAddressBytes(U8x20); + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2WPKHAddressBytes(U8x20); + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2WSHAddressBytes(U8x32); + +#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct P2TRAddressBytes(U8x32); + +#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct U8x20([u8; 20]); +impl From<&[u8]> for U8x20 { + fn from(slice: &[u8]) -> Self { + let mut arr = [0; 20]; + arr.copy_from_slice(slice); + Self(arr) + } +} + +#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct U8x32([u8; 32]); +impl From<&[u8]> for U8x32 { + fn from(slice: &[u8]) -> Self { + let mut arr = [0; 32]; + arr.copy_from_slice(slice); + Self(arr) + } +} + +#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct U8x33(#[serde(with = "serde_bytes")] [u8; 33]); +impl From<&[u8]> for U8x33 { + fn from(slice: &[u8]) -> Self { + let mut arr = [0; 33]; + arr.copy_from_slice(slice); + Self(arr) + } +} + +#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct U8x64(#[serde(with = "serde_bytes")] [u8; 64]); +impl From<&[u8]> for U8x64 { + fn from(slice: &[u8]) -> Self { + let mut arr = [0; 64]; + arr.copy_from_slice(slice); + Self(arr) + } +} + +#[derive(Debug, Clone, Deref, DerefMut, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct U8x65(#[serde(with = "serde_bytes")] [u8; 65]); +impl From<&[u8]> for U8x65 { + fn from(slice: &[u8]) -> Self { + let mut arr = [0; 65]; + arr.copy_from_slice(slice); + Self(arr) + } +} diff --git a/crates/brk_indexer/src/structs/addressindex.rs b/crates/brk_indexer/src/structs/addressindex.rs new file mode 100644 index 000000000..c30d2ac4e --- /dev/null +++ b/crates/brk_indexer/src/structs/addressindex.rs @@ -0,0 +1,95 @@ +use std::ops::Add; + +use byteview::ByteView; +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Addressindex(u32); + +impl Addressindex { + pub const BYTES: usize = size_of::(); + + pub fn decremented(self) -> Self { + Self(*self - 1) + } + + pub fn increment(&mut self) { + self.0 += 1; + } + + pub fn incremented(self) -> Self { + Self(*self + 1) + } +} + +impl From for Addressindex { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for Addressindex { + fn from(value: u64) -> Self { + Self(value as u32) + } +} +impl From for u64 { + fn from(value: Addressindex) -> Self { + value.0 as u64 + } +} + +impl From for Addressindex { + fn from(value: usize) -> Self { + Self(value as u32) + } +} +impl From for usize { + fn from(value: Addressindex) -> Self { + value.0 as usize + } +} + +impl TryFrom for Addressindex { + type Error = storable_vec::Error; + fn try_from(value: ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} +impl From for ByteView { + fn from(value: Addressindex) -> Self { + Self::new(value.as_bytes()) + } +} + +impl Add for Addressindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs as u32) + } +} + +impl Add for Addressindex { + type Output = Self; + fn add(self, rhs: Addressindex) -> Self::Output { + Self(self.0 + rhs.0) + } +} diff --git a/crates/brk_indexer/src/structs/addresstype.rs b/crates/brk_indexer/src/structs/addresstype.rs new file mode 100644 index 000000000..94a3e06a8 --- /dev/null +++ b/crates/brk_indexer/src/structs/addresstype.rs @@ -0,0 +1,59 @@ +use brk_parser::bitcoin::ScriptBuf; +use serde::Serialize; +use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes}; + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, TryFromBytes, Immutable, IntoBytes, KnownLayout, Serialize, +)] +#[repr(u8)] +pub enum Addresstype { + P2PK65, + P2PK33, + P2PKH, + P2SH, + P2WPKH, + P2WSH, + P2TR, + Multisig = 251, + PushOnly = 252, + OpReturn = 253, + Empty = 254, + Unknown = 255, +} + +impl From<&ScriptBuf> for Addresstype { + fn from(script: &ScriptBuf) -> Self { + if script.is_p2pk() { + let bytes = script.as_bytes(); + + match bytes.len() { + 67 => Self::P2PK65, + 35 => Self::P2PK33, + _ => { + dbg!(bytes); + unreachable!() + } + } + } else if script.is_p2pkh() { + Self::P2PKH + } else if script.is_p2sh() { + Self::P2SH + } else if script.is_p2wpkh() { + Self::P2WPKH + } else if script.is_p2wsh() { + Self::P2WSH + } else if script.is_p2tr() { + Self::P2TR + } else if script.is_empty() { + Self::Empty + } else if script.is_op_return() { + Self::OpReturn + } else if script.is_push_only() { + Self::PushOnly + } else if script.is_multisig() { + Self::Multisig + } else { + Self::Unknown + } + } +} diff --git a/crates/brk_indexer/src/structs/addresstypeindex.rs b/crates/brk_indexer/src/structs/addresstypeindex.rs new file mode 100644 index 000000000..97f99c533 --- /dev/null +++ b/crates/brk_indexer/src/structs/addresstypeindex.rs @@ -0,0 +1,566 @@ +use std::ops::Add; + +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Addresstypeindex(u32); + +impl Addresstypeindex { + pub fn decremented(self) -> Self { + Self(*self - 1) + } + + pub fn increment(&mut self) { + self.0 += 1; + } + + pub fn incremented(self) -> Self { + Self(*self + 1) + } + + pub fn copy_then_increment(&mut self) -> Self { + let i = *self; + self.increment(); + i + } +} + +impl From for Addresstypeindex { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for Addresstypeindex { + fn from(value: u64) -> Self { + Self(value as u32) + } +} +impl From for u64 { + fn from(value: Addresstypeindex) -> Self { + value.0 as u64 + } +} + +impl From for Addresstypeindex { + fn from(value: usize) -> Self { + Self(value as u32) + } +} +impl From for usize { + fn from(value: Addresstypeindex) -> Self { + value.0 as usize + } +} + +impl Add for Addresstypeindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs as u32) + } +} + +impl Add for Addresstypeindex { + type Output = Self; + fn add(self, rhs: Addresstypeindex) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Emptyindex(Addresstypeindex); +impl From for Emptyindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: Emptyindex) -> Self { + Self::from(*value) + } +} +impl From for Emptyindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for Emptyindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Multisigindex(Addresstypeindex); +impl From for Multisigindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: Multisigindex) -> Self { + Self::from(*value) + } +} +impl From for Multisigindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for Multisigindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Opreturnindex(Addresstypeindex); +impl From for Opreturnindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: Opreturnindex) -> Self { + Self::from(*value) + } +} +impl From for Opreturnindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for Opreturnindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Pushonlyindex(Addresstypeindex); +impl From for Pushonlyindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: Pushonlyindex) -> Self { + Self::from(*value) + } +} +impl From for Pushonlyindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for Pushonlyindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Unknownindex(Addresstypeindex); +impl From for Unknownindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: Unknownindex) -> Self { + Self::from(*value) + } +} +impl From for Unknownindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for Unknownindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2PK33index(Addresstypeindex); +impl From for P2PK33index { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2PK33index) -> Self { + Self::from(*value) + } +} +impl From for P2PK33index { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2PK33index { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2PK65index(Addresstypeindex); +impl From for P2PK65index { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2PK65index) -> Self { + Self::from(*value) + } +} +impl From for P2PK65index { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2PK65index { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2PKHindex(Addresstypeindex); +impl From for P2PKHindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2PKHindex) -> Self { + Self::from(*value) + } +} +impl From for P2PKHindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2PKHindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2SHindex(Addresstypeindex); +impl From for P2SHindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2SHindex) -> Self { + Self::from(*value) + } +} +impl From for P2SHindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2SHindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2TRindex(Addresstypeindex); +impl From for P2TRindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2TRindex) -> Self { + Self::from(*value) + } +} +impl From for P2TRindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2TRindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2WPKHindex(Addresstypeindex); +impl From for P2WPKHindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2WPKHindex) -> Self { + Self::from(*value) + } +} +impl From for P2WPKHindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2WPKHindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct P2WSHindex(Addresstypeindex); +impl From for P2WSHindex { + fn from(value: Addresstypeindex) -> Self { + Self(value) + } +} +impl From for usize { + fn from(value: P2WSHindex) -> Self { + Self::from(*value) + } +} +impl From for P2WSHindex { + fn from(value: usize) -> Self { + Self(Addresstypeindex::from(value)) + } +} +impl Add for P2WSHindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(*self + rhs) + } +} diff --git a/crates/brk_indexer/src/structs/blockhash.rs b/crates/brk_indexer/src/structs/blockhash.rs new file mode 100644 index 000000000..4fd59e391 --- /dev/null +++ b/crates/brk_indexer/src/structs/blockhash.rs @@ -0,0 +1,31 @@ +use std::mem; + +use brk_parser::{ + Height, + rpc::{Client, RpcApi}, +}; +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct BlockHash([u8; 32]); + +impl From for BlockHash { + fn from(value: bitcoin::BlockHash) -> Self { + unsafe { mem::transmute(value) } + } +} + +impl From for bitcoin::BlockHash { + fn from(value: BlockHash) -> Self { + unsafe { mem::transmute(value) } + } +} + +impl TryFrom<(&Client, Height)> for BlockHash { + type Error = brk_parser::rpc::Error; + fn try_from((rpc, height): (&Client, Height)) -> Result { + Ok(Self::from(rpc.get_block_hash(u64::from(height))?)) + } +} diff --git a/crates/brk_indexer/src/structs/compressed.rs b/crates/brk_indexer/src/structs/compressed.rs new file mode 100644 index 000000000..51a00f54d --- /dev/null +++ b/crates/brk_indexer/src/structs/compressed.rs @@ -0,0 +1,100 @@ +use std::hash::Hasher; + +use byteview::ByteView; +use derive_deref::Deref; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::{Addressbytes, Addresstype, BlockHash, Txid}; + +#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, Immutable, IntoBytes, KnownLayout)] +pub struct AddressHash([u8; 8]); +impl From<(&Addressbytes, Addresstype)> for AddressHash { + fn from((addressbytes, addresstype): (&Addressbytes, Addresstype)) -> Self { + let mut hasher = rapidhash::RapidHasher::default(); + hasher.write(addressbytes.as_slice()); + let mut slice = hasher.finish().to_le_bytes(); + slice[0] = slice[0].wrapping_add(addresstype as u8); + Self(slice) + } +} +impl From<[u8; 8]> for AddressHash { + fn from(value: [u8; 8]) -> Self { + Self(value) + } +} +impl TryFrom for AddressHash { + type Error = storable_vec::Error; + fn try_from(value: ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} +impl From<&AddressHash> for ByteView { + fn from(value: &AddressHash) -> Self { + Self::new(value.as_bytes()) + } +} +impl From for ByteView { + fn from(value: AddressHash) -> Self { + Self::from(&value) + } +} + +#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, Immutable, IntoBytes, KnownLayout)] +pub struct BlockHashPrefix([u8; 8]); +impl From<&BlockHash> for BlockHashPrefix { + fn from(value: &BlockHash) -> Self { + Self(copy_first_8bytes(&value[..]).unwrap()) + } +} +impl TryFrom for BlockHashPrefix { + type Error = storable_vec::Error; + fn try_from(value: ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} +impl From<&BlockHashPrefix> for ByteView { + fn from(value: &BlockHashPrefix) -> Self { + Self::new(value.as_bytes()) + } +} +impl From for ByteView { + fn from(value: BlockHashPrefix) -> Self { + Self::from(&value) + } +} + +#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, Immutable, IntoBytes, KnownLayout)] +pub struct TxidPrefix([u8; 8]); +impl From<&Txid> for TxidPrefix { + fn from(value: &Txid) -> Self { + Self(copy_first_8bytes(&value[..]).unwrap()) + } +} +impl TryFrom for TxidPrefix { + type Error = storable_vec::Error; + fn try_from(value: ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} +impl From<&TxidPrefix> for ByteView { + fn from(value: &TxidPrefix) -> Self { + Self::new(value.as_bytes()) + } +} +impl From for ByteView { + fn from(value: TxidPrefix) -> Self { + Self::from(&value) + } +} + +fn copy_first_8bytes(slice: &[u8]) -> Result<[u8; 8], ()> { + let mut buf: [u8; 8] = [0; 8]; + let buf_len = buf.len(); + if slice.len() < buf_len { + return Err(()); + } + slice.iter().take(buf_len).enumerate().for_each(|(i, r)| { + buf[i] = *r; + }); + Ok(buf) +} diff --git a/crates/brk_indexer/src/structs/indexes.rs b/crates/brk_indexer/src/structs/indexes.rs new file mode 100644 index 000000000..5339b559d --- /dev/null +++ b/crates/brk_indexer/src/structs/indexes.rs @@ -0,0 +1,114 @@ +use brk_parser::NUMBER_OF_UNSAFE_BLOCKS; +use brk_parser::{Height, rpc::Client}; +use color_eyre::eyre::ContextCompat; +use storable_vec::CACHED_GETS; + +use crate::storage::{Fjalls, StorableVecs}; + +use super::{ + Addressindex, BlockHash, Emptyindex, Multisigindex, Opreturnindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, + P2TRindex, P2WPKHindex, P2WSHindex, Pushonlyindex, Txindex, Txinindex, Txoutindex, Unknownindex, +}; + +#[derive(Debug, Default)] +pub struct Indexes { + pub addressindex: Addressindex, + pub emptyindex: Emptyindex, + pub height: Height, + pub multisigindex: Multisigindex, + pub opreturnindex: Opreturnindex, + pub p2pk33index: P2PK33index, + pub p2pk65index: P2PK65index, + pub p2pkhindex: P2PKHindex, + pub p2shindex: P2SHindex, + pub p2trindex: P2TRindex, + pub p2wpkhindex: P2WPKHindex, + pub p2wshindex: P2WSHindex, + pub pushonlyindex: Pushonlyindex, + pub txindex: Txindex, + pub txinindex: Txinindex, + pub txoutindex: Txoutindex, + pub unknownindex: Unknownindex, +} + +impl Indexes { + pub fn push_if_needed(&self, vecs: &mut StorableVecs) -> storable_vec::Result<()> { + let height = self.height; + vecs.height_to_first_txindex.push_if_needed(height, self.txindex)?; + vecs.height_to_first_txinindex.push_if_needed(height, self.txinindex)?; + vecs.height_to_first_txoutindex + .push_if_needed(height, self.txoutindex)?; + vecs.height_to_first_addressindex + .push_if_needed(height, self.addressindex)?; + vecs.height_to_first_emptyindex + .push_if_needed(height, self.emptyindex)?; + vecs.height_to_first_multisigindex + .push_if_needed(height, self.multisigindex)?; + vecs.height_to_first_opreturnindex + .push_if_needed(height, self.opreturnindex)?; + vecs.height_to_first_pushonlyindex + .push_if_needed(height, self.pushonlyindex)?; + vecs.height_to_first_unknownindex + .push_if_needed(height, self.unknownindex)?; + vecs.height_to_first_p2pk33index + .push_if_needed(height, self.p2pk33index)?; + vecs.height_to_first_p2pk65index + .push_if_needed(height, self.p2pk65index)?; + vecs.height_to_first_p2pkhindex + .push_if_needed(height, self.p2pkhindex)?; + vecs.height_to_first_p2shindex.push_if_needed(height, self.p2shindex)?; + vecs.height_to_first_p2trindex.push_if_needed(height, self.p2trindex)?; + vecs.height_to_first_p2wpkhindex + .push_if_needed(height, self.p2wpkhindex)?; + vecs.height_to_first_p2wshindex + .push_if_needed(height, self.p2wshindex)?; + Ok(()) + } + + pub fn push_future_if_needed(&mut self, vecs: &mut StorableVecs) -> storable_vec::Result<()> { + self.height.increment(); + self.push_if_needed(vecs)?; + self.height.decrement(); + Ok(()) + } +} + +impl TryFrom<(&mut StorableVecs, &Fjalls, &Client)> for Indexes { + type Error = color_eyre::Report; + fn try_from((vecs, trees, rpc): (&mut StorableVecs, &Fjalls, &Client)) -> color_eyre::Result { + // Height at which we wanna start: min last saved + 1 or 0 + let starting_height = vecs.starting_height().min(trees.starting_height()); + + // But we also need to check the chain and start earlier in case of a reorg + let height = (starting_height + .checked_sub(NUMBER_OF_UNSAFE_BLOCKS as u32) + .unwrap_or_default()..*starting_height) // ..= because of last saved + 1 + .map(Height::from) + .find(|height| { + let rpc_blockhash = BlockHash::try_from((rpc, *height)).unwrap(); + let saved_blockhash = vecs.height_to_blockhash.get(*height).unwrap().unwrap(); + &rpc_blockhash != saved_blockhash.as_ref() + }) + .unwrap_or(starting_height); + + Ok(Self { + addressindex: *vecs.height_to_first_addressindex.get(height)?.context("")?, + emptyindex: *vecs.height_to_first_emptyindex.get(height)?.context("")?, + height, + multisigindex: *vecs.height_to_first_multisigindex.get(height)?.context("")?, + opreturnindex: *vecs.height_to_first_opreturnindex.get(height)?.context("")?, + p2pk33index: *vecs.height_to_first_p2pk33index.get(height)?.context("")?, + p2pk65index: *vecs.height_to_first_p2pk65index.get(height)?.context("")?, + p2pkhindex: *vecs.height_to_first_p2pkhindex.get(height)?.context("")?, + p2shindex: *vecs.height_to_first_p2shindex.get(height)?.context("")?, + p2trindex: *vecs.height_to_first_p2trindex.get(height)?.context("")?, + p2wpkhindex: *vecs.height_to_first_p2wpkhindex.get(height)?.context("")?, + p2wshindex: *vecs.height_to_first_p2wshindex.get(height)?.context("")?, + pushonlyindex: *vecs.height_to_first_pushonlyindex.get(height)?.context("")?, + txindex: *vecs.height_to_first_txindex.get(height)?.context("")?, + txinindex: *vecs.height_to_first_txinindex.get(height)?.context("")?, + txoutindex: *vecs.height_to_first_txoutindex.get(height)?.context("")?, + unknownindex: *vecs.height_to_first_unknownindex.get(height)?.context("")?, + }) + } +} diff --git a/crates/brk_indexer/src/structs/locktime.rs b/crates/brk_indexer/src/structs/locktime.rs new file mode 100644 index 000000000..ceaac5b46 --- /dev/null +++ b/crates/brk_indexer/src/structs/locktime.rs @@ -0,0 +1,30 @@ +use brk_parser::Height; +use serde::Serialize; +use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes}; + +use super::Timestamp; + +#[derive(Debug, Immutable, Clone, Copy, IntoBytes, KnownLayout, TryFromBytes, Serialize)] +#[repr(C)] +pub enum LockTime { + Height(Height), + Timestamp(Timestamp), +} + +impl From for LockTime { + fn from(value: bitcoin::absolute::LockTime) -> Self { + match value { + bitcoin::absolute::LockTime::Blocks(h) => LockTime::Height(h.into()), + bitcoin::absolute::LockTime::Seconds(t) => LockTime::Timestamp(t.into()), + } + } +} + +impl From for bitcoin::absolute::LockTime { + fn from(value: LockTime) -> Self { + match value { + LockTime::Height(h) => bitcoin::absolute::LockTime::Blocks(h.into()), + LockTime::Timestamp(t) => bitcoin::absolute::LockTime::Seconds(t.into()), + } + } +} diff --git a/crates/brk_indexer/src/structs/mod.rs b/crates/brk_indexer/src/structs/mod.rs new file mode 100644 index 000000000..9c64bd3f8 --- /dev/null +++ b/crates/brk_indexer/src/structs/mod.rs @@ -0,0 +1,37 @@ +mod addressbytes; +mod addressindex; +mod addresstype; +mod addresstypeindex; +mod blockhash; +mod compressed; +mod indexes; +mod locktime; +mod sats; +mod timestamp; +mod txid; +mod txindex; +mod txinindex; +mod txoutindex; +mod txversion; +mod vin; +mod vout; +mod weight; + +pub use addressbytes::*; +pub use addressindex::*; +pub use addresstype::*; +pub use addresstypeindex::*; +pub use blockhash::*; +pub use compressed::*; +pub use indexes::*; +pub use locktime::*; +pub use sats::*; +pub use timestamp::*; +pub use txid::*; +pub use txindex::*; +pub use txinindex::*; +pub use txoutindex::*; +pub use txversion::*; +pub use vin::*; +pub use vout::*; +pub use weight::*; diff --git a/crates/brk_indexer/src/structs/sats.rs b/crates/brk_indexer/src/structs/sats.rs new file mode 100644 index 000000000..9d5881440 --- /dev/null +++ b/crates/brk_indexer/src/structs/sats.rs @@ -0,0 +1,107 @@ +use std::{ + iter::Sum, + ops::{Add, AddAssign, Mul, Sub, SubAssign}, +}; + +use brk_parser::{Height, bitcoin::Amount}; +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Sats(u64); + +impl Sats { + pub const ZERO: Self = Self(0); + + pub fn is_zero(&self) -> bool { + *self == Self::ZERO + } +} + +impl Add for Sats { + type Output = Sats; + fn add(self, rhs: Sats) -> Self::Output { + Sats::from(*self + *rhs) + } +} + +impl AddAssign for Sats { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sub for Sats { + type Output = Sats; + fn sub(self, rhs: Sats) -> Self::Output { + Sats::from(*self - *rhs) + } +} + +impl SubAssign for Sats { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl Mul for Sats { + type Output = Sats; + fn mul(self, rhs: Sats) -> Self::Output { + Sats::from(*self * *rhs) + } +} + +impl Mul for Sats { + type Output = Sats; + fn mul(self, rhs: u64) -> Self::Output { + Sats::from(*self * rhs) + } +} + +impl Mul for Sats { + type Output = Sats; + fn mul(self, rhs: Height) -> Self::Output { + Sats::from(*self * *rhs as u64) + } +} + +impl Sum for Sats { + fn sum>(iter: I) -> Self { + let sats: u64 = iter.map(|sats| *sats).sum(); + Sats::from(sats) + } +} + +impl From for Sats { + fn from(value: u64) -> Self { + Self(value) + } +} + +impl From for Sats { + fn from(value: Amount) -> Self { + Self(value.to_sat()) + } +} +impl From for Amount { + fn from(value: Sats) -> Self { + Self::from_sat(value.0) + } +} diff --git a/crates/brk_indexer/src/structs/timestamp.rs b/crates/brk_indexer/src/structs/timestamp.rs new file mode 100644 index 000000000..ee48a5027 --- /dev/null +++ b/crates/brk_indexer/src/structs/timestamp.rs @@ -0,0 +1,32 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive( + Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, Immutable, IntoBytes, KnownLayout, Serialize, +)] +pub struct Timestamp(u32); + +impl From for Timestamp { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for jiff::Timestamp { + fn from(value: Timestamp) -> Self { + jiff::Timestamp::from_second(*value as i64).unwrap() + } +} + +impl From for Timestamp { + fn from(value: bitcoin::locktime::absolute::Time) -> Self { + Self(value.to_consensus_u32()) + } +} + +impl From for bitcoin::locktime::absolute::Time { + fn from(value: Timestamp) -> Self { + bitcoin::locktime::absolute::Time::from_consensus(*value).unwrap() + } +} diff --git a/crates/brk_indexer/src/structs/txid.rs b/crates/brk_indexer/src/structs/txid.rs new file mode 100644 index 000000000..c180ab457 --- /dev/null +++ b/crates/brk_indexer/src/structs/txid.rs @@ -0,0 +1,20 @@ +use std::mem; + +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct Txid([u8; 32]); + +impl From for Txid { + fn from(value: bitcoin::Txid) -> Self { + unsafe { mem::transmute(value) } + } +} + +impl From for bitcoin::Txid { + fn from(value: Txid) -> Self { + unsafe { mem::transmute(value) } + } +} diff --git a/crates/brk_indexer/src/structs/txindex.rs b/crates/brk_indexer/src/structs/txindex.rs new file mode 100644 index 000000000..8ca6fba34 --- /dev/null +++ b/crates/brk_indexer/src/structs/txindex.rs @@ -0,0 +1,102 @@ +use std::ops::{Add, AddAssign, Sub}; + +use byteview::ByteView; +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Txindex(u32); + +impl Txindex { + pub fn incremented(self) -> Self { + Self(*self + 1) + } + + pub fn decremented(self) -> Self { + Self(*self - 1) + } +} + +impl Add for Txindex { + type Output = Self; + fn add(self, rhs: Txindex) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Add for Txindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs as u32) + } +} + +impl AddAssign for Txindex { + fn add_assign(&mut self, rhs: Txindex) { + self.0 += rhs.0 + } +} + +impl Sub for Txindex { + type Output = Txindex; + fn sub(self, rhs: Txindex) -> Self::Output { + Self::from(*self - *rhs) + } +} + +impl From for Txindex { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for Txindex { + fn from(value: u64) -> Self { + Self(value as u32) + } +} +impl From for u64 { + fn from(value: Txindex) -> Self { + value.0 as u64 + } +} + +impl From for Txindex { + fn from(value: usize) -> Self { + Self(value as u32) + } +} +impl From for usize { + fn from(value: Txindex) -> Self { + value.0 as usize + } +} + +impl TryFrom for Txindex { + type Error = storable_vec::Error; + fn try_from(value: ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} +impl From for ByteView { + fn from(value: Txindex) -> Self { + Self::new(value.as_bytes()) + } +} diff --git a/crates/brk_indexer/src/structs/txinindex.rs b/crates/brk_indexer/src/structs/txinindex.rs new file mode 100644 index 000000000..0089ea33e --- /dev/null +++ b/crates/brk_indexer/src/structs/txinindex.rs @@ -0,0 +1,101 @@ +use std::ops::{Add, AddAssign, Sub}; + +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Vin; + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Txinindex(u64); + +impl Txinindex { + pub fn incremented(self) -> Self { + Self(*self + 1) + } + + pub fn decremented(self) -> Self { + Self(*self - 1) + } +} + +impl Add for Txinindex { + type Output = Self; + fn add(self, rhs: Txinindex) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Add for Txinindex { + type Output = Self; + fn add(self, rhs: Vin) -> Self::Output { + Self(self.0 + u64::from(rhs)) + } +} + +impl Add for Txinindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs as u64) + } +} + +impl AddAssign for Txinindex { + fn add_assign(&mut self, rhs: Txinindex) { + self.0 += rhs.0 + } +} + +impl Sub for Txinindex { + type Output = Self; + fn sub(self, rhs: Txinindex) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl From for u32 { + fn from(value: Txinindex) -> Self { + if value.0 > u32::MAX as u64 { + panic!() + } + value.0 as u32 + } +} + +impl From for Txinindex { + fn from(value: u64) -> Self { + Self(value) + } +} +impl From for u64 { + fn from(value: Txinindex) -> Self { + value.0 + } +} + +impl From for Txinindex { + fn from(value: usize) -> Self { + Self(value as u64) + } +} +impl From for usize { + fn from(value: Txinindex) -> Self { + value.0 as usize + } +} diff --git a/crates/brk_indexer/src/structs/txoutindex.rs b/crates/brk_indexer/src/structs/txoutindex.rs new file mode 100644 index 000000000..72d4c0a37 --- /dev/null +++ b/crates/brk_indexer/src/structs/txoutindex.rs @@ -0,0 +1,107 @@ +use std::ops::{Add, AddAssign, Sub}; + +use derive_deref::{Deref, DerefMut}; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use super::Vout; + +#[derive( + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Copy, + Deref, + DerefMut, + Default, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, + Serialize, +)] +pub struct Txoutindex(u64); + +impl Txoutindex { + pub const COINBASE: Self = Self(u64::MAX); + + pub fn incremented(self) -> Self { + Self(*self + 1) + } + + pub fn decremented(self) -> Self { + Self(*self - 1) + } + + pub fn is_coinbase(self) -> bool { + self == Self::COINBASE + } +} + +impl Add for Txoutindex { + type Output = Self; + fn add(self, rhs: Txoutindex) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Add for Txoutindex { + type Output = Self; + fn add(self, rhs: Vout) -> Self::Output { + Self(self.0 + u64::from(rhs)) + } +} + +impl Add for Txoutindex { + type Output = Self; + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs as u64) + } +} + +impl AddAssign for Txoutindex { + fn add_assign(&mut self, rhs: Txoutindex) { + self.0 += rhs.0 + } +} + +impl Sub for Txoutindex { + type Output = Self; + fn sub(self, rhs: Txoutindex) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl From for u32 { + fn from(value: Txoutindex) -> Self { + if value.0 > u32::MAX as u64 { + panic!() + } + value.0 as u32 + } +} + +impl From for Txoutindex { + fn from(value: u64) -> Self { + Self(value) + } +} +impl From for u64 { + fn from(value: Txoutindex) -> Self { + value.0 + } +} + +impl From for Txoutindex { + fn from(value: usize) -> Self { + Self(value as u64) + } +} +impl From for usize { + fn from(value: Txoutindex) -> Self { + value.0 as usize + } +} diff --git a/crates/brk_indexer/src/structs/txversion.rs b/crates/brk_indexer/src/structs/txversion.rs new file mode 100644 index 000000000..c7ca53183 --- /dev/null +++ b/crates/brk_indexer/src/structs/txversion.rs @@ -0,0 +1,18 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Debug, Deref, Clone, Copy, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct TxVersion(i32); + +impl From for TxVersion { + fn from(value: bitcoin::transaction::Version) -> Self { + Self(value.0) + } +} + +impl From for bitcoin::transaction::Version { + fn from(value: TxVersion) -> Self { + Self(value.0) + } +} diff --git a/crates/brk_indexer/src/structs/vin.rs b/crates/brk_indexer/src/structs/vin.rs new file mode 100644 index 000000000..a26e0cfa1 --- /dev/null +++ b/crates/brk_indexer/src/structs/vin.rs @@ -0,0 +1,31 @@ +use derive_deref::Deref; + +#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Vin(u32); + +impl Vin { + pub const ZERO: Self = Vin(0); + pub const ONE: Self = Vin(1); + + pub fn is_zero(&self) -> bool { + *self == Self::ZERO + } +} + +impl From for Vin { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for Vin { + fn from(value: usize) -> Self { + Self(value as u32) + } +} + +impl From for u64 { + fn from(value: Vin) -> Self { + value.0 as u64 + } +} diff --git a/crates/brk_indexer/src/structs/vout.rs b/crates/brk_indexer/src/structs/vout.rs new file mode 100644 index 000000000..4cc8ef793 --- /dev/null +++ b/crates/brk_indexer/src/structs/vout.rs @@ -0,0 +1,30 @@ +use derive_deref::Deref; + +#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Vout(u32); + +impl Vout { + const ZERO: Self = Vout(0); + + pub fn is_zero(&self) -> bool { + *self == Self::ZERO + } +} + +impl From for Vout { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for Vout { + fn from(value: usize) -> Self { + Self(value as u32) + } +} + +impl From for u64 { + fn from(value: Vout) -> Self { + value.0 as u64 + } +} diff --git a/crates/brk_indexer/src/structs/weight.rs b/crates/brk_indexer/src/structs/weight.rs new file mode 100644 index 000000000..0b086e94d --- /dev/null +++ b/crates/brk_indexer/src/structs/weight.rs @@ -0,0 +1,18 @@ +use derive_deref::Deref; +use serde::Serialize; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Debug, Deref, Clone, Copy, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize)] +pub struct Weight(u64); + +impl From for Weight { + fn from(value: bitcoin::Weight) -> Self { + Self(value.to_wu()) + } +} + +impl From for bitcoin::Weight { + fn from(value: Weight) -> Self { + Self::from_wu(*value) + } +} diff --git a/crates/brk_logger/Cargo.toml b/crates/brk_logger/Cargo.toml new file mode 100644 index 000000000..d598f3fc2 --- /dev/null +++ b/crates/brk_logger/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "brk_logger" +description = "A clean logger used in the Bitcoin Research Kit" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +color-eyre = { workspace = true } +env_logger = "0.11.6" +jiff = { workspace = true } diff --git a/crates/brk_logger/src/lib.rs b/crates/brk_logger/src/lib.rs new file mode 100644 index 000000000..37540bb7e --- /dev/null +++ b/crates/brk_logger/src/lib.rs @@ -0,0 +1,61 @@ +use std::{ + fmt::Display, + fs::{self, OpenOptions}, + io::Write, + path::Path, +}; + +use color_eyre::owo_colors::OwoColorize; +use env_logger::{Builder, Env}; +use jiff::{Timestamp, tz}; + +#[inline(always)] +pub fn init(path: Option<&Path>) { + let file = path.map(|path| { + let _ = fs::remove_file(path); + OpenOptions::new().create(true).append(true).open(path).unwrap() + }); + + Builder::from_env(Env::default().default_filter_or("info,fjall=off,lsm_tree=off")) + .format(move |buf, record| { + let date_time = Timestamp::now() + .to_zoned(tz::TimeZone::system()) + .strftime("%Y-%m-%d %H:%M:%S") + .to_string(); + let level = record.level().as_str().to_lowercase(); + let level = format!("{:5}", level); + let target = record.target(); + let dash = "-"; + let args = record.args(); + + if let Some(file) = file.as_ref() { + let _ = write(file.try_clone().unwrap(), &date_time, target, &level, dash, args); + } + + let colored_date_time = date_time.bright_black(); + let colored_level = match level.chars().next().unwrap() { + 'e' => level.red().to_string(), + 'w' => level.yellow().to_string(), + 'i' => level.green().to_string(), + 'd' => level.blue().to_string(), + 't' => level.cyan().to_string(), + _ => panic!(), + }; + let colored_dash = dash.bright_black(); + + write(buf, colored_date_time, target, colored_level, colored_dash, args) + }) + .init(); +} + +fn write( + mut buf: impl Write, + date_time: impl Display, + _target: impl Display, + level: impl Display, + dash: impl Display, + args: impl Display, +) -> Result<(), std::io::Error> { + writeln!(buf, "{} {} {} {}", date_time, dash, level, args) + // writeln!(buf, "{} {} {} {} {}", date_time, _target, level, dash, args) +} diff --git a/crates/brk_logger/src/main.rs b/crates/brk_logger/src/main.rs new file mode 100644 index 000000000..d2a6728ea --- /dev/null +++ b/crates/brk_logger/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + brk_logger::init(None); +} diff --git a/crates/brk_parser/CHANGELOG.md b/crates/brk_parser/CHANGELOG.md new file mode 100644 index 000000000..9b9c5b195 --- /dev/null +++ b/crates/brk_parser/CHANGELOG.md @@ -0,0 +1,12 @@ +# v0.2.1 + +- Clean `.json` if necessary +- Only save `.json` if needed +- Updated benchmarks +- Updated packages + +# v0.2.0 + +- Removed the need for an output directory path +- Changed the location of the saved json file from the previously needed output directory path to the Bitcoin data directory +- Added a save of the json file every 144 * 30 blocks instead of only at the end diff --git a/biter/Cargo.lock b/crates/brk_parser/Cargo.lock similarity index 84% rename from biter/Cargo.lock rename to crates/brk_parser/Cargo.lock index e178bb13f..c3335d7d9 100644 --- a/biter/Cargo.lock +++ b/crates/brk_parser/Cargo.lock @@ -1,12 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "base58ck" @@ -32,9 +32,9 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bitcoin" -version = "0.32.2" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" +checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ "base58ck", "bech32", @@ -110,7 +110,7 @@ dependencies = [ [[package]] name = "biter" -version = "0.1.1" +version = "0.2.2" dependencies = [ "bitcoin", "bitcoincore-rpc", @@ -129,9 +129,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.6" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -240,9 +243,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jsonrpc" @@ -258,9 +261,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "log" @@ -287,27 +290,27 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -370,9 +373,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "secp256k1" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", "rand", @@ -382,27 +385,27 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] [[package]] name = "serde" -version = "1.0.204" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -411,9 +414,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -422,10 +425,16 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.72" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -434,9 +443,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "wasi" @@ -446,9 +455,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zerocopy" -version = "0.6.6" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", @@ -456,9 +465,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.6.6" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", diff --git a/crates/brk_parser/Cargo.toml b/crates/brk_parser/Cargo.toml new file mode 100644 index 000000000..772740f35 --- /dev/null +++ b/crates/brk_parser/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "brk_parser" +description = "A very fast Bitcoin Core block parser and iterator built on top of bitcoin-rust" +repository = "https://github.com/kibo-money/kibo/tree/main/src/crates/biter" +keywords = ["bitcoin", "block", "iterator"] +categories = ["cryptography::cryptocurrencies", "encoding"] +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +bitcoin = { workspace = true } +bitcoincore-rpc = "0.19.0" +byteview = { workspace = true } +crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } +derive_deref = { workspace = true } +rayon = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +zerocopy = { workspace = true } diff --git a/biter/LICENSE.md b/crates/brk_parser/LICENSE.md similarity index 97% rename from biter/LICENSE.md rename to crates/brk_parser/LICENSE.md index 690c7b5da..3fd127e58 100644 --- a/biter/LICENSE.md +++ b/crates/brk_parser/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Biter +Copyright (c) 2024 biter Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/brk_parser/README.md b/crates/brk_parser/README.md new file mode 100644 index 000000000..d66ae4cc8 --- /dev/null +++ b/crates/brk_parser/README.md @@ -0,0 +1,29 @@ +# biter + +Biter (Bitcoin Block Iterator) is a very fast and simple Rust library which reads raw block files (*blkXXXXX.dat*) from Bitcoin Core Node and creates an iterator over all the requested blocks in sequential order (0, 1, 2, ...). + +The element returned by the iterator is a tuple which includes the: +- Height: `usize` +- Block: `Block` (from `bitcoin-rust`) +- Block's Hash: `BlockHash` (also from `bitcoin-rust`) + +## Example + +`src/main.rs` + +## Requirements + +Even though it reads *blkXXXXX.dat* files, it **needs** `bitcoind` to run with the RPC server to filter out block forks. + +Peak memory should be around 500MB. + +## Comparaison + +| | [biter](https://crates.io/crates/biter) | [bitcoin-explorer (deprecated)](https://crates.io/crates/bitcoin-explorer) | [blocks_iterator](https://crates.io/crates/blocks_iterator) | +| --- | --- | --- | --- | +| Runs **with** `bitcoind` | Yes ✅ | No ❌ | Yes ✅ | +| Runs **without** `bitcoind` | No ❌ | Yes ✅ | Yes ✅ | +| `0..=855_000` | 4mn 10s | 4mn 45s | > 2h | +| `800_000..=855_000` | 0mn 52s (4mn 10s if first run) | 0mn 55s | > 2h | + +*Benchmarked on a Macbook Pro M3 Pro* diff --git a/crates/brk_parser/src/blk_index_to_blk_path.rs b/crates/brk_parser/src/blk_index_to_blk_path.rs new file mode 100644 index 000000000..8c95acb7e --- /dev/null +++ b/crates/brk_parser/src/blk_index_to_blk_path.rs @@ -0,0 +1,46 @@ +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, +}; + +use derive_deref::{Deref, DerefMut}; + +const BLK: &str = "blk"; +const DAT: &str = ".dat"; + +#[derive(Debug, Deref, DerefMut)] +pub struct BlkIndexToBlkPath(BTreeMap); + +impl BlkIndexToBlkPath { + pub fn scan(data_dir: &Path) -> Self { + let blocks_dir = data_dir.join("blocks"); + + Self( + fs::read_dir(blocks_dir) + .unwrap() + .map(|entry| entry.unwrap().path()) + .filter(|path| { + let is_file = path.is_file(); + + if is_file { + let file_name = path.file_name().unwrap().to_str().unwrap(); + + file_name.starts_with(BLK) && file_name.ends_with(DAT) + } else { + false + } + }) + .map(|path| { + let file_name = path.file_name().unwrap().to_str().unwrap(); + + let blk_index = file_name[BLK.len()..(file_name.len() - DAT.len())] + .parse::() + .unwrap(); + + (blk_index, path) + }) + .collect::>(), + ) + } +} diff --git a/crates/brk_parser/src/blk_index_to_blk_recap.rs b/crates/brk_parser/src/blk_index_to_blk_recap.rs new file mode 100644 index 000000000..f95ef13ae --- /dev/null +++ b/crates/brk_parser/src/blk_index_to_blk_recap.rs @@ -0,0 +1,95 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + fs::File, + io::{BufReader, BufWriter}, + path::{Path, PathBuf}, +}; + +use crate::{BlkIndexToBlkPath, Height, blk_recap::BlkRecap}; + +#[derive(Debug)] +pub struct BlkIndexToBlkRecap { + pub path: PathBuf, + pub tree: BTreeMap, +} + +impl BlkIndexToBlkRecap { + pub fn import(data_dir: &Path, blk_index_to_blk_path: &BlkIndexToBlkPath, start: Option) -> (Self, u16) { + let path = data_dir.join("blk_index_to_blk_recap.json"); + + let tree = { + if let Ok(file) = File::open(&path) { + let reader = BufReader::new(file); + serde_json::from_reader(reader).unwrap_or_default() + } else { + BTreeMap::default() + } + }; + + let mut slf = Self { path, tree }; + + let min_removed = slf.clean_outdated(blk_index_to_blk_path); + + let blk_index = slf.get_start_recap(min_removed, start); + + (slf, blk_index) + } + + fn clean_outdated(&mut self, blk_index_to_blk_path: &BlkIndexToBlkPath) -> Option { + let mut min_removed_blk_index: Option = None; + + let mut unprocessed_keys = self.tree.keys().copied().collect::>(); + + blk_index_to_blk_path.iter().for_each(|(blk_index, blk_path)| { + unprocessed_keys.remove(blk_index); + if let Some(blk_recap) = self.tree.get(blk_index) { + if blk_recap.has_different_modified_time(blk_path) { + self.tree.remove(blk_index).unwrap(); + if min_removed_blk_index.is_none_or(|_blk_index| *blk_index < _blk_index) { + min_removed_blk_index.replace(*blk_index); + } + } + } + }); + + unprocessed_keys.into_iter().for_each(|blk_index| { + self.tree.remove(&blk_index).unwrap(); + if min_removed_blk_index.is_none_or(|_blk_index| blk_index < _blk_index) { + min_removed_blk_index.replace(blk_index); + } + }); + + min_removed_blk_index + } + + pub fn get_start_recap(&mut self, min_removed: Option, start: Option) -> u16 { + if start.is_none() { + return 0; + } + + let height = start.unwrap(); + + let mut start = None; + + if let Some(found) = self.tree.iter().find(|(_, recap)| recap.max_height >= height) { + start = Some(*found.0); + } + + if let Some(min_removed) = min_removed { + if start.is_none_or(|start| start > min_removed) { + start = Some(min_removed); + } + } + + start.unwrap() + } + + pub fn export(&self) { + let file = File::create(&self.path).unwrap_or_else(|_| { + dbg!(&self.path); + panic!("No such file or directory") + }); + + serde_json::to_writer(&mut BufWriter::new(file), &self.tree).unwrap(); + } +} diff --git a/biter/src/blk_metadata.rs b/crates/brk_parser/src/blk_metadata.rs similarity index 63% rename from biter/src/blk_metadata.rs rename to crates/brk_parser/src/blk_metadata.rs index d9322a8ab..2d8614b6b 100644 --- a/biter/src/blk_metadata.rs +++ b/crates/brk_parser/src/blk_metadata.rs @@ -1,15 +1,15 @@ -use std::path::PathBuf; +use std::path::Path; use crate::path_to_modified_time; -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct BlkMetadata { - pub index: usize, + pub index: u16, pub modified_time: u64, } impl BlkMetadata { - pub fn new(index: usize, path: &PathBuf) -> Self { + pub fn new(index: u16, path: &Path) -> Self { Self { index, modified_time: path_to_modified_time(path), diff --git a/crates/brk_parser/src/blk_recap.rs b/crates/brk_parser/src/blk_recap.rs new file mode 100644 index 000000000..d7c8e8ca6 --- /dev/null +++ b/crates/brk_parser/src/blk_recap.rs @@ -0,0 +1,18 @@ +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use crate::{Height, path_to_modified_time}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[repr(C)] +pub struct BlkRecap { + pub max_height: Height, + pub modified_time: u64, +} + +impl BlkRecap { + pub fn has_different_modified_time(&self, blk_path: &Path) -> bool { + self.modified_time != path_to_modified_time(blk_path) + } +} diff --git a/crates/brk_parser/src/block_state.rs b/crates/brk_parser/src/block_state.rs new file mode 100644 index 000000000..32e13ddea --- /dev/null +++ b/crates/brk_parser/src/block_state.rs @@ -0,0 +1,25 @@ +use bitcoin::{Block, consensus::Decodable, io::Cursor}; + +use crate::{XORBytes, XORIndex}; + +pub enum BlockState { + Raw(Vec), + Decoded(Block), +} + +impl BlockState { + pub fn decode(&mut self, xor_i: &mut XORIndex, xor_bytes: &XORBytes) { + let bytes = match self { + BlockState::Raw(bytes) => bytes, + _ => unreachable!(), + }; + + xor_i.bytes(bytes.as_mut_slice(), xor_bytes); + + let mut cursor = Cursor::new(bytes); + + let block = Block::consensus_decode(&mut cursor).unwrap(); + + *self = BlockState::Decoded(block); + } +} diff --git a/crates/brk_parser/src/error.rs b/crates/brk_parser/src/error.rs new file mode 100644 index 000000000..0a7562c65 --- /dev/null +++ b/crates/brk_parser/src/error.rs @@ -0,0 +1,35 @@ +use std::{ + fmt::{self, Debug}, + io, +}; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + IO(io::Error), + ZeroCopyError, +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::IO(value) + } +} + +impl From> for Error { + fn from(_: zerocopy::error::SizeError) -> Self { + Self::ZeroCopyError + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::IO(error) => Debug::fmt(&error, f), + Error::ZeroCopyError => write!(f, "Zero copy convert error"), + } + } +} + +impl std::error::Error for Error {} diff --git a/crates/brk_parser/src/height.rs b/crates/brk_parser/src/height.rs new file mode 100644 index 000000000..c81c0bc3b --- /dev/null +++ b/crates/brk_parser/src/height.rs @@ -0,0 +1,205 @@ +use std::{ + fmt::{self, Debug}, + ops::{Add, AddAssign, Rem, Sub}, +}; + +use derive_deref::{Deref, DerefMut}; +use serde::{Deserialize, Serialize}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::rpc::{self, RpcApi}; + +#[derive( + Debug, + Clone, + Copy, + Deref, + DerefMut, + PartialEq, + Eq, + PartialOrd, + Ord, + Default, + Serialize, + Deserialize, + FromBytes, + Immutable, + IntoBytes, + KnownLayout, +)] +pub struct Height(u32); + +impl Height { + pub const ZERO: Self = Height(0); + pub const MAX: Self = Height(u32::MAX); + + pub fn write(&self, path: &std::path::Path) -> Result<(), std::io::Error> { + std::fs::write(path, self.as_bytes()) + } + + pub fn increment(&mut self) { + self.0 += 1; + } + + pub fn incremented(self) -> Self { + Self(self.0 + 1) + } + + pub fn decrement(&mut self) { + self.0 -= 1; + } + + pub fn decremented(self) -> Self { + Self(self.0.checked_sub(1).unwrap_or_default()) + } + + pub fn is_zero(self) -> bool { + self == Self::ZERO + } +} + +impl PartialEq for Height { + fn eq(&self, other: &u64) -> bool { + **self == *other as u32 + } +} + +impl Add for Height { + type Output = Height; + + fn add(self, rhs: Height) -> Self::Output { + Self::from(self.0 + rhs.0) + } +} + +impl Add for Height { + type Output = Height; + + fn add(self, rhs: u32) -> Self::Output { + Self::from(self.0 + rhs) + } +} + +impl Add for Height { + type Output = Height; + + fn add(self, rhs: usize) -> Self::Output { + Self::from(*self + rhs as u32) + } +} + +impl Sub for Height { + type Output = Height; + fn sub(self, rhs: Height) -> Self::Output { + Self::from(*self - *rhs) + } +} + +impl Sub for Height { + type Output = Height; + fn sub(self, rhs: i32) -> Self::Output { + Self::from(*self - rhs as u32) + } +} + +impl Sub for Height { + type Output = Height; + fn sub(self, rhs: u32) -> Self::Output { + Self::from(*self - rhs) + } +} + +impl Sub for Height { + type Output = Height; + fn sub(self, rhs: usize) -> Self::Output { + Self::from(*self - rhs as u32) + } +} + +impl AddAssign for Height { + fn add_assign(&mut self, rhs: usize) { + *self = self.add(rhs); + } +} + +impl Rem for Height { + type Output = Height; + fn rem(self, rhs: Height) -> Self::Output { + Self(self.0.rem(rhs.0)) + } +} + +impl Rem for Height { + type Output = Height; + fn rem(self, rhs: usize) -> Self::Output { + Self(self.0.rem(Height::from(rhs).0)) + } +} + +impl fmt::Display for Height { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", **self) + } +} + +impl From for Height { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for Height { + fn from(value: usize) -> Self { + Self(value as u32) + } +} +impl From for usize { + fn from(value: Height) -> Self { + value.0 as usize + } +} + +impl From for u64 { + fn from(value: Height) -> Self { + value.0 as u64 + } +} + +impl TryFrom<&rpc::Client> for Height { + type Error = rpc::Error; + fn try_from(value: &rpc::Client) -> Result { + Ok((value.get_blockchain_info()?.blocks as usize - 1).into()) + } +} + +impl From for Height { + fn from(value: bitcoin::locktime::absolute::Height) -> Self { + Self(value.to_consensus_u32()) + } +} + +impl From for bitcoin::locktime::absolute::Height { + fn from(value: Height) -> Self { + bitcoin::locktime::absolute::Height::from_consensus(*value).unwrap() + } +} + +impl TryFrom<&std::path::Path> for Height { + type Error = crate::Error; + fn try_from(value: &std::path::Path) -> Result { + Ok(Self::read_from_bytes(std::fs::read(value)?.as_slice())?.to_owned()) + } +} + +impl TryFrom for Height { + type Error = crate::Error; + fn try_from(value: byteview::ByteView) -> Result { + Ok(Self::read_from_bytes(&value)?) + } +} + +impl From for byteview::ByteView { + fn from(value: Height) -> Self { + Self::new(value.as_bytes()) + } +} diff --git a/crates/brk_parser/src/lib.rs b/crates/brk_parser/src/lib.rs new file mode 100644 index 000000000..1ad841f91 --- /dev/null +++ b/crates/brk_parser/src/lib.rs @@ -0,0 +1,256 @@ +use std::{ + cmp::Ordering, + collections::BTreeMap, + fs::{self}, + ops::ControlFlow, + path::{Path, PathBuf}, + thread, +}; + +use bitcoin::{Block, BlockHash}; +use bitcoincore_rpc::RpcApi; +use blk_index_to_blk_path::*; +use blk_recap::BlkRecap; +use crossbeam::channel::{Receiver, bounded}; +use rayon::prelude::*; + +pub use bitcoin; +pub use bitcoincore_rpc as rpc; + +mod blk_index_to_blk_path; +mod blk_index_to_blk_recap; +mod blk_metadata; +mod blk_recap; +mod block_state; +mod error; +mod height; +mod utils; +mod xor_bytes; +mod xor_index; + +use blk_index_to_blk_recap::*; +use blk_metadata::*; +use block_state::*; +pub use error::*; +pub use height::*; +use utils::*; +use xor_bytes::*; +use xor_index::*; + +pub const NUMBER_OF_UNSAFE_BLOCKS: usize = 1000; + +const MAGIC_BYTES: [u8; 4] = [249, 190, 180, 217]; +const BOUND_CAP: usize = 50; + +pub struct Parser { + data_dir: PathBuf, + rpc: &'static bitcoincore_rpc::Client, +} + +impl Parser { + pub fn new(data_dir: &Path, rpc: &'static bitcoincore_rpc::Client) -> Self { + Self { + data_dir: data_dir.to_owned(), + rpc, + } + } + + /// + /// Returns a crossbeam channel receiver that receives `(Height, Block, BlockHash)` tuples from an **inclusive** range (`start` and `end`) + /// + /// For an example checkout `./main.rs` + /// + pub fn parse(&self, start: Option, end: Option) -> Receiver<(Height, Block, BlockHash)> { + let data_dir = self.data_dir.as_path(); + let rpc = self.rpc; + + let (send_bytes, recv_bytes) = bounded(BOUND_CAP); + let (send_block, recv_block) = bounded(BOUND_CAP); + let (send_height_block_hash, recv_height_block_hash) = bounded(BOUND_CAP); + + let blk_index_to_blk_path = BlkIndexToBlkPath::scan(data_dir); + + let (mut blk_index_to_blk_recap, blk_index) = + BlkIndexToBlkRecap::import(data_dir, &blk_index_to_blk_path, start); + + let xor_bytes = XORBytes::from(data_dir); + + thread::spawn(move || { + let xor_bytes = xor_bytes; + + blk_index_to_blk_path + .range(blk_index..) + .try_for_each(move |(blk_index, blk_path)| { + let mut xor_i = XORIndex::default(); + + let blk_index = *blk_index; + + let blk_metadata = BlkMetadata::new(blk_index, blk_path.as_path()); + + let mut blk_bytes_ = fs::read(blk_path).unwrap(); + let blk_bytes = blk_bytes_.as_mut_slice(); + let blk_bytes_len = blk_bytes.len(); + + let mut current_4bytes = [0; 4]; + + let mut i = 0; + + 'parent: loop { + loop { + if i == blk_bytes_len { + break 'parent; + } + + current_4bytes.rotate_left(1); + + current_4bytes[3] = xor_i.byte(blk_bytes[i], &xor_bytes); + i += 1; + + if current_4bytes == MAGIC_BYTES { + break; + } + } + + let len = + u32::from_le_bytes(xor_i.bytes(&mut blk_bytes[i..(i + 4)], &xor_bytes).try_into().unwrap()) + as usize; + i += 4; + + let block_bytes = (blk_bytes[i..(i + len)]).to_vec(); + + if send_bytes + .send((blk_metadata, BlockState::Raw(block_bytes), xor_i)) + .is_err() + { + return ControlFlow::Break(()); + } + + i += len; + xor_i.add_assign(len); + } + + ControlFlow::Continue(()) + }); + }); + + thread::spawn(move || { + let xor_bytes = xor_bytes; + + let mut bulk = vec![]; + + let drain_and_send = |bulk: &mut Vec<_>| { + // Using a vec and sending after to not end up with stuck threads in par iter + bulk.par_iter_mut().for_each(|(_, block_state, xor_i)| { + BlockState::decode(block_state, xor_i, &xor_bytes); + }); + + bulk.drain(..).try_for_each(|(blk_metadata, block_state, _)| { + let block = match block_state { + BlockState::Decoded(block) => block, + _ => unreachable!(), + }; + + if send_block.send((blk_metadata, block)).is_err() { + return ControlFlow::Break(()); + } + + ControlFlow::Continue(()) + }) + }; + + recv_bytes.iter().try_for_each(|tuple| { + bulk.push(tuple); + + if bulk.len() < BOUND_CAP / 2 { + return ControlFlow::Continue(()); + } + + // Sending in bulk to not lock threads in standby + drain_and_send(&mut bulk) + }); + + drain_and_send(&mut bulk) + }); + + thread::spawn(move || { + let mut current_height = start.unwrap_or_default(); + + let mut future_blocks = BTreeMap::default(); + + recv_block + .iter() + .try_for_each(|(blk_metadata, block)| -> ControlFlow<(), _> { + let hash = block.block_hash(); + let header = rpc.get_block_header_info(&hash); + + if header.is_err() { + return ControlFlow::Continue(()); + } + let header = header.unwrap(); + if header.confirmations <= 0 { + return ControlFlow::Continue(()); + } + + let height = Height::from(header.height); + // println!("{height}"); + + let len = blk_index_to_blk_recap.tree.len(); + if blk_metadata.index == len as u16 || blk_metadata.index + 1 == len as u16 { + match (len as u16).cmp(&blk_metadata.index) { + Ordering::Equal => { + if len % 21 == 0 { + blk_index_to_blk_recap.export(); + } + } + Ordering::Less => panic!(), + Ordering::Greater => {} + } + + blk_index_to_blk_recap + .tree + .entry(blk_metadata.index) + .and_modify(|recap| { + if recap.max_height < height { + recap.max_height = height; + } + }) + .or_insert(BlkRecap { + max_height: height, + modified_time: blk_metadata.modified_time, + }); + } + + let mut opt = if current_height == height { + Some((block, hash)) + } else { + if start.is_none_or(|start| start <= height) && end.is_none_or(|end| end >= height) { + future_blocks.insert(height, (block, hash)); + } + None + }; + + while let Some((block, hash)) = opt.take().or_else(|| { + if !future_blocks.is_empty() { + future_blocks.remove(¤t_height) + } else { + None + } + }) { + send_height_block_hash.send((current_height, block, hash)).unwrap(); + + if end == Some(current_height) { + return ControlFlow::Break(()); + } + + current_height.increment(); + } + + ControlFlow::Continue(()) + }); + + blk_index_to_blk_recap.export(); + }); + + recv_height_block_hash + } +} diff --git a/crates/brk_parser/src/main.rs b/crates/brk_parser/src/main.rs new file mode 100644 index 000000000..3e820a0bd --- /dev/null +++ b/crates/brk_parser/src/main.rs @@ -0,0 +1,28 @@ +use std::path::Path; + +use bitcoincore_rpc::{Auth, Client}; +use brk_parser::Parser; + +fn main() { + let i = std::time::Instant::now(); + + let data_dir = Path::new("../../../bitcoin"); + let rpc = Box::leak(Box::new( + Client::new( + "http://localhost:8332", + Auth::CookieFile(Path::new(data_dir).join(".cookie")), + ) + .unwrap(), + )); + + let start = None; + let end = None; + + let parser = Parser::new(data_dir, rpc); + + parser.parse(start, end).iter().for_each(|(height, _block, hash)| { + println!("{height}: {hash}"); + }); + + dbg!(i.elapsed()); +} diff --git a/crates/brk_parser/src/utils.rs b/crates/brk_parser/src/utils.rs new file mode 100644 index 000000000..0b73fc981 --- /dev/null +++ b/crates/brk_parser/src/utils.rs @@ -0,0 +1,11 @@ +use std::{fs, path::Path, time::UNIX_EPOCH}; + +pub fn path_to_modified_time(path: &Path) -> u64 { + fs::metadata(path) + .unwrap() + .modified() + .unwrap() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} diff --git a/crates/brk_parser/src/xor_bytes.rs b/crates/brk_parser/src/xor_bytes.rs new file mode 100644 index 000000000..9b900f534 --- /dev/null +++ b/crates/brk_parser/src/xor_bytes.rs @@ -0,0 +1,19 @@ +use std::{fs, path::Path}; + +use derive_deref::Deref; + +pub const XOR_LEN: usize = 8; + +#[derive(Debug, Clone, Copy, Deref)] +pub struct XORBytes([u8; XOR_LEN]); + +impl From<&Path> for XORBytes { + fn from(value: &Path) -> Self { + Self( + fs::read(value.join("blocks/xor.dat")) + .unwrap_or(vec![0; 8]) + .try_into() + .unwrap(), + ) + } +} diff --git a/crates/brk_parser/src/xor_index.rs b/crates/brk_parser/src/xor_index.rs new file mode 100644 index 000000000..81b79498d --- /dev/null +++ b/crates/brk_parser/src/xor_index.rs @@ -0,0 +1,39 @@ +use crate::xor_bytes::{XOR_LEN, XORBytes}; + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub struct XORIndex(usize); + +impl XORIndex { + pub fn bytes<'a>(&mut self, bytes: &'a mut [u8], xor_bytes: &XORBytes) -> &'a mut [u8] { + let len = bytes.len(); + let mut bytes_index = 0; + + while bytes_index < len { + bytes[bytes_index] ^= xor_bytes[self.0]; + self.increment(); + bytes_index += 1; + } + + bytes + } + + #[inline(always)] + pub fn byte(&mut self, mut byte: u8, xor_bytes: &XORBytes) -> u8 { + byte ^= xor_bytes[self.0]; + self.increment(); + byte + } + + #[inline(always)] + pub fn increment(&mut self) { + self.0 += 1; + if self.0 == XOR_LEN { + self.0 = 0; + } + } + + #[inline(always)] + pub fn add_assign(&mut self, i: usize) { + self.0 = (self.0 + i) % XOR_LEN; + } +} diff --git a/crates/brk_server/Cargo.toml b/crates/brk_server/Cargo.toml new file mode 100644 index 000000000..6e1ffb612 --- /dev/null +++ b/crates/brk_server/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "brk_server" +description = "A server that serves Bitcoin data and swappable front-ends, built on top of brk_indexer, brk_fetcher and brk_computer" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +axum = "0.8.1" +brk_computer = { workspace = true } +brk_indexer = { workspace = true } +brk_logger = { workspace = true } +color-eyre = { workspace = true } +derive_deref = { workspace = true } +jiff = { workspace = true } +log = { workspace = true } +oxc = { version = "0.52.0", features = ["codegen", "minifier"] } +serde = { workspace = true } +serde_json = { workspace = true } +storable_vec = { workspace = true } +tokio = { version = "1.43.0", features = ["full"] } +tower-http = { version = "0.6.2", features = ["compression-full"] } diff --git a/website/styles/moscow-time.css b/crates/brk_server/src/api/explorer/mod.rs similarity index 100% rename from website/styles/moscow-time.css rename to crates/brk_server/src/api/explorer/mod.rs diff --git a/crates/brk_server/src/api/mod.rs b/crates/brk_server/src/api/mod.rs new file mode 100644 index 000000000..4f9025ec4 --- /dev/null +++ b/crates/brk_server/src/api/mod.rs @@ -0,0 +1,18 @@ +use axum::{routing::get, Router}; + +use super::AppState; + +mod explorer; +mod vecs; + +pub use vecs::VecIdToIndexToVec; + +pub trait ApiRoutes { + fn add_api_routes(self) -> Self; +} + +impl ApiRoutes for Router { + fn add_api_routes(self) -> Self { + self.route("/api/vecs", get(vecs::handler)) + } +} diff --git a/crates/brk_server/src/api/vecs/format.rs b/crates/brk_server/src/api/vecs/format.rs new file mode 100644 index 000000000..1877c78d1 --- /dev/null +++ b/crates/brk_server/src/api/vecs/format.rs @@ -0,0 +1,30 @@ +use color_eyre::eyre::eyre; + +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Format { + CSV, + TSV, + JSON, +} + +impl TryFrom> for Format { + type Error = color_eyre::Report; + fn try_from(value: Option) -> Result { + if let Some(value) = value { + let value = value.to_lowercase(); + let value = value.as_str(); + if value == "csv" { + Ok(Self::CSV) + } else if value == "tsv" { + Ok(Self::TSV) + } else if value == "json" { + Ok(Self::JSON) + } else { + Err(eyre!("Fail")) + } + } else { + Err(eyre!("Fail")) + } + } +} diff --git a/crates/brk_server/src/api/vecs/index.rs b/crates/brk_server/src/api/vecs/index.rs new file mode 100644 index 000000000..ab439a075 --- /dev/null +++ b/crates/brk_server/src/api/vecs/index.rs @@ -0,0 +1,66 @@ +use std::fmt::{self, Debug}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Index { + Addressindex, + Dateindex, + Height, + P2PK33index, + P2PK65index, + P2PKHindex, + P2SHindex, + P2TRindex, + P2WPKHindex, + P2WSHindex, + Txindex, + Txinindex, + Txoutindex, +} + +impl Index { + pub fn all() -> [Self; 13] { + [ + Self::Addressindex, + Self::Dateindex, + Self::Height, + Self::P2PK33index, + Self::P2PK65index, + Self::P2PKHindex, + Self::P2SHindex, + Self::P2TRindex, + Self::P2WPKHindex, + Self::P2WSHindex, + Self::Txindex, + Self::Txinindex, + Self::Txoutindex, + ] + } +} + +impl TryFrom<&str> for Index { + type Error = (); + fn try_from(value: &str) -> Result { + Ok(match value { + "d" | "date" | "dateindex" => Self::Dateindex, + "h" | "height" => Self::Height, + "txi" | "txindex" => Self::Txindex, + "txini" | "txinindex" => Self::Txinindex, + "txouti" | "txoutindex" => Self::Txoutindex, + "addri" | "addressindex" => Self::Addressindex, + "p2pk33i" | "p2pk33index" => Self::P2PK33index, + "p2pk65i" | "p2pk65index" => Self::P2PK65index, + "p2pkhi" | "p2pkhindex" => Self::P2PKHindex, + "p2shi" | "p2shindex" => Self::P2SHindex, + "p2tri" | "p2trindex" => Self::P2TRindex, + "p2wpkhi" | "p2wpkhindex" => Self::P2WPKHindex, + "p2wshi" | "p2wshindex" => Self::P2WSHindex, + _ => return Err(()), + }) + } +} + +impl fmt::Display for Index { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(self, f) + } +} diff --git a/crates/brk_server/src/api/vecs/mod.rs b/crates/brk_server/src/api/vecs/mod.rs new file mode 100644 index 000000000..baa3200db --- /dev/null +++ b/crates/brk_server/src/api/vecs/mod.rs @@ -0,0 +1,155 @@ +use std::time::Instant; + +use axum::{ + extract::{Query, State}, + http::{HeaderMap, StatusCode, Uri}, + response::{IntoResponse, Response}, + Json, +}; +use color_eyre::eyre::eyre; +use serde_json::Value; + +use crate::{log_result, traits::HeaderMapExtended}; + +use super::AppState; + +mod format; +mod index; +mod query; +mod tree; + +use format::Format; +use index::Index; +use query::QueryS; +pub use tree::*; + +pub async fn handler( + headers: HeaderMap, + uri: Uri, + query: Query, + State(app_state): State, +) -> Response { + let instant = Instant::now(); + + let path = uri.path(); + + match req_to_response_res(headers, query, app_state) { + Ok(response) => { + log_result(response.status(), path, instant); + response + } + Err(error) => { + let mut response = (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(); + log_result(response.status(), path, instant); + response.headers_mut().insert_cors(); + response + } + } +} + +fn req_to_response_res( + headers: HeaderMap, + Query(QueryS { format, from, i, to, v }): Query, + AppState { vecs, .. }: AppState, +) -> color_eyre::Result { + let format = Format::try_from(format).ok(); + + let indexes = i + .to_lowercase() + .split(",") + .flat_map(|s| Index::try_from(s).ok()) + .collect::>(); + + if indexes.len() > 1 { + return Err(eyre!("Multiple indexes aren't supported")); + } else if indexes.is_empty() { + return Err(eyre!("Unknown index")); + } + + let ids = v + .to_lowercase() + .split(",") + .map(|s| (s.to_owned(), vecs.get(&s.replace("_", "-")))) + .filter(|(_, opt)| opt.is_some()) + .map(|(id, vec)| (id, vec.unwrap())) + .collect::>(); + + if ids.is_empty() { + return Ok(Json(()).into_response()); + } + + let values = ids + .iter() + .flat_map(|(_, i_to_v)| i_to_v.get(indexes.first().unwrap())) + .map(|vec| -> storable_vec::Result> { vec.collect_range_values(from, to) }) + .collect::>>()?; + + if ids.is_empty() { + return Ok(Json(()).into_response()); + } + + let ids_last_i = ids.len() - 1; + + let mut response = match format { + Some(Format::CSV) | Some(Format::TSV) => { + let delimiter = if format == Some(Format::CSV) { ',' } else { '\t' }; + + let mut csv = ids + .into_iter() + .map(|(id, _)| id) + .collect::>() + .join(&delimiter.to_string()); + + csv.push('\n'); + + let values_len = values.first().unwrap().len(); + + (0..values_len).for_each(|i| { + let mut line = "".to_string(); + values.iter().enumerate().for_each(|(id_i, v)| { + line += &v.get(i).unwrap().to_string(); + if id_i == ids_last_i { + line.push('\n'); + } else { + line.push(delimiter); + } + }); + csv += &line; + }); + + csv.into_response() + } + Some(Format::JSON) | None => { + if values.len() == 1 { + let values = values.first().unwrap(); + if values.len() == 1 { + let value = values.first().unwrap(); + Json(value).into_response() + } else { + Json(values).into_response() + } + } else { + Json(values).into_response() + } + } + }; + + let headers = response.headers_mut(); + + headers.insert_cors(); + // headers.insert_last_modified(date_modified); + + match format { + Some(format) => { + headers.insert_content_disposition_attachment(); + match format { + Format::CSV => headers.insert_content_type_text_csv(), + Format::TSV => headers.insert_content_type_text_tsv(), + Format::JSON => headers.insert_content_type_application_json(), + } + } + _ => headers.insert_content_type_application_json(), + }; + + Ok(response) +} diff --git a/crates/brk_server/src/api/vecs/query.rs b/crates/brk_server/src/api/vecs/query.rs new file mode 100644 index 000000000..6ad5db15e --- /dev/null +++ b/crates/brk_server/src/api/vecs/query.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct QueryS { + pub i: String, + pub v: String, + pub from: Option, + pub to: Option, + pub format: Option, +} diff --git a/crates/brk_server/src/api/vecs/tree.rs b/crates/brk_server/src/api/vecs/tree.rs new file mode 100644 index 000000000..d3bc4e7e2 --- /dev/null +++ b/crates/brk_server/src/api/vecs/tree.rs @@ -0,0 +1,78 @@ +use std::{collections::BTreeMap, fs, io}; + +use derive_deref::{Deref, DerefMut}; +use storable_vec::AnyJsonStorableVec; + +use crate::WEBSITE_DEV_PATH; + +use super::index::Index; + +#[derive(Default, Deref, DerefMut)] +pub struct VecIdToIndexToVec(BTreeMap); + +impl VecIdToIndexToVec { + // Not the most performant or type safe but only built once so that's okay + pub fn insert(&mut self, vec: &'static dyn AnyJsonStorableVec) { + let file_name = vec.file_name(); + let split = file_name.split("_to_").collect::>(); + if split.len() != 2 { + panic!(); + } + let str = vec.index_type_to_string().split("::").last().unwrap().to_lowercase(); + let index = Index::try_from(str.as_str()) + .inspect_err(|_| { + dbg!(str); + }) + .unwrap(); + if split[0] != index.to_string().to_lowercase() { + dbg!(split[0], index.to_string()); + panic!(); + } + let key = split[1].to_string().replace("_", "-"); + let prev = self.entry(key).or_default().insert(index, vec); + if prev.is_some() { + panic!() + } + } + + pub fn generate_dts_file(&self) -> io::Result<()> { + if !fs::exists(WEBSITE_DEV_PATH)? { + return Ok(()); + } + + let path = format!("{WEBSITE_DEV_PATH}/scripts/types/vecid-to-indexes.d.ts"); + + let mut contents = Index::all() + .into_iter() + .enumerate() + .map(|(i_of_i, i)| format!("type {} = {};", i, i_of_i)) + .collect::>() + .join("\n"); + + contents += "\n\ninterface VecIdToIndexes {\n"; + + self.iter().for_each(|(id, index_to_vec)| { + let indexes = index_to_vec + .keys() + .map(|i| i.to_string()) + .collect::>() + .join(", "); + + contents += &format!( + " {}: [{indexes}]\n", + if id.contains("-") { + format!("\"{id}\"") + } else { + id.to_owned() + } + ); + }); + + contents.push('}'); + + fs::write(path, contents) + } +} + +#[derive(Default, Deref, DerefMut)] +pub struct IndexToVec(BTreeMap); diff --git a/crates/brk_server/src/files/file.rs b/crates/brk_server/src/files/file.rs new file mode 100644 index 000000000..196f39033 --- /dev/null +++ b/crates/brk_server/src/files/file.rs @@ -0,0 +1,133 @@ +use std::{ + fs::{self}, + path::{Path, PathBuf}, + time::Instant, +}; + +use axum::{ + body::Body, + extract, + http::{HeaderMap, StatusCode}, + response::{IntoResponse, Response}, +}; +use log::{error, info}; + +use crate::{ + WEBSITE_DEV_PATH, log_result, + traits::{HeaderMapExtended, ModifiedState, ResponseExtended}, +}; + +use super::minify::minify_js; + +pub async fn file_handler(headers: HeaderMap, path: extract::Path) -> Response { + any_handler(headers, Some(path)) +} + +pub async fn index_handler(headers: HeaderMap) -> Response { + any_handler(headers, None) +} + +fn any_handler(headers: HeaderMap, path: Option>) -> Response { + let instant = Instant::now(); + + let response = if let Some(path) = path.as_ref() { + let path = path.0.replace("..", "").replace("\\", ""); + + let mut path = str_to_path(&path); + + if !path.exists() { + if path.extension().is_some() { + let mut response: Response = + (StatusCode::INTERNAL_SERVER_ERROR, "File doesn't exist".to_string()).into_response(); + + response.headers_mut().insert_cors(); + + return response; + } else { + path = str_to_path("index.html"); + } + } + + path_to_response(&headers, &path) + } else { + path_to_response(&headers, &str_to_path("index.html")) + }; + + log_result( + response.status(), + &format!("/{}", path.map_or("".to_owned(), |p| p.0)), + instant, + ); + + response +} + +fn path_to_response(headers: &HeaderMap, path: &Path) -> Response { + match path_to_response_(headers, path) { + Ok(response) => response, + Err(error) => { + let mut response = (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(); + + response.headers_mut().insert_cors(); + + response + } + } +} + +fn path_to_response_(headers: &HeaderMap, path: &Path) -> color_eyre::Result { + let (modified, date) = headers.check_if_modified_since(path)?; + if modified == ModifiedState::NotModifiedSince { + return Ok(Response::new_not_modified()); + } + + let mut response; + + let is_localhost = headers.check_if_host_is_localhost(); + + if !is_localhost + && path.extension().unwrap_or_else(|| { + dbg!(path); + panic!(); + }) == "js" + { + let content = minify_js(path); + + response = Response::new(content.into()); + } else { + let content = fs::read(path).unwrap_or_else(|error| { + error!("{error}"); + let path = path.to_str().unwrap(); + info!("Can't read file {path}"); + panic!("") + }); + + response = Response::new(content.into()); + } + + let headers = response.headers_mut(); + headers.insert_cors(); + headers.insert_content_type(path); + + if !is_localhost { + let serialized_path = path.to_str().unwrap(); + + if serialized_path.contains("fonts/") + || serialized_path.contains("assets/") + || serialized_path.contains("packages/") + || path.extension().is_some_and(|extension| { + extension == "pdf" || extension == "jpg" || extension == "png" || extension == "woff2" + }) + { + headers.insert_cache_control_immutable(); + } + } + + headers.insert_last_modified(date); + + Ok(response) +} + +fn str_to_path(path: &str) -> PathBuf { + PathBuf::from(&format!("{WEBSITE_DEV_PATH}{path}")) +} diff --git a/server/src/website/handlers/_minify.rs b/crates/brk_server/src/files/minify.rs similarity index 54% rename from server/src/website/handlers/_minify.rs rename to crates/brk_server/src/files/minify.rs index aa5497b55..327800bcc 100644 --- a/server/src/website/handlers/_minify.rs +++ b/crates/brk_server/src/files/minify.rs @@ -1,41 +1,39 @@ -// Files are bigger than with SWC, to retest later - // Source: https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/examples/minifier.rs use std::{fs, path::Path}; use oxc::{ allocator::Allocator, - codegen::{CodeGenerator, CodegenOptions}, - minifier::{MinifierOptions, MinifierReturn}, - parser::{Parser, ParserReturn}, + codegen::{CodeGenerator, CodegenOptions, LegalComment}, + minifier::{CompressOptions, MangleOptions, Minifier, MinifierOptions}, + parser::Parser, span::SourceType, }; -// pub fn minify_js(path: &Path) -> String { - let allocator = Allocator::default(); - + let source_text = fs::read_to_string(path).unwrap(); let source_type = SourceType::from_path(path).unwrap(); - let source_text = fs::read_to_string(path).unwrap(); + let allocator = Allocator::default(); - let ParserReturn { mut program, .. } = - Parser::new(&allocator, &source_text, source_type).parse(); + let mut program = Parser::new(&allocator, &source_text, source_type).parse().program; - let minifier = oxc::minifier::Minifier::new(MinifierOptions::default()); - - let MinifierReturn { mangler } = minifier.build(&allocator, &mut program); + let minifier_return = Minifier::new(MinifierOptions { + mangle: Some(MangleOptions::default()), + compress: Some(CompressOptions::default()), + }) + .build(&allocator, &mut program); CodeGenerator::new() .with_options(CodegenOptions { - single_quote: false, minify: true, + single_quote: false, comments: false, annotation_comments: false, source_map_path: None, + legal_comments: LegalComment::None, }) - .with_mangler(mangler) + .with_symbol_table(minifier_return.symbol_table) .build(&program) .code } diff --git a/crates/brk_server/src/files/mod.rs b/crates/brk_server/src/files/mod.rs new file mode 100644 index 000000000..9be3690de --- /dev/null +++ b/crates/brk_server/src/files/mod.rs @@ -0,0 +1,18 @@ +use axum::{routing::get, Router}; + +use super::AppState; + +mod file; +mod minify; + +use file::{file_handler, index_handler}; + +pub trait FilesRoutes { + fn add_website_routes(self) -> Self; +} + +impl FilesRoutes for Router { + fn add_website_routes(self) -> Self { + self.route("/{*path}", get(file_handler)).route("/", get(index_handler)) + } +} diff --git a/crates/brk_server/src/lib.rs b/crates/brk_server/src/lib.rs new file mode 100644 index 000000000..8cdc6423f --- /dev/null +++ b/crates/brk_server/src/lib.rs @@ -0,0 +1,84 @@ +use std::time::Instant; + +use api::{ApiRoutes, VecIdToIndexToVec}; +use axum::{Json, Router, http::StatusCode, routing::get, serve}; +use brk_computer::Computer; +use brk_indexer::Indexer; +use color_eyre::owo_colors::OwoColorize; +use files::FilesRoutes; +use log::{error, info}; +use storable_vec::STATELESS; +use tokio::net::TcpListener; +use tower_http::compression::CompressionLayer; + +mod api; +mod files; +mod traits; + +#[derive(Clone)] +pub struct AppState { + vecs: &'static VecIdToIndexToVec, + indexer: &'static Indexer, + computer: &'static Computer, +} + +pub const WEBSITE_DEV_PATH: &str = "../../websites/kibo.money/"; + +pub async fn main(indexer: Indexer, computer: Computer) -> color_eyre::Result<()> { + let indexer = Box::leak(Box::new(indexer)); + let computer = Box::leak(Box::new(computer)); + let vecs = Box::leak(Box::new(VecIdToIndexToVec::default())); + + indexer + .vecs + .as_any_json_vec_slice() + .into_iter() + .for_each(|vec| vecs.insert(vec)); + + vecs.generate_dts_file()?; + + let state = AppState { + vecs, + indexer, + computer, + }; + + let compression_layer = CompressionLayer::new().br(true).deflate(true).gzip(true).zstd(true); + + let router = Router::new() + .add_api_routes() + .add_website_routes() + .route("/version", get(Json(env!("CARGO_PKG_VERSION")))) + .with_state(state) + .layer(compression_layer); + + let mut port = 3110; + + let mut listener; + loop { + listener = TcpListener::bind(format!("0.0.0.0:{port}")).await; + if listener.is_ok() { + break; + } + port += 1; + } + + info!("Starting server on port {port}..."); + + let listener = listener.unwrap(); + + serve(listener, router).await?; + + Ok(()) +} + +pub fn log_result(code: StatusCode, path: &str, instant: Instant) { + let time = format!("{}µs", instant.elapsed().as_micros()); + let time = time.bright_black(); + match code { + StatusCode::INTERNAL_SERVER_ERROR => error!("{} {} {}", code.as_u16().red(), path, time), + StatusCode::NOT_MODIFIED => info!("{} {} {}", code.as_u16().bright_black(), path, time), + StatusCode::OK => info!("{} {} {}", code.as_u16().green(), path, time), + _ => error!("{} {} {}", code.as_u16().red(), path, time), + } +} diff --git a/crates/brk_server/src/main.rs b/crates/brk_server/src/main.rs new file mode 100644 index 000000000..7d3e07b7d --- /dev/null +++ b/crates/brk_server/src/main.rs @@ -0,0 +1,20 @@ +use std::path::Path; + +use brk_computer::Computer; +use brk_indexer::Indexer; +use storable_vec::STATELESS; + +#[tokio::main] +pub async fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + brk_logger::init(None); + + let path = Path::new("../../_outputs"); + let indexer: Indexer = Indexer::import(&path.join("indexes"))?; + let computer: Computer = Computer::import(&path.join("computed"))?; + + brk_server::main(indexer, computer).await.unwrap(); + + Ok(()) +} diff --git a/server/src/header_map.rs b/crates/brk_server/src/traits/header_map.rs similarity index 74% rename from server/src/header_map.rs rename to crates/brk_server/src/traits/header_map.rs index f1af0527b..923155885 100644 --- a/server/src/header_map.rs +++ b/crates/brk_server/src/traits/header_map.rs @@ -1,21 +1,22 @@ -use std::path::Path; +use std::{path::Path, time}; -use axum::{ - body::Body, - http::{header, HeaderMap, Response}, - response::IntoResponse, -}; -use chrono::{DateTime, Timelike, Utc}; -use parser::log; -use reqwest::{ - header::{HOST, IF_MODIFIED_SINCE}, - StatusCode, +use axum::http::{ + HeaderMap, + header::{self, HOST, IF_MODIFIED_SINCE}, }; +use jiff::{Timestamp, civil::DateTime, fmt::strtime, tz::TimeZone}; +use log::info; const STALE_IF_ERROR: u64 = 30_000_000; // 1 Year ish const MODIFIED_SINCE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT"; -pub trait HeaderMapUtils { +#[derive(PartialEq, Eq)] +pub enum ModifiedState { + ModifiedSince, + NotModifiedSince, +} + +pub trait HeaderMapExtended { fn get_scheme(&self) -> &str; fn get_host(&self) -> &str; fn check_if_host_is_any_local(&self) -> bool; @@ -24,15 +25,13 @@ pub trait HeaderMapUtils { fn insert_cors(&mut self); - fn get_if_modified_since(&self) -> Option>; - fn check_if_modified_since( - &self, - path: &Path, - ) -> color_eyre::Result<(DateTime, Option>)>; + fn get_if_modified_since(&self) -> Option; + fn check_if_modified_since(&self, path: &Path) -> color_eyre::Result<(ModifiedState, DateTime)>; fn insert_cache_control_immutable(&mut self); + #[allow(unused)] fn insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64); - fn insert_last_modified(&mut self, date: DateTime); + fn insert_last_modified(&mut self, date: DateTime); fn insert_content_disposition_attachment(&mut self); @@ -46,12 +45,13 @@ pub trait HeaderMapUtils { fn insert_content_type_application_pdf(&mut self); fn insert_content_type_text_css(&mut self); fn insert_content_type_text_csv(&mut self); + fn insert_content_type_text_tsv(&mut self); fn insert_content_type_text_html(&mut self); fn insert_content_type_text_plain(&mut self); fn insert_content_type_font_woff2(&mut self); } -impl HeaderMapUtils for HeaderMap { +impl HeaderMapExtended for HeaderMap { fn get_scheme(&self) -> &str { if self.check_if_host_is_any_local() { "http" @@ -104,44 +104,39 @@ impl HeaderMapUtils for HeaderMap { ); } - fn insert_last_modified(&mut self, date: DateTime) { - let formatted = date.format(MODIFIED_SINCE_FORMAT).to_string(); + fn insert_last_modified(&mut self, date: DateTime) { + let formatted = date + .to_zoned(TimeZone::system()) + .unwrap() + .strftime(MODIFIED_SINCE_FORMAT) + .to_string(); self.insert(header::LAST_MODIFIED, formatted.parse().unwrap()); } - fn check_if_modified_since( - &self, - path: &Path, - ) -> color_eyre::Result<(DateTime, Option>)> { - let time = path.metadata()?.modified()?; - let date: DateTime = time.into(); - let date = date.with_nanosecond(0).unwrap(); - let mut response_opt = None; + fn check_if_modified_since(&self, path: &Path) -> color_eyre::Result<(ModifiedState, DateTime)> { + let duration = path.metadata()?.modified()?.duration_since(time::UNIX_EPOCH).unwrap(); + let date = Timestamp::new(duration.as_secs() as i64, 0) + .unwrap() + .to_zoned(TimeZone::UTC) + .datetime(); if let Some(if_modified_since) = self.get_if_modified_since() { if if_modified_since == date { - let mut response = (StatusCode::NOT_MODIFIED, "").into_response(); - let headers = response.headers_mut(); - headers.insert_cors(); - response_opt.replace(response); + return Ok((ModifiedState::NotModifiedSince, date)); } } - Ok((date, response_opt)) + Ok((ModifiedState::ModifiedSince, date)) } - fn get_if_modified_since(&self) -> Option> { + fn get_if_modified_since(&self) -> Option { if let Some(modified_since) = self.get(IF_MODIFIED_SINCE) { if let Ok(modified_since) = modified_since.to_str() { - let date = DateTime::parse_from_str( - &format!("{modified_since} +00:00"), - &format!("{MODIFIED_SINCE_FORMAT} %z"), - ); - - if let Ok(x) = date { - return Some(x.to_utc()); - } + return strtime::parse(MODIFIED_SINCE_FORMAT, modified_since) + .unwrap() + .to_datetime() + .ok(); } } @@ -163,7 +158,7 @@ impl HeaderMapUtils for HeaderMap { "png" => self.insert_content_type_image_png(), "webmanifest" => self.insert_content_type_application_manifest_json(), extension => { - log(&format!("Extension unsupported: {extension}")); + info!("Extension unsupported: {extension}"); panic!() } } @@ -182,10 +177,7 @@ impl HeaderMapUtils for HeaderMap { } fn insert_content_type_application_javascript(&mut self) { - self.insert( - header::CONTENT_TYPE, - "application/javascript".parse().unwrap(), - ); + self.insert(header::CONTENT_TYPE, "application/javascript".parse().unwrap()); } fn insert_content_type_application_json(&mut self) { @@ -193,10 +185,7 @@ impl HeaderMapUtils for HeaderMap { } fn insert_content_type_application_manifest_json(&mut self) { - self.insert( - header::CONTENT_TYPE, - "application/manifest+json".parse().unwrap(), - ); + self.insert(header::CONTENT_TYPE, "application/manifest+json".parse().unwrap()); } fn insert_content_type_application_pdf(&mut self) { @@ -211,6 +200,10 @@ impl HeaderMapUtils for HeaderMap { self.insert(header::CONTENT_TYPE, "text/csv".parse().unwrap()); } + fn insert_content_type_text_tsv(&mut self) { + self.insert(header::CONTENT_TYPE, "text/tab-separated-values".parse().unwrap()); + } + fn insert_content_type_text_html(&mut self) { self.insert(header::CONTENT_TYPE, "text/html".parse().unwrap()); } diff --git a/crates/brk_server/src/traits/mod.rs b/crates/brk_server/src/traits/mod.rs new file mode 100644 index 000000000..7055e7209 --- /dev/null +++ b/crates/brk_server/src/traits/mod.rs @@ -0,0 +1,5 @@ +mod header_map; +mod response; + +pub use header_map::*; +pub use response::*; diff --git a/crates/brk_server/src/traits/response.rs b/crates/brk_server/src/traits/response.rs new file mode 100644 index 000000000..9fef0a496 --- /dev/null +++ b/crates/brk_server/src/traits/response.rs @@ -0,0 +1,23 @@ +use axum::{ + body::Body, + http::{Response, StatusCode}, + response::IntoResponse, +}; + +use super::header_map::HeaderMapExtended; + +pub trait ResponseExtended +where + Self: Sized, +{ + fn new_not_modified() -> Self; +} + +impl ResponseExtended for Response { + fn new_not_modified() -> Response { + let mut response = (StatusCode::NOT_MODIFIED, "").into_response(); + let headers = response.headers_mut(); + headers.insert_cors(); + response + } +} diff --git a/crates/hodor/Cargo.toml b/crates/hodor/Cargo.toml new file mode 100644 index 000000000..0100d6fc9 --- /dev/null +++ b/crates/hodor/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hodor" +description = "Hold the door, an exit blocker that can wait until a task is completed" +version = "0.1.0" +edition = { workspace = true } +license = { workspace = true } + +[dependencies] +ctrlc = "3.4.5" +log = { workspace = true } diff --git a/crates/hodor/src/lib.rs b/crates/hodor/src/lib.rs new file mode 100644 index 000000000..726f85642 --- /dev/null +++ b/crates/hodor/src/lib.rs @@ -0,0 +1,62 @@ +use std::{ + process::exit, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread::sleep, + time::Duration, +}; + +use log::info; + +#[derive(Default, Clone)] +pub struct Exit { + blocked: Arc, + active: Arc, +} + +impl Exit { + pub fn new() -> Self { + let s = Self { + active: Arc::new(AtomicBool::new(false)), + blocked: Arc::new(AtomicBool::new(false)), + }; + + let active = s.active.clone(); + + let _blocked = s.blocked.clone(); + let blocked = move || _blocked.load(Ordering::SeqCst); + + ctrlc::set_handler(move || { + info!("Exitting..."); + + active.store(true, Ordering::SeqCst); + + if blocked() { + info!("Waiting to exit safely"); + + while blocked() { + sleep(Duration::from_millis(50)); + } + } + + exit(0); + }) + .expect("Error setting Ctrl-C handler"); + + s + } + + pub fn block(&self) { + self.blocked.store(true, Ordering::SeqCst); + } + + pub fn unblock(&self) { + self.blocked.store(false, Ordering::SeqCst); + } + + pub fn blocked(&self) -> bool { + self.active.load(Ordering::SeqCst) + } +} diff --git a/crates/storable_vec/.gitignore b/crates/storable_vec/.gitignore new file mode 100644 index 000000000..a7ab01af7 --- /dev/null +++ b/crates/storable_vec/.gitignore @@ -0,0 +1 @@ +/v diff --git a/crates/storable_vec/Cargo.lock b/crates/storable_vec/Cargo.lock new file mode 100644 index 000000000..86ab81ef7 --- /dev/null +++ b/crates/storable_vec/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "storable_vec" +version = "0.1.2" +dependencies = [ + "memmap2", +] diff --git a/crates/storable_vec/Cargo.toml b/crates/storable_vec/Cargo.toml new file mode 100644 index 000000000..1b22c346b --- /dev/null +++ b/crates/storable_vec/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "storable_vec" +description = "A very small, fast, efficient and simple storable Vec" +version = "0.1.3" +keywords = ["vec", "disk", "data"] +categories = ["database"] +edition = { workspace = true } +license = { workspace = true } + +[features] +json = ["serde", "serde_json"] + +[dependencies] +memmap2 = "0.9.5" +rayon = { workspace = true } +serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } +zerocopy = { workspace = true } diff --git a/crates/storable_vec/LICENSE.md b/crates/storable_vec/LICENSE.md new file mode 100644 index 000000000..bd3da5717 --- /dev/null +++ b/crates/storable_vec/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 storable_vec + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/storable_vec/README.md b/crates/storable_vec/README.md new file mode 100644 index 000000000..e098e2cd4 --- /dev/null +++ b/crates/storable_vec/README.md @@ -0,0 +1,53 @@ +# storable_vec + +A very small, fast, efficient and simple storable `vec` which uses `mmap2` for speed. + +## Features + +- [x] Get (Rayon compatible) +- [x] Push +- [ ] Update +- [ ] Insert +- [ ] Remove + +## Example + +```rust +use std::path::Path; + +use storable_vec::{AnyStorableVec, StorableVec}; + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + + { + let mut vec: StorableVec = StorableVec::import(Path::new("./v"))?; + vec.push(21); + dbg!(vec.get(0)?); // 21 + vec.flush()?; + } + + { + let vec: StorableVec = StorableVec::import(Path::new("./v"))?; + dbg!(vec.get(0)?); // 21 + } + + Ok(()) +} +``` + +## Disclaimer + +Portability will depend on the type of values. + +Non bytes/slices types (`u8`, `u16`, ...) will be read as slice in an unsafe manner (using `std::slice::from_raw_parts`) and thus have the endianness of the system. On the other hand, `&[u8]` should be inserted as is. + +If portability is important to you, just create a wrapper struct which has custom `get`, `push`, ... methods and does something like: + +```rust +impl StorableVecU64 { + pub fn push(&mut self, value: u64) { + self.push(&value.to_be_bytes()) + } +} +``` diff --git a/crates/storable_vec/src/enums/error.rs b/crates/storable_vec/src/enums/error.rs new file mode 100644 index 000000000..b066a4ece --- /dev/null +++ b/crates/storable_vec/src/enums/error.rs @@ -0,0 +1,67 @@ +use std::{ + fmt::{self, Debug}, + io, +}; + +use crate::Version; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + WrongEndian, + DifferentVersion { found: Version, expected: Version }, + MmapsVecIsTooSmall, + IO(io::Error), + ZeroCopyError, + IndexTooHigh, + IndexTooLow, + ExpectFileToHaveIndex, + ExpectVecToHaveIndex, + FailedKeyTryIntoUsize, + UnsupportedUnflushedState, + RangeFromAfterTo, +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::IO(value) + } +} + +impl From> for Error { + fn from(_: zerocopy::error::ConvertError) -> Self { + Self::ZeroCopyError + } +} + +impl From> for Error { + fn from(_: zerocopy::error::SizeError) -> Self { + Self::ZeroCopyError + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::WrongEndian => write!(f, "Wrong endian"), + Error::DifferentVersion { found, expected } => { + write!(f, "Different version; found: {found:?}, expected: {expected:?}") + } + Error::MmapsVecIsTooSmall => write!(f, "Mmaps vec is too small"), + Error::IO(error) => Debug::fmt(&error, f), + Error::IndexTooHigh => write!(f, "Index too high"), + Error::IndexTooLow => write!(f, "Index too low"), + Error::ExpectFileToHaveIndex => write!(f, "Expect file to have index"), + Error::ExpectVecToHaveIndex => write!(f, "Expect vec to have index"), + Error::FailedKeyTryIntoUsize => write!(f, "Failed to convert key to usize"), + Error::UnsupportedUnflushedState => { + write!(f, "Unsupported unflush state, please flush before using this function") + } + Error::ZeroCopyError => write!(f, "Zero copy convert error"), + Error::RangeFromAfterTo => write!(f, "Range, from is after to"), + } + } +} + +impl std::error::Error for Error {} diff --git a/crates/storable_vec/src/enums/mod.rs b/crates/storable_vec/src/enums/mod.rs new file mode 100644 index 000000000..9198ca8e0 --- /dev/null +++ b/crates/storable_vec/src/enums/mod.rs @@ -0,0 +1,5 @@ +mod error; +mod value; + +pub use error::*; +pub use value::*; diff --git a/crates/storable_vec/src/enums/value.rs b/crates/storable_vec/src/enums/value.rs new file mode 100644 index 000000000..7adaf7bb2 --- /dev/null +++ b/crates/storable_vec/src/enums/value.rs @@ -0,0 +1,39 @@ +use std::{fmt::Debug, ops::Deref}; + +#[derive(Debug, Clone)] +pub enum Value<'a, T> { + Ref(&'a T), + Owned(T), +} + +impl Value<'_, T> +where + T: Sized + Debug + Clone, +{ + pub fn into_inner(self) -> T { + match self { + Self::Ref(t) => t.to_owned(), + Self::Owned(t) => t, + } + } +} +impl Deref for Value<'_, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + match self { + Self::Ref(t) => t, + Self::Owned(t) => t, + } + } +} +impl AsRef for Value<'_, T> +where + T: Sized + Debug + Clone, +{ + fn as_ref(&self) -> &T { + match self { + Self::Ref(t) => t, + Self::Owned(t) => t, + } + } +} diff --git a/crates/storable_vec/src/lib.rs b/crates/storable_vec/src/lib.rs new file mode 100644 index 000000000..184a31939 --- /dev/null +++ b/crates/storable_vec/src/lib.rs @@ -0,0 +1,724 @@ +use std::{ + cmp::Ordering, + error, + fmt::Debug, + fs::{self, File, OpenOptions}, + io::{self, Read, Seek, SeekFrom, Write}, + marker::PhantomData, + mem, + ops::{Add, Range, Sub}, + path::{Path, PathBuf}, + sync::OnceLock, +}; + +pub use memmap2; +use rayon::prelude::*; +pub use zerocopy; + +mod enums; +mod structs; +mod traits; + +pub use enums::*; +pub use structs::*; +pub use traits::*; + +type Buffer = Vec; + +/// Uses `Mmap` instead of `File` +/// +/// Used in `/indexer` +pub const CACHED_GETS: u8 = 0; + +/// Will use the same `File` for every read, so not thread safe +/// +/// Used in `/computer` +pub const SINGLE_THREAD: u8 = 1; + +/// Will spin up a new `File` for every read +/// +/// Used in `/server` +pub const STATELESS: u8 = 2; + +/// +/// A very small, fast, efficient and simple storable Vec +/// +/// Reads (imports of Mmap) are lazy +/// +/// Stores only raw data without any overhead, and doesn't even have a header (TODO: which it should, at least to Err if wrong endian) +/// +/// The file isn't portable for speed reasons (TODO: but could be ?) +/// +/// If you don't call `.flush()` it just acts as a normal Vec +/// +#[derive(Debug)] +pub struct StorableVec { + pathbuf: PathBuf, + file: File, + /// **Number of values NOT number of bytes** + file_len: usize, + file_position: u64, + buf: Buffer, + /// Only for CACHED_GETS + cache: Vec>>, // Boxed Mmap to reduce the size of the Lock (from 24 to 16) + pushed: Vec, + // updated: BTreeMap, + // inserted: BTreeMap, + // removed: BTreeSet, + // min: AtomicUsize, + // opened_mmaps: AtomicUsize, + phantom: PhantomData, +} + +/// In bytes +const MAX_PAGE_SIZE: usize = 4 * 4096; +const ONE_MB: usize = 1000 * 1024; +// const MAX_CACHE_SIZE: usize = usize::MAX; +const MAX_CACHE_SIZE: usize = 100 * ONE_MB; + +impl StorableVec +where + I: StoredIndex, + T: StoredType, +{ + pub const SIZE_OF_T: usize = size_of::(); + pub const PER_PAGE: usize = MAX_PAGE_SIZE / Self::SIZE_OF_T; + /// In bytes + pub const PAGE_SIZE: usize = Self::PER_PAGE * Self::SIZE_OF_T; + pub const CACHE_LENGTH: usize = MAX_CACHE_SIZE / Self::PAGE_SIZE; + + /// Same as import but will remove the folder if the endian or the version is different, so be careful ! + pub fn forced_import(path: &Path, version: Version) -> Result { + let res = Self::import(path, version); + match res { + Err(Error::WrongEndian) | Err(Error::DifferentVersion { found: _, expected: _ }) => { + fs::remove_dir_all(path)?; + Self::import(path, version) + } + _ => res, + } + } + + pub fn import(path: &Path, version: Version) -> Result { + fs::create_dir_all(path)?; + + if MODE != STATELESS { + let path_version = Self::path_version_(path); + + if let Ok(prev_version) = Version::try_from(path_version.as_path()) { + if prev_version != version { + if prev_version.swap_bytes() == version { + return Err(Error::WrongEndian); + } + return Err(Error::DifferentVersion { + found: prev_version, + expected: version, + }); + } + } + + version.write(&path_version)?; + } + + let file = Self::open_file_(&Self::path_vec_(path))?; + + let mut slf = Self { + pathbuf: path.to_owned(), + file_position: 0, + file_len: Self::read_disk_len_(&file)?, + file, + buf: Self::create_buffer(), + cache: vec![], + pushed: vec![], + // updated: BTreeMap::new(), + // inserted: BTreeMap::new(), + // removed: BTreeSet::new(), + phantom: PhantomData, + // min: AtomicUsize::new(usize::MAX), + // opened_mmaps: AtomicUsize::new(0), + }; + + slf.reset_disk_related_state()?; + + Ok(slf) + } + + #[inline] + fn create_buffer() -> Buffer { + vec![0; Self::SIZE_OF_T] + } + + fn open_file(&self) -> io::Result { + Self::open_file_(&self.path_vec()) + } + fn open_file_(path: &Path) -> io::Result { + OpenOptions::new() + .read(true) + .create(true) + .truncate(false) + .append(true) + .open(path) + } + + fn open_file_at_then_read(&self, index: usize) -> Result { + let mut file = self.open_file()?; + Self::seek(&mut file, Self::index_to_byte_index(index))?; + let mut buf = Self::create_buffer(); + Self::read_exact(&mut file, &mut buf).map(|v| v.to_owned()) + } + + fn read_disk_len(&self) -> io::Result { + Self::read_disk_len_(&self.file) + } + fn read_disk_len_(file: &File) -> io::Result { + Ok(Self::byte_index_to_index(file.metadata()?.len() as usize)) + } + + fn reset_disk_related_state(&mut self) -> io::Result<()> { + self.file_len = self.read_disk_len()?; + self.file_position = 0; + self.reset_cache() + } + + fn reset_cache(&mut self) -> io::Result<()> { + match MODE { + CACHED_GETS => { + self.cache.par_iter_mut().for_each(|lock| { + lock.take(); + }); + + let len = (self.file_len as f64 / Self::PER_PAGE as f64).ceil() as usize; + let len = Self::CACHE_LENGTH.min(len); + + if self.cache.len() != len { + self.cache.resize_with(len, Default::default); + // self.cache.shrink_to_fit(); + } + + Ok(()) + } + _ => Ok(()), + } + } + + #[inline] + fn seek(file: &mut File, byte_index: u64) -> io::Result { + file.seek(SeekFrom::Start(byte_index)) + } + + fn read_exact<'a>(file: &'a mut File, buf: &'a mut [u8]) -> Result<&'a T> { + file.read_exact(buf)?; + let v = T::try_ref_from_bytes(&buf[..])?; + Ok(v) + } + + #[inline] + fn push_(&mut self, value: T) { + self.pushed.push(value) + } + + #[inline] + fn push_if_needed_(&mut self, index: I, value: T) -> Result<()> { + match self.len().cmp(&Self::i_to_usize(index)?) { + Ordering::Greater => { + // dbg!(len, index, &self.pathbuf); + // panic!(); + Ok(()) + } + Ordering::Equal => { + self.pushed.push(value); + Ok(()) + } + Ordering::Less => { + dbg!(index, value); + Err(Error::IndexTooHigh) + } + } + } + + #[inline] + pub fn len(&self) -> usize { + self.file_len + self.pushed_len() + } + + #[inline] + pub fn pushed_len(&self) -> usize { + self.pushed.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[inline] + pub fn has(&self, index: I) -> Result { + Ok(self.has_(Self::i_to_usize(index)?)) + } + #[inline] + fn has_(&self, index: usize) -> bool { + index < self.len() + } + + #[inline] + pub fn hasnt(&self, index: I) -> Result { + self.has(index).map(|b| !b) + } + + pub fn flush(&mut self) -> io::Result<()> { + if self.pushed.is_empty() { + return Ok(()); + } + + let mut bytes: Vec = vec![0; self.pushed_len() * Self::SIZE_OF_T]; + + let unsafe_bytes = UnsafeSlice::new(&mut bytes); + + mem::take(&mut self.pushed) + .into_par_iter() + .enumerate() + .for_each(|(i, v)| unsafe_bytes.copy_slice(i * Self::SIZE_OF_T, v.as_bytes())); + + self.file.write_all(&bytes)?; + + self.reset_disk_related_state()?; + + Ok(()) + } + + pub fn truncate_if_needed(&mut self, index: I) -> Result> { + let index = Self::i_to_usize(index)?; + + if index >= self.file_len { + return Ok(None); + } + + let value_at_index = self.open_file_at_then_read(index).ok(); + + self.file.set_len(Self::index_to_byte_index(index))?; + + self.reset_disk_related_state()?; + + Ok(value_at_index) + } + + #[inline] + fn i_to_usize(index: I) -> Result { + index.try_into().map_err(|_| Error::FailedKeyTryIntoUsize) + } + + #[inline] + fn byte_index_to_index(byte_index: usize) -> usize { + byte_index / Self::SIZE_OF_T + } + + #[inline] + fn index_to_byte_index(index: usize) -> u64 { + (index * Self::SIZE_OF_T) as u64 + } + + #[inline] + fn index_to_byte_range(index: usize) -> Range { + let index = (Self::index_to_byte_index(index) as usize) % Self::PAGE_SIZE; + index..(index + Self::SIZE_OF_T) + } + + fn index_to_pushed_index(&self, index: usize) -> Result> { + if index >= self.file_len { + let index = index - self.file_len; + if index >= self.pushed.len() { + Err(Error::IndexTooHigh) + } else { + Ok(Some(index)) + } + } else { + Err(Error::IndexTooLow) + } + } + + pub fn file_name(&self) -> String { + self.path().file_name().unwrap().to_str().unwrap().to_owned() + } + + #[inline] + pub fn path(&self) -> &Path { + &self.pathbuf + } + + #[inline] + fn path_vec(&self) -> PathBuf { + Self::path_vec_(&self.pathbuf) + } + #[inline] + fn path_vec_(path: &Path) -> PathBuf { + path.join("vec") + } + + #[inline] + fn path_version_(path: &Path) -> PathBuf { + path.join("version") + } + + pub fn index_type_to_string(&self) -> &str { + std::any::type_name::() + } +} + +impl StorableVec +where + I: StoredIndex, + T: StoredType, +{ + #[inline] + pub fn get(&self, index: I) -> Result>> { + self.get_(Self::i_to_usize(index)?) + } + fn get_(&self, index: usize) -> Result>> { + match self.index_to_pushed_index(index) { + Ok(index) => { + if let Some(index) = index { + return Ok(self.pushed.get(index).map(|v| Value::Ref(v))); + } + } + Err(Error::IndexTooHigh) => return Ok(None), + Err(Error::IndexTooLow) => {} + Err(error) => return Err(error), + } + + // if !self.updated.is_empty() { + // if let Some(v) = self.updated.get(&index) { + // return Ok(Some(v)); + // } + // } + + let page_index = index / Self::PER_PAGE; + let last_index = self.file_len - 1; + let max_page_index = last_index / Self::PER_PAGE; + let min_page_index = (max_page_index + 1) - self.cache.len(); + + // let min_open_page = self.min.load(AtomicOrdering::SeqCst); + + // if self.min.load(AtomicOrdering::SeqCst) { + // self.min.set(value) + // } + + if page_index >= min_page_index { + let mmap = &**self + .cache + .get(page_index - min_page_index) + .ok_or(Error::MmapsVecIsTooSmall)? + .get_or_init(|| { + Box::new(unsafe { + memmap2::MmapOptions::new() + .len(Self::PAGE_SIZE) + .offset((page_index * Self::PAGE_SIZE) as u64) + .map(&self.file) + .unwrap() + }) + }); + + let range = Self::index_to_byte_range(index); + let slice = &mmap[range]; + return Ok(Some(Value::Ref(T::try_ref_from_bytes(slice)?))); + } + + Ok(Some(Value::Owned(self.open_file_at_then_read(index)?.to_owned()))) + } + + pub fn get_or_default(&self, index: I) -> Result + where + T: Default + Clone, + { + Ok(self.get(index)?.map(|v| (*v).clone()).unwrap_or(Default::default())) + } + + pub fn iter_from(&self, mut index: I, mut f: F) -> Result<()> + where + F: FnMut((I, Value)) -> Result<()>, + { + let disk_len = I::from(Self::read_disk_len_(&self.file)?); + + while index < disk_len { + f((index, self.get(index)?.unwrap()))?; + index = index + 1; + } + + let mut index = I::from(0); + let pushed_len = I::from(self.pushed_len()); + let disk_len = Self::i_to_usize(disk_len)?; + while index < pushed_len { + f(((index + disk_len), self.get(index)?.unwrap()))?; + index = index + 1; + } + + Ok(()) + } + + #[inline] + pub fn push(&mut self, value: T) { + self.push_(value) + } + + #[inline] + pub fn push_if_needed(&mut self, index: I, value: T) -> Result<()> { + self.push_if_needed_(index, value) + } +} + +const FLUSH_EVERY: usize = 10_000; +impl StorableVec +where + I: StoredIndex, + T: StoredType, +{ + pub fn get(&mut self, index: I) -> Result<&T> { + self.get_(Self::i_to_usize(index)?) + } + fn get_(&mut self, index: usize) -> Result<&T> { + let byte_index = Self::index_to_byte_index(index); + if self.file_position != byte_index { + self.file_position = Self::seek(&mut self.file, byte_index)?; + } + let res = Self::read_exact(&mut self.file, &mut self.buf); + if res.is_ok() { + self.file_position += Self::SIZE_OF_T as u64; + } + res + } + + pub fn last(&mut self) -> Result> { + let len = self.len(); + if len == 0 { + return Ok(None); + } + Ok(self.get_(len - 1).ok()) + } + + #[inline] + pub fn push(&mut self, value: T) { + self.push_(value) + } + + #[inline] + pub fn push_if_needed(&mut self, index: I, value: T) -> Result<()> { + self.push_if_needed_(index, value)?; + + if self.pushed_len() >= FLUSH_EVERY { + Ok(self.flush()?) + } else { + Ok(()) + } + } + + pub fn iter(&mut self, f: F) -> Result<()> + where + F: FnMut((I, &T)) -> Result<()>, + { + self.iter_from(I::default(), f) + } + + pub fn iter_from(&mut self, mut index: I, mut f: F) -> Result<()> + where + F: FnMut((I, &T)) -> Result<()>, + { + // let pushed_len = self.pushed_len(); + + // self.seek_if_needed(index)?; + + if !self.pushed.is_empty() { + return Err(Error::UnsupportedUnflushedState); + } + + let disk_len = I::from(Self::read_disk_len_(&self.file)?); + + while index < disk_len { + f((index, self.get(index)?))?; + index = index + 1; + } + + // i = 0; + // while i < pushed_len { + // f((I::from(i + disk_len), self.pushed.get(i).as_ref().unwrap()))?; + // i += 1; + // } + + Ok(()) + } + + pub fn compute_transform(&mut self, other: &mut StorableVec, t: F) -> Result<()> + where + A: StoredType, + F: Fn(&A) -> T, + { + other.iter_from(I::from(self.len()), |(i, a)| self.push_if_needed(i, t(a)))?; + Ok(self.flush()?) + } + + pub fn compute_inverse_more_to_less(&mut self, other: &mut StorableVec) -> Result<()> + where + I: StoredType, + T: StoredIndex, + { + let index = self.last()?.cloned().unwrap_or_default(); + other.iter_from(index, |(v, i)| self.push_if_needed(*i, v))?; + Ok(self.flush()?) + } + + pub fn compute_inverse_less_to_more( + &mut self, + first_indexes: &mut StorableVec, + last_indexes: &mut StorableVec, + ) -> Result<()> + where + I: StoredType, + T: StoredIndex, + { + first_indexes.iter_from(T::from(self.len()), |(value, first_index)| { + let first_index = Self::i_to_usize(*first_index)?; + let last_index = Self::i_to_usize(*last_indexes.get(value)?)?; + (first_index..last_index).try_for_each(|index| self.push_if_needed(I::from(index), value)) + })?; + Ok(self.flush()?) + } + + pub fn compute_last_index_from_first( + &mut self, + first_index_vec: &mut StorableVec, + final_len: usize, + ) -> Result<()> + where + T: Copy + From + Sub + StoredIndex, + { + let one = T::from(1); + let mut prev_index: Option = None; + first_index_vec.iter_from(I::from(self.len()), |(i, v)| { + if let Some(prev_index) = prev_index { + self.push_if_needed(prev_index, *v - one)?; + } + prev_index.replace(i); + Ok(()) + })?; + if let Some(prev_index) = prev_index { + self.push_if_needed(prev_index, T::from(final_len) - one)?; + } + Ok(self.flush()?) + } + + pub fn compute_count_from_indexes( + &mut self, + first_indexes: &mut StorableVec, + last_indexes: &mut StorableVec, + ) -> Result<()> + where + T: From, + T2: StoredType + Copy + Add + Sub + TryInto, + >::Error: error::Error + 'static, + { + first_indexes.iter_from(I::from(self.len()), |(i, first_index)| { + let last_index = last_indexes.get(i)?; + let count = *last_index + 1_usize - *first_index; + self.push_if_needed(i, count.into()) + })?; + Ok(self.flush()?) + } + + pub fn compute_is_first_ordered( + &mut self, + self_to_other: &mut StorableVec, + other_to_self: &mut StorableVec, + ) -> Result<()> + where + I: StoredType, + T: From, + A: StoredIndex + StoredType, + { + self_to_other.iter_from(I::from(self.len()), |(i, other)| { + self.push_if_needed(i, T::from(other_to_self.get(*other)? == &i)) + })?; + Ok(self.flush()?) + } + + pub fn compute_sum_from_indexes( + &mut self, + first_indexes: &mut StorableVec, + last_indexes: &mut StorableVec, + ) -> Result<()> + where + T: From, + T2: StoredType + Copy + Add + Sub + TryInto, + >::Error: error::Error + 'static, + F: Fn(&T2) -> T, + { + first_indexes.iter_from(I::from(self.len()), |(i, first_index)| { + let last_index = last_indexes.get(i)?; + let count = *last_index + 1_usize - *first_index; + self.push_if_needed(i, count.into()) + })?; + Ok(self.flush()?) + } +} + +impl StorableVec +where + I: StoredIndex, + T: StoredType, +{ + #[inline] + pub fn get(&self, index: I) -> Result> { + Ok(Some(self.open_file_at_then_read(Self::i_to_usize(index)?)?)) + } + + pub fn collect_range(&self, from: Option, to: Option) -> Result> { + if !self.pushed.is_empty() { + return Err(Error::UnsupportedUnflushedState); + } + + let mut file = self.open_file()?; + + let len = Self::read_disk_len_(&file)?; + + let from = from.map_or(0, |from| { + if from >= 0 { + from as usize + } else { + (len as i64 + from) as usize + } + }); + + let to = to.map_or(len, |to| { + if to >= 0 { + to as usize + } else { + (len as i64 + to) as usize + } + }); + + if from >= to { + return Err(Error::RangeFromAfterTo); + } + + Self::seek(&mut file, Self::index_to_byte_index(from))?; + + let mut buf = Self::create_buffer(); + + Ok((from..to) + .map(|_| Self::read_exact(&mut file, &mut buf).map(|v| v.to_owned()).unwrap()) + .collect::>()) + } + + // Add iter iter_from iter_range collect.. + // + add memory cap +} + +impl Clone for StorableVec +where + I: StoredIndex, + T: StoredType, +{ + fn clone(&self) -> Self { + let path = &self.pathbuf; + let path_version = Self::path_version_(path); + let version = Version::try_from(path_version.as_path()).unwrap(); + Self::import(path, version).unwrap() + } +} diff --git a/crates/storable_vec/src/main.rs b/crates/storable_vec/src/main.rs new file mode 100644 index 000000000..15c162146 --- /dev/null +++ b/crates/storable_vec/src/main.rs @@ -0,0 +1,30 @@ +use std::path::Path; + +use storable_vec::{StorableVec, Version, CACHED_GETS, SINGLE_THREAD}; + +fn main() -> Result<(), Box> { + { + let mut vec: StorableVec = + StorableVec::forced_import(Path::new("./v"), Version::from(1))?; + + vec.push(0); + vec.push(1); + vec.push(2); + dbg!(vec.get(0)?); // Some(0) + dbg!(vec.get(21)?); // None + + vec.flush()?; + } + + { + let mut vec: StorableVec = + StorableVec::forced_import(Path::new("./v"), Version::from(1))?; + + dbg!(vec.get(0)?); // 0 + dbg!(vec.get(1)?); // 0 + dbg!(vec.get(2)?); // 0 + dbg!(vec.get(0)?); // 0 + } + + Ok(()) +} diff --git a/crates/storable_vec/src/structs/mod.rs b/crates/storable_vec/src/structs/mod.rs new file mode 100644 index 000000000..51482371f --- /dev/null +++ b/crates/storable_vec/src/structs/mod.rs @@ -0,0 +1,5 @@ +mod unsafe_slice; +mod version; + +pub use unsafe_slice::*; +pub use version::*; diff --git a/crates/storable_vec/src/structs/unsafe_slice.rs b/crates/storable_vec/src/structs/unsafe_slice.rs new file mode 100644 index 000000000..8f354e0ca --- /dev/null +++ b/crates/storable_vec/src/structs/unsafe_slice.rs @@ -0,0 +1,30 @@ +use std::cell::UnsafeCell; + +#[derive(Copy, Clone)] +pub struct UnsafeSlice<'a, T>(&'a [UnsafeCell]); +unsafe impl Send for UnsafeSlice<'_, T> {} +unsafe impl Sync for UnsafeSlice<'_, T> {} + +impl<'a, T> UnsafeSlice<'a, T> { + pub fn new(slice: &'a mut [T]) -> Self { + let ptr = slice as *mut [T] as *const [UnsafeCell]; + Self(unsafe { &*ptr }) + } + + /// SAFETY: It is UB if two threads write to the same index without + /// synchronization. + pub fn write(&self, i: usize, value: T) { + unsafe { + *self.0[i].get() = value; + } + } + + pub fn copy_slice(&self, start: usize, slice: &[T]) + where + T: Copy, + { + slice.iter().enumerate().for_each(|(i, v)| { + self.write(start + i, *v); + }); + } +} diff --git a/crates/storable_vec/src/structs/version.rs b/crates/storable_vec/src/structs/version.rs new file mode 100644 index 000000000..565f868b1 --- /dev/null +++ b/crates/storable_vec/src/structs/version.rs @@ -0,0 +1,37 @@ +use std::{ + fs, + io::{self, Read}, + path::Path, +}; + +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct Version(u32); + +impl Version { + pub fn write(&self, path: &Path) -> Result<(), io::Error> { + fs::write(path, self.as_bytes()) + } + + pub fn swap_bytes(self) -> Self { + Self(self.0.swap_bytes()) + } +} + +impl From for Version { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl TryFrom<&Path> for Version { + type Error = Error; + fn try_from(value: &Path) -> Result { + let mut buf = [0; 4]; + fs::read(value)?.as_slice().read_exact(&mut buf)?; + Ok(*(Self::ref_from_bytes(&buf)?)) + } +} diff --git a/crates/storable_vec/src/traits/any.rs b/crates/storable_vec/src/traits/any.rs new file mode 100644 index 000000000..d35982042 --- /dev/null +++ b/crates/storable_vec/src/traits/any.rs @@ -0,0 +1,65 @@ +use std::{io, mem}; + +use crate::{Result, StorableVec, STATELESS}; + +use super::{StoredIndex, StoredType}; + +pub trait AnyStorableVec: Send + Sync { + fn file_name(&self) -> String; + fn index_type_to_string(&self) -> &str; + fn len(&self) -> usize; + fn is_empty(&self) -> bool; + fn flush(&mut self) -> io::Result<()>; +} + +impl AnyStorableVec for StorableVec +where + I: StoredIndex, + T: StoredType, +{ + fn file_name(&self) -> String { + self.file_name() + } + + fn index_type_to_string(&self) -> &str { + self.index_type_to_string() + } + + fn len(&self) -> usize { + self.len() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn flush(&mut self) -> io::Result<()> { + self.flush() + } +} + +#[cfg(feature = "json")] +pub trait AnyJsonStorableVec: AnyStorableVec { + fn collect_range_values(&self, from: Option, to: Option) -> Result>; +} + +#[cfg(feature = "json")] +impl AnyJsonStorableVec for StorableVec +where + I: StoredIndex, + T: StoredType + serde::Serialize, +{ + fn collect_range_values(&self, from: Option, to: Option) -> Result> { + if MODE == STATELESS { + Ok( + unsafe { mem::transmute::<&StorableVec, &StorableVec>(self) } + .collect_range(from, to)? + .into_iter() + .map(|v| serde_json::to_value(v).unwrap()) + .collect::>(), + ) + } else { + todo!("todo ?") + } + } +} diff --git a/crates/storable_vec/src/traits/bytes.rs b/crates/storable_vec/src/traits/bytes.rs new file mode 100644 index 000000000..569d9dd32 --- /dev/null +++ b/crates/storable_vec/src/traits/bytes.rs @@ -0,0 +1,11 @@ +use std::sync::Arc; + +use crate::Result; + +pub trait Bytes: Sized { + const LEN: usize = size_of::(); + fn to_bytes(&self) -> Arc<[u8]>; + fn try_from_bytes(bytes: &[u8]) -> Result; +} + +pub trait UnsafeBytes {} diff --git a/crates/storable_vec/src/traits/mod.rs b/crates/storable_vec/src/traits/mod.rs new file mode 100644 index 000000000..c12f4f424 --- /dev/null +++ b/crates/storable_vec/src/traits/mod.rs @@ -0,0 +1,8 @@ +mod any; +// mod bytes; +mod stored_index; +mod stored_type; + +pub use any::*; +pub use stored_index::*; +pub use stored_type::*; diff --git a/crates/storable_vec/src/traits/stored_index.rs b/crates/storable_vec/src/traits/stored_index.rs new file mode 100644 index 000000000..c0200f1ed --- /dev/null +++ b/crates/storable_vec/src/traits/stored_index.rs @@ -0,0 +1,35 @@ +use std::{fmt::Debug, ops::Add}; + +pub trait StoredIndex +where + Self: Debug + + Default + + Copy + + Clone + + PartialEq + + Eq + + PartialOrd + + Ord + + TryInto + + From + + Add + + Send + + Sync, +{ +} +impl StoredIndex for I where + I: Debug + + Default + + Copy + + Clone + + PartialEq + + Eq + + PartialOrd + + Ord + + TryInto + + From + + Add + + Send + + Sync +{ +} diff --git a/crates/storable_vec/src/traits/stored_type.rs b/crates/storable_vec/src/traits/stored_type.rs new file mode 100644 index 000000000..f0a7b9e42 --- /dev/null +++ b/crates/storable_vec/src/traits/stored_type.rs @@ -0,0 +1,13 @@ +use std::fmt::Debug; + +use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes}; + +pub trait StoredType +where + Self: Sized + Debug + Clone + TryFromBytes + IntoBytes + Immutable + KnownLayout + Send + Sync, +{ +} +impl StoredType for T where + T: Sized + Debug + Clone + TryFromBytes + IntoBytes + Immutable + KnownLayout + Send + Sync +{ +} diff --git a/iterable/Cargo.lock b/iterable/Cargo.lock deleted file mode 100644 index 582ac585d..000000000 --- a/iterable/Cargo.lock +++ /dev/null @@ -1,60 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "proc-macro2" -version = "1.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "struct_iterable" -version = "0.1.2" -dependencies = [ - "struct_iterable_derive", - "struct_iterable_internal", -] - -[[package]] -name = "struct_iterable_derive" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "struct_iterable_internal", - "syn", -] - -[[package]] -name = "struct_iterable_internal" -version = "0.1.1" - -[[package]] -name = "syn" -version = "2.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/iterable/Cargo.toml b/iterable/Cargo.toml deleted file mode 100644 index ba00c67ad..000000000 --- a/iterable/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "struct_iterable" -version = "0.1.2" -authors = ["André de Moraes "] -edition = "2021" -description = "A Rust library providing a proc macro to make a struct iterable." -license = "MIT" -repository = "https://github.com/decomoraes/rust_struct_iterable" -readme = "README.md" -keywords = ["proc-macro", "struct", "iterable"] -categories = ["development-tools::cargo-plugins"] -homepage = "https://github.com/decomoraes/rust_struct_iterable" -documentation = "https://docs.rs/struct_iterable" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -struct_iterable_derive = { path = "./struct_iterable_derive" } -struct_iterable_internal = { path = "./struct_iterable_internal" } - -[lib] -name = "struct_iterable" -path = "src/lib.rs" - -[package.metadata.docs.rs] -all-features = true diff --git a/iterable/README.md b/iterable/README.md deleted file mode 100644 index b7cecc86a..000000000 --- a/iterable/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Struct Iterable - -`Struct Iterable` is a Rust library that provides a proc macro to make a struct iterable. This allows you to iterate over the fields of your struct in a generic way, with each iteration returning a tuple containing the name of the field as a static string and a reference to the field's value as a `dyn Any`. - -## How to Use - -First, add `Struct Iterable` to your `Cargo.toml`: - -```toml -[dependencies] -struct_iterable = "0.1.1" -``` - -Next, include the library at the top of your Rust file: - -```rust -use struct_iterable::Iterable; -``` - -Finally, add the `#[derive(Iterable)]` attribute to your struct: - -```rust -#[derive(Iterable)] -struct MyStruct { - field1: u32, - field2: String, - // etc. -} -``` - -Now, you can iterate over the fields of an instance of your struct: - -```rust -let my_instance = MyStruct { - field1: 42, - field2: "Hello, world!".to_string(), -}; - -for (field_name, field_value) in my_instance.iter() { - println!("{}: {:?}", field_name, field_value); -} -``` - -## Limitations -- Only structs with named fields are supported. -- Only structs are supported, not enums or unions. - -## Implementation - -Here is the implementation of the proc macro: - -```rust -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; -use iterable_structs::Iterable; - -#[proc_macro_derive(Iterable)] -pub fn derive_iterable(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let struct_name = input.ident; - let fields = match input.data { - Data::Struct(data_struct) => match data_struct.fields { - Fields::Named(fields_named) => fields_named.named, - _ => panic!("Only structs with named fields are supported"), - }, - _ => panic!("Only structs are supported"), - }; - - let fields_iter = fields.iter().map(|field| { - let field_ident = &field.ident; - let field_name = field_ident.as_ref().unwrap().to_string(); - quote! { - (#field_name, &(self.#field_ident) as &dyn std::any::Any) - } - }); - - let expanded = quote! { - impl Iterable for #struct_name { - fn iter<'a>(&'a self) -> std::vec::IntoIter<(&'static str, &'a dyn std::any::Any)> { - vec![ - #(#fields_iter),* - ].into_iter() - } - } - }; - - TokenStream::from(expanded) -} -``` - -The macro takes in the TokenStream of a struct and expands it into an implementation of the Iterable trait for that struct. This trait provides an iter method that returns an iterator over tuples of field names and values. - -## Contributing and License - -`Struct Iterable` is an open-source project, and contributions are warmly welcomed. Whether you're fixing bugs, improving the documentation, or proposing new features, your efforts are highly appreciated! - -If you're interested in contributing, please feel free to submit a pull request. For major changes, please open an issue first to discuss what you would like to change. - -Please note that this project is released with a Contributor Code of Conduct. By participating in this project, you agree to abide by its terms. - -`Struct Iterable` is distributed under the terms of the MIT license. As such, you're free to use, modify, distribute, and privately use it in any way you see fit, in accordance with the terms of the license. diff --git a/iterable/src/lib.rs b/iterable/src/lib.rs deleted file mode 100644 index c1fb55ed6..000000000 --- a/iterable/src/lib.rs +++ /dev/null @@ -1,91 +0,0 @@ -/// The `Iterable` proc macro. -/// -/// This macro provides a convenient way to make a struct iterable. -/// The struct fields' names are returned as static strings and their values as `dyn Any`. -/// This allows to iterate over the struct fields in a generic way. -/// -/// Note that only structs with named fields are supported. -/// -/// # Example -/// -/// ``` -/// use struct_iterable::Iterable; -/// -/// #[derive(Iterable)] -/// struct MyStruct { -/// field1: i32, -/// field2: String, -/// // etc. -/// } -/// -/// let my_instance = MyStruct { -/// field1: 42, -/// field2: "Hello, world!".to_string(), -/// }; -/// -/// for (field_name, field_value) in my_instance.iter() { -/// println!("{}: {:?}", field_name, field_value); -/// } -/// ``` -pub use struct_iterable_derive::Iterable; - -/// The `Iterable` trait. -/// -/// This trait is implemented by the struct once the `Iterable` proc macro is derived. -/// It provides an `iter` method that returns an iterator over tuples of field names and values. -/// -/// # Example -/// -/// ``` -/// use struct_iterable::Iterable; -/// -/// #[derive(Iterable)] -/// struct MyStruct { -/// field1: i32, -/// field2: String, -/// // etc. -/// } -/// -/// let my_instance = MyStruct { -/// field1: 42, -/// field2: "Hello, world!".to_string(), -/// }; -/// -/// for (field_name, field_value) in my_instance.iter() { -/// println!("{}: {:?}", field_name, field_value); -/// } -/// ``` -pub use struct_iterable_internal::Iterable; - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Iterable)] - struct MyStruct { - field1: i32, - field2: String, - // etc. - } - - #[test] - fn it_works() { - let mut my_instance = MyStruct { - field1: 42, - field2: "Hello, world!".to_string(), - }; - - for (field_name, field_value) in my_instance.iter() { - dbg!("{}: {:?}", field_name, field_value); - } - - for (field_name, field_value) in my_instance.iter_mut() { - dbg!("{}: {:?}", field_name, &field_value); - if let Some(i32) = field_value.downcast_mut::() { - *i32 += 1; - dbg!(i32); - } - dbg!("{}: {:?}", field_name, &field_value); - } - } -} diff --git a/iterable/struct_iterable_derive/Cargo.toml b/iterable/struct_iterable_derive/Cargo.toml deleted file mode 100644 index 95d4be924..000000000 --- a/iterable/struct_iterable_derive/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "struct_iterable_derive" -version = "0.1.0" -authors = ["André de Moraes "] -edition = "2021" -description = "An internal crate for struct_iterable" -license = "MIT" - -[lib] -proc-macro = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -syn = "2.0.85" -quote = "1.0.37" -proc-macro2 = "1.0.89" -struct_iterable_internal = { path = "../struct_iterable_internal" } diff --git a/iterable/struct_iterable_derive/README.md b/iterable/struct_iterable_derive/README.md deleted file mode 100644 index 5703ba278..000000000 --- a/iterable/struct_iterable_derive/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Struct Iterable Derive - -This crate is a supporting library for the `struct_iterable` crate. It provides the proc macro `Iterable` which is used in conjunction with the `struct_iterable_internal` crate to provide an easy way to make a struct iterable in Rust. - -**Please note:** This crate is not intended to be used directly. If you want to make your structs iterable, please use the `struct_iterable` crate instead. - -Please visit the [`struct_iterable` crate on crates.io](https://crates.io/crates/struct_iterable) for more information and usage examples. diff --git a/iterable/struct_iterable_derive/src/lib.rs b/iterable/struct_iterable_derive/src/lib.rs deleted file mode 100644 index db2a7a01c..000000000 --- a/iterable/struct_iterable_derive/src/lib.rs +++ /dev/null @@ -1,88 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; - -/// The `Iterable` proc macro. -/// -/// Deriving this macro for your struct will make it "iterable". An iterable struct allows you to iterate over its fields, returning a tuple containing the field name as a static string and a reference to the field's value as `dyn Any`. -/// -/// # Limitations -/// -/// - Only structs are supported, not enums or unions. -/// - Only structs with named fields are supported. -/// -/// # Usage -/// -/// Add the derive attribute (`#[derive(Iterable)]`) above your struct definition. -/// -/// ``` -/// use struct_iterable::Iterable; -/// -/// #[derive(Iterable)] -/// struct MyStruct { -/// field1: i32, -/// field2: String, -/// } -/// ``` -/// -/// You can now call the `iter` method on instances of your struct to get an iterator over its fields: -/// -/// ``` -/// let my_instance = MyStruct { -/// field1: 42, -/// field2: "Hello, world!".to_string(), -/// }; -/// -/// for (field_name, field_value) in my_instance.iter() { -/// println!("{}: {:?}", field_name, field_value); -/// } -/// ``` -#[proc_macro_derive(Iterable)] -pub fn derive_iterable(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let struct_name = input.ident; - let fields = match input.data { - Data::Struct(data_struct) => match data_struct.fields { - Fields::Named(fields_named) => fields_named.named, - _ => panic!("Only structs with named fields are supported"), - }, - _ => panic!("Only structs are supported"), - }; - - let fields_iter = fields.iter().map(|field| { - let field_ident = &field.ident; - let field_name = field_ident.as_ref().unwrap().to_string(); - quote! { - (#field_name, &(self.#field_ident) as &dyn std::any::Any) - } - }); - - let fields_iter_mut = fields.iter().map(|field| { - let field_ident = &field.ident; - let field_name = field_ident.as_ref().unwrap().to_string(); - quote! { - (#field_name, &mut (self.#field_ident) as &mut dyn std::any::Any) - } - }); - - let expanded = quote! { - impl Iterable for #struct_name { - fn iter<'a>(&'a self) -> std::vec::IntoIter<(&'static str, &'a dyn std::any::Any)> { - vec![ - #(#fields_iter),* - ].into_iter() - } - - fn iter_mut<'a>(&'a mut self) -> std::vec::IntoIter<(&'static str, &'a mut dyn std::any::Any)> { - vec![ - #(#fields_iter_mut),* - ].into_iter() - } - } - }; - - TokenStream::from(expanded) -} diff --git a/iterable/struct_iterable_internal/Cargo.toml b/iterable/struct_iterable_internal/Cargo.toml deleted file mode 100644 index cdf06702c..000000000 --- a/iterable/struct_iterable_internal/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "struct_iterable_internal" -version = "0.1.1" -authors = ["André de Moraes "] -edition = "2021" -description = "An internal crate for struct_iterable" -license = "MIT" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/iterable/struct_iterable_internal/README.md b/iterable/struct_iterable_internal/README.md deleted file mode 100644 index 462811fe4..000000000 --- a/iterable/struct_iterable_internal/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Struct Iterable Internal - -This crate is a supporting library for the `struct_iterable` crate. It provides the `Iterable` trait which is used in conjunction with the `struct_iterable_derive` crate to provide an easy way to make a struct iterable in Rust. - -**Please note:** This crate is not intended to be used directly. If you want to make your structs iterable, please use the `struct_iterable` crate instead. - -Please visit the [`struct_iterable` crate on crates.io](https://crates.io/crates/struct_iterable) for more information and usage examples. diff --git a/iterable/struct_iterable_internal/src/lib.rs b/iterable/struct_iterable_internal/src/lib.rs deleted file mode 100644 index 1b5c7bb1c..000000000 --- a/iterable/struct_iterable_internal/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -/// The `Iterable` trait. -/// -/// This trait is implemented for structs that derive the `Iterable` proc macro. -/// It provides the `iter` method which returns an iterator over the struct's fields as tuples, containing the field name as a static string and a reference to the field's value as `dyn Any`. -/// -/// You usually don't need to implement this trait manually, as it is automatically derived when using the `#[derive(Iterable)]` proc macro. -/// -/// # Example -/// -/// ``` -/// use struct_iterable::Iterable; -/// -/// #[derive(Iterable)] -/// struct MyStruct { -/// field1: i32, -/// field2: String, -/// } -/// -/// let my_instance = MyStruct { -/// field1: 42, -/// field2: "Hello, world!".to_string(), -/// }; -/// -/// // Iterate over the fields of `my_instance`: -/// for (field_name, field_value) in my_instance.iter() { -/// println!("{}: {:?}", field_name, field_value); -/// } -/// ``` -pub trait Iterable { - /// Returns an iterator over the struct's fields as tuples. - /// - /// Each tuple contains a field's name as a static string and a reference to the field's value as `dyn Any`. - /// - /// # Example - /// - /// ``` - /// use struct_iterable::Iterable; - /// - /// #[derive(Iterable)] - /// struct MyStruct { - /// field1: i32, - /// field2: String, - /// } - /// - /// let my_instance = MyStruct { - /// field1: 42, - /// field2: "Hello, world!".to_string(), - /// }; - /// - /// // Iterate over the fields of `my_instance`: - /// for (field_name, field_value) in my_instance.iter() { - /// println!("{}: {:?}", field_name, field_value); - /// } - /// ``` - fn iter(&self) -> std::vec::IntoIter<(&'static str, &'_ dyn std::any::Any)>; - - fn iter_mut(&mut self) -> std::vec::IntoIter<(&'static str, &'_ mut dyn std::any::Any)>; -} diff --git a/parser/Cargo.toml b/parser/Cargo.toml deleted file mode 100644 index bf12d64de..000000000 --- a/parser/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "parser" -version = "0.5.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -allocative = "0.3.3" -bincode = { git = "https://github.com/bincode-org/bincode.git", features = [ - "serde", -] } -bitcoin_hashes = { version = "0.14.0" } -biter = { path = "../biter" } -chrono = { version = "0.4.38", features = ["serde"] } -clap = { version = "4.5.20", features = ["derive"] } -color-eyre = "0.6.3" -ctrlc = { version = "3.4.5", features = ["termination"] } -derive_deref = "1.1.1" -inferno = "0.11.21" -itertools = "0.13.0" -ordered-float = "4.4.0" -rayon = "1.10.0" -reqwest = { version = "0.12.9", features = ["blocking", "json"] } -sanakirja = "1.4.3" -serde = { version = "1.0.214", features = ["derive"] } -serde_json = "1.0.132" -struct_iterable = { path = "../iterable" } -toml = "0.8.19" -zstd = "0.13.2" -# memory-stats = "1.2.0" -# sysinfo = "0.32.0" diff --git a/parser/reset.sh b/parser/reset.sh deleted file mode 100755 index d7a64be38..000000000 --- a/parser/reset.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -echo "Deleting datasets..." -rm -r ../datasets -echo "Deleting states and databases..." -rm -r ./out -echo "Done." diff --git a/parser/run.sh b/parser/run.sh deleted file mode 100755 index b82977f49..000000000 --- a/parser/run.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -# https://stackoverflow.com/questions/31389483/find-and-delete-file-or-folder-older-than-x-days - -if command -v ulimit &> /dev/null; then - echo "Increasing limit of opened files..." - ulimit -n 1000000 # Can't be $(ulimit -Hn), bitcoind needs some too ! - - # Needed because the datasets tree is too big lol - echo "Increasing stack size..." - ulimit -s $(ulimit -Hs) -fi - -# For Mac OS users -if [ "$(uname)" == "Darwin" ]; then - if mdutil -s / | grep "enabled"; then - echo "Disabling spotlight indexing..." - sudo mdutil -a -i off &> /dev/null - fi - - echo "Thinning local TimeMachine snapshots..." - tmutil thinlocalsnapshots / &> /dev/null -fi - -cargo run -r -- "$@" diff --git a/parser/samply.sh b/parser/samply.sh deleted file mode 100755 index 6376c139a..000000000 --- a/parser/samply.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -echo "Increasing limit of opened files..." -ulimit -n 1000000 - -# Needed because the datasets tree is too big lol -echo "Increasing stack size..." -ulimit -s $(ulimit -Hs) - -cargo build --profile profiling && samply record ./target/profiling/parser "$HOME/Developer/bitcoin" diff --git a/parser/src/actions/export.rs b/parser/src/actions/export.rs deleted file mode 100644 index 88af14a76..000000000 --- a/parser/src/actions/export.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::thread::{self}; - -use crate::{ - databases::Databases, - datasets::AllDatasets, - states::States, - structs::{Date, Height}, - utils::{log, time}, - Exit, -}; - -pub struct ExportedData<'a> { - pub databases: Option<&'a mut Databases>, - pub datasets: &'a mut AllDatasets, - pub date: Date, - pub height: Height, - pub states: Option<&'a States>, - pub exit: Exit, -} - -pub fn export( - ExportedData { - databases, - datasets, - states, - height, - date, - exit, - }: ExportedData, -) -> color_eyre::Result<()> { - if exit.active() { - log("Exit in progress, skipping export"); - return Ok(()); - } - - exit.block(); - - log("Exporting..."); - - time("Total save time", || -> color_eyre::Result<()> { - time("Datasets saved", || datasets.export())?; - - thread::scope(|s| { - if let Some(databases) = databases { - s.spawn(|| time("Databases saved", || databases.export(height, date))); - } - - if let Some(states) = states { - s.spawn(|| time("States saved", || states.export())); - } - }); - - Ok(()) - })?; - - exit.unblock(); - - Ok(()) -} diff --git a/parser/src/databases/_database.rs b/parser/src/databases/_database.rs deleted file mode 100644 index abc695f56..000000000 --- a/parser/src/databases/_database.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::Debug, - fs, mem, - path::PathBuf, -}; - -use allocative::Allocative; - -// https://docs.rs/sanakirja/latest/sanakirja/index.html -// https://pijul.org/posts/2021-02-06-rethinking-sanakirja/ -// -// Seems indeed much faster than ReDB and LMDB (heed) -// But a lot has changed code wise between them so a retest wouldn't hurt -// -// Possible compression: https://pijul.org/posts/sanakirja-zstd/ -use sanakirja::{ - btree::{self, page, Db_, Iter}, - Commit, Env, Error, MutTxn, RootDb, Storable, -}; - -#[derive(Allocative)] -#[allocative(bound = "Key: Allocative, Value: Allocative")] -/// There is no `cached_gets` since it's much cheaper and faster to do a parallel search first using `unsafe_get` than caching gets along the way. -pub struct Database -where - Key: Ord + Clone + Debug + Storable, - Value: Storable + PartialEq, -{ - pub cached_puts: BTreeMap, - pub cached_dels: BTreeSet, - path: PathBuf, - #[allocative(skip)] - db: Db_>, - #[allocative(skip)] - txn: MutTxn, -} - -const ROOT_DB: usize = 0; -const PAGE_SIZE: u64 = 4096; - -impl Database -where - Key: Ord + Clone + Debug + Storable, - Value: Storable + PartialEq, -{ - pub fn open(path: PathBuf) -> color_eyre::Result { - let env = unsafe { Env::new_nolock(&path, PAGE_SIZE, 1)? }; - - let mut txn = Env::mut_txn_begin(env)?; - - let db = txn - .root_db(ROOT_DB) - .unwrap_or_else(|| unsafe { btree::create_db_(&mut txn).unwrap() }); - - Ok(Self { - path, - cached_puts: BTreeMap::default(), - cached_dels: BTreeSet::default(), - db, - txn, - }) - } - - pub fn iter(&self) -> Iter<'_, MutTxn, Key, Value, page::Page> { - btree::iter(&self.txn, &self.db, None).unwrap() - } - - pub fn iter_collect(&self) -> BTreeMap - where - Value: Clone, - { - self.iter() - .map(|r| r.unwrap()) - .map(|(key, value)| (key.clone(), value.clone())) - .collect::<_>() - } - - pub fn get(&self, key: &Key) -> Option<&Value> { - if let Some(cached_put) = self.get_from_puts(key) { - return Some(cached_put); - } - - self.db_get(key) - } - - pub fn db_get(&self, key: &Key) -> Option<&Value> { - let option = btree::get(&self.txn, &self.db, key, None).unwrap(); - - if let Some((key_found, v)) = option { - if key == key_found { - return Some(v); - } - } - - None - } - - #[inline(always)] - pub fn get_from_puts(&self, key: &Key) -> Option<&Value> { - self.cached_puts.get(key) - } - - #[inline(always)] - pub fn get_mut_from_puts(&mut self, key: &Key) -> Option<&mut Value> { - self.cached_puts.get_mut(key) - } - - #[inline(always)] - pub fn remove(&mut self, key: &Key) -> Option { - self.remove_from_puts(key).or_else(|| { - self.db_remove(key); - - None - }) - } - - #[inline] - pub fn db_remove(&mut self, key: &Key) { - self.cached_dels.insert(key.clone()); - } - - #[inline] - pub fn update(&mut self, key: Key, value: Value) -> Option { - self.cached_dels.insert(key.clone()); - self.cached_puts.insert(key, value) - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.iter().next().is_none() - } - - #[inline] - pub fn remove_from_puts(&mut self, key: &Key) -> Option { - self.cached_puts.remove(key) - } - - #[inline] - pub fn insert(&mut self, key: Key, value: Value) -> Option { - self.cached_dels.remove(&key); - - self.unsafe_insert(key, value) - } - - #[inline] - pub fn unsafe_insert(&mut self, key: Key, value: Value) -> Option { - self.cached_puts.insert(key, value) - } - - fn db_multi_put(&mut self, tree: BTreeMap) -> Result<(), Error> { - tree.into_iter() - .try_for_each(|(key, value)| -> Result<(), Error> { - btree::put(&mut self.txn, &mut self.db, &key, &value)?; - Ok(()) - }) - } - - fn db_multi_del(&mut self, tree: BTreeSet) -> Result<(), Error> { - tree.into_iter().try_for_each(|key| -> Result<(), Error> { - btree::del(&mut self.txn, &mut self.db, &key, None)?; - Ok(()) - }) - } -} - -pub trait AnyDatabase { - fn export(self) -> color_eyre::Result<(), Error>; - fn boxed_export(self: Box) -> color_eyre::Result<(), Error>; - #[allow(unused)] - fn defragment(self); - fn boxed_defragment(self: Box); - fn destroy(self); -} - -impl AnyDatabase for Database -where - Key: Ord + Clone + Debug + Storable, - Value: Storable + PartialEq + Clone, -{ - fn export(self) -> color_eyre::Result<(), Error> { - Box::new(self).boxed_export() - } - - fn boxed_export(mut self: Box) -> color_eyre::Result<(), Error> { - if self.cached_dels.is_empty() && self.cached_puts.is_empty() { - return Ok(()); - } - - let cached_dels = mem::take(&mut self.cached_dels); - self.db_multi_del(cached_dels)?; - - let cached_puts = mem::take(&mut self.cached_puts); - self.db_multi_put(cached_puts)?; - - self.txn.set_root(ROOT_DB, self.db.db.into()); - - self.txn.commit() - } - - fn defragment(self) { - Box::new(self).boxed_defragment() - } - - fn boxed_defragment(self: Box) { - let btree = self.iter_collect(); - - let path = self.path.to_owned(); - - self.destroy(); - - let mut db = Self::open(path).unwrap(); - - if !db.is_empty() { - panic!() - } - - db.cached_puts = btree; - db.export().unwrap(); - } - - fn destroy(self) { - let path = self.path.to_owned(); - - drop(self); - - fs::remove_file(&path).unwrap_or_else(|_| { - dbg!(path); - panic!("Error"); - }); - } -} diff --git a/parser/src/databases/_trait.rs b/parser/src/databases/_trait.rs deleted file mode 100644 index acf040d0b..000000000 --- a/parser/src/databases/_trait.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::{fs, io, path::PathBuf}; - -use crate::{ - io::OUTPUTS_FOLDER_PATH, - structs::{Date, Height}, - utils::log, -}; - -use super::AnyDatabase; - -pub trait AnyDatabaseGroup -where - Self: Sized, -{ - fn init() -> Self { - let s = Self::import(); - s.create_dir_all().unwrap(); - s - } - - fn import() -> Self; - - fn folder<'a>() -> &'a str; - - fn drain_to_vec(&mut self) -> Vec>; - fn open_all(&mut self); - - fn export_metadata(&mut self, height: Height, date: Date) -> color_eyre::Result<()>; - - fn create_dir_all(&self) -> color_eyre::Result<(), io::Error>; - - fn remove_dir_all(&self) -> color_eyre::Result<(), io::Error> { - fs::remove_dir_all(Self::root()) - } - - fn reset(&mut self) -> color_eyre::Result<(), io::Error> { - log(&format!("Reset {}", Self::folder())); - - self.reset_metadata(); - self.remove_dir_all()?; - self.create_dir_all()?; - - Ok(()) - } - - fn reset_metadata(&mut self); - - fn root() -> PathBuf { - let folder = Self::folder(); - PathBuf::from(format!("{OUTPUTS_FOLDER_PATH}/databases/{folder}")) - } -} diff --git a/parser/src/io/binary.rs b/parser/src/io/binary.rs deleted file mode 100644 index 202a6bdfb..000000000 --- a/parser/src/io/binary.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::{ - fmt::Debug, - fs::{self, File}, - io::{BufReader, BufWriter, Cursor}, - path::Path, -}; - -use bincode::{ - config, decode_from_slice, decode_from_std_read, encode_into_std_write, Decode, Encode, -}; -use zstd::decode_all; - -const ZST_EXTENSION: &str = "zst"; - -pub const BIN_EXTENSION: &str = "bin"; -pub const COMPRESSED_BIN_EXTENSION: &str = "bin.zst"; - -enum BinaryType { - Raw, - Compressed, -} - -pub struct Binary; - -impl Binary { - pub fn import(path: &Path) -> color_eyre::Result - where - T: Decode, - { - match Self::type_from_path(path) { - BinaryType::Compressed => Self::import_compressed(path), - BinaryType::Raw => Self::import_raw(path), - } - } - - fn import_raw(path: &Path) -> color_eyre::Result - where - T: Decode, - { - let config = config::standard(); - - let file = File::open(path)?; - - let mut reader = BufReader::new(file); - - let decoded = decode_from_std_read(&mut reader, config)?; - - Ok(decoded) - } - - fn import_compressed(path: &Path) -> color_eyre::Result - where - T: Decode, - { - let file = File::open(path).unwrap(); - - let reader = BufReader::new(file); - - let decompressed = decode_all(reader).unwrap(); - - let config = config::standard(); - - let decoded = decode_from_slice::(&decompressed, config)?.0; - - Ok(decoded) - } - - pub fn export(path: &Path, value: &T) -> color_eyre::Result<()> - where - T: Debug + Encode, - { - // log(&format!("Exporting: {:?}", path)); - - match Self::type_from_path(path) { - BinaryType::Compressed => Self::export_compressed(path, value), - BinaryType::Raw => Self::export_raw(path, value), - } - } - - fn export_raw(path: &Path, value: &T) -> color_eyre::Result<()> - where - T: Debug + Encode, - { - let config = config::standard(); - - let file = File::create(path).inspect_err(|_| { - dbg!(path, value); - })?; - - let mut writer = BufWriter::new(file); - - encode_into_std_write(value, &mut writer, config)?; - - Ok(()) - } - - fn export_compressed(path: &Path, value: &T) -> color_eyre::Result<()> - where - T: Debug + Encode, - { - let config = config::standard(); - - let encoded = bincode::encode_to_vec(value, config).unwrap(); - - let cursor = Cursor::new(encoded); - - let compressed = zstd::encode_all(cursor, 0).unwrap(); - - fs::write(path, compressed).unwrap(); - - Ok(()) - } - - pub fn has_correct_extension(path: &Path) -> bool { - let path = path.to_str().unwrap(); - path.ends_with(BIN_EXTENSION) || path.ends_with(COMPRESSED_BIN_EXTENSION) - } - - fn type_from_path(path: &Path) -> BinaryType { - let extension = path.extension(); - - if extension.is_none() { - panic!("Should have extension"); - } - - if !Self::has_correct_extension(path) { - dbg!(path); - panic!("Wrong extension") - } - - let extension = extension.unwrap(); - - if extension == ZST_EXTENSION { - BinaryType::Compressed - } else { - BinaryType::Raw - } - } -} diff --git a/parser/src/io/consts.rs b/parser/src/io/consts.rs deleted file mode 100644 index 750f7edb0..000000000 --- a/parser/src/io/consts.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub const INPUTS_FOLDER_PATH: &str = "./in"; -pub const OUTPUTS_FOLDER_PATH: &str = "./out"; diff --git a/parser/src/io/json.rs b/parser/src/io/json.rs deleted file mode 100644 index 6d81fe580..000000000 --- a/parser/src/io/json.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::{ - fs::File, - io::{BufReader, BufWriter}, - path::Path, -}; - -use serde::{de::DeserializeOwned, Serialize}; - -pub struct Json; - -pub const JSON_EXTENSION: &str = "json"; -pub const HAR_EXTENSION: &str = "har"; - -impl Json { - pub fn import(path: &Path) -> color_eyre::Result - where - T: DeserializeOwned, - { - if !Self::has_correct_extension(path) { - panic!("Wrong extension"); - } - - let file = File::open(path)?; - - let reader = BufReader::new(file); - - Ok(serde_json::from_reader(reader)?) - } - - pub fn export(path: &Path, value: &T) -> color_eyre::Result<()> - where - T: Serialize, - { - if !Self::has_correct_extension(path) { - dbg!(path); - panic!("Wrong extension"); - } - - let file = File::create(path).unwrap_or_else(|_| { - dbg!(&path); - panic!("No such file or directory") - }); - - let mut writer = BufWriter::new(file); - - serde_json::to_writer_pretty(&mut writer, value)?; - - Ok(()) - } - - #[inline(always)] - pub fn has_correct_extension(path: &Path) -> bool { - let path = path.to_str().unwrap(); - path.ends_with(JSON_EXTENSION) || path.ends_with(HAR_EXTENSION) - } -} diff --git a/parser/src/io/mod.rs b/parser/src/io/mod.rs deleted file mode 100644 index 674784567..000000000 --- a/parser/src/io/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod binary; -mod consts; -mod json; -mod serialization; - -pub use binary::*; -pub use consts::*; -pub use json::*; -pub use serialization::*; diff --git a/parser/src/io/serialization.rs b/parser/src/io/serialization.rs deleted file mode 100644 index e9cd8137d..000000000 --- a/parser/src/io/serialization.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::{fmt::Debug, fs, path::Path}; - -use allocative::Allocative; -use bincode::{Decode, Encode}; -use color_eyre::eyre::eyre; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::io::{Binary, Json}; - -use super::{BIN_EXTENSION, COMPRESSED_BIN_EXTENSION, HAR_EXTENSION, JSON_EXTENSION}; - -#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy, Default, Allocative)] -pub enum Serialization { - #[default] - Binary, - Json, -} - -impl Serialization { - pub fn is_serializable(&self, path: &Path) -> bool { - let path = path.to_str().unwrap(); - match self { - Self::Binary => { - path.ends_with(BIN_EXTENSION) || path.ends_with(COMPRESSED_BIN_EXTENSION) - } - Self::Json => path.ends_with(JSON_EXTENSION) || path.ends_with(HAR_EXTENSION), - } - } - - pub fn from_path(path: &Path) -> Self { - let path = path.to_str().unwrap(); - if path.ends_with(BIN_EXTENSION) || path.ends_with(COMPRESSED_BIN_EXTENSION) { - Self::Binary - } else if path.ends_with(JSON_EXTENSION) || path.ends_with(HAR_EXTENSION) { - Self::Json - } else { - panic!("Extension \"{path}\" isn't supported") - } - } - - pub fn import(&self, path: &Path) -> color_eyre::Result - where - T: Debug + DeserializeOwned + Decode, - { - match self { - Serialization::Binary => { - if self.is_serializable(path) { - Binary::import(path) - } else { - let path = path.to_str().unwrap(); - let bin_path_str = format!("{path}.{BIN_EXTENSION}"); - let bin_path = Path::new(&bin_path_str); - - if bin_path.exists() { - return Binary::import(bin_path); - } - - let compressed_bin_path_str = format!("{path}.{COMPRESSED_BIN_EXTENSION}"); - let compressed_bin_path = Path::new(&compressed_bin_path_str); - - if compressed_bin_path.exists() { - return Binary::import(compressed_bin_path); - } - - Err(eyre!("Wrong path or no file")) - } - } - Serialization::Json => { - if self.is_serializable(path) { - Json::import(path) - } else { - let path = path.to_str().unwrap(); - let json_path_str = format!("{path}.{JSON_EXTENSION}"); - let json_path = Path::new(&json_path_str); - - if json_path.exists() { - return Json::import(json_path); - } - - Err(eyre!("Wrong path or no file")) - } - } - } - } - - pub fn export(&self, path: &Path, value: &T) -> color_eyre::Result<()> - where - T: Debug + Serialize + Encode, - { - match self { - Serialization::Binary => { - if self.is_serializable(path) { - Binary::export(path, value) - } else { - let path = path.to_str().unwrap(); - - let res = Binary::export( - Path::new(&format!("{}.{COMPRESSED_BIN_EXTENSION}", path)), - value, - ); - - if res.is_ok() { - let _ = fs::remove_file(Path::new(&format!("{}.{BIN_EXTENSION}", path))); - } - - res - } - } - Serialization::Json => { - if self.is_serializable(path) { - Json::export(path, value) - } else { - Json::export( - Path::new(&format!("{}.{JSON_EXTENSION}", path.to_str().unwrap())), - value, - ) - } - } - } - } -} diff --git a/parser/src/lib.rs b/parser/src/lib.rs deleted file mode 100644 index c0b5efd29..000000000 --- a/parser/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod actions; -mod databases; -mod datasets; -mod io; -mod price; -mod states; -mod structs; -mod utils; - -pub use crate::{ - actions::iter_blocks, - databases::{AnyDatabase, Database}, - io::{Binary, Json, Serialization, COMPRESSED_BIN_EXTENSION, JSON_EXTENSION}, - structs::{ - Amount, Config, Date, DateMap, Exit, Height, HeightMap, MapChunkId, MapValue, - SerializedBTreeMap, SerializedVec, TxoutIndex, HEIGHT_MAP_CHUNK_SIZE, OHLC, - }, - utils::{create_rpc, log, reset_logs}, -}; diff --git a/parser/src/main.rs b/parser/src/main.rs deleted file mode 100644 index a95380569..000000000 --- a/parser/src/main.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::{thread::sleep, time::Duration}; - -use biter::bitcoincore_rpc::RpcApi; -use parser::{create_rpc, iter_blocks, log, reset_logs, Config, Exit}; - -fn main() -> color_eyre::Result<()> { - color_eyre::install()?; - - reset_logs(); - - let mut config = Config::import()?; - - let rpc = create_rpc(&config).unwrap(); - - let exit = Exit::new(); - - loop { - let block_count = rpc.get_blockchain_info().unwrap().blocks as usize; - - log(&format!("{block_count} blocks found.")); - - iter_blocks(&mut config, &rpc, block_count, exit.clone())?; - - if let Some(delay) = config.delay { - sleep(Duration::from_secs(delay)) - } - - log("Waiting for a new block...\n"); - - while block_count == rpc.get_blockchain_info().unwrap().blocks as usize { - sleep(Duration::from_secs(1)) - } - } - - // Ok(()) -} diff --git a/parser/src/states/_trait.rs b/parser/src/states/_trait.rs deleted file mode 100644 index 2b82fbd94..000000000 --- a/parser/src/states/_trait.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::{fmt::Debug, fs, io, path::Path}; - -use bincode::{Decode, Encode}; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::{io::OUTPUTS_FOLDER_PATH, Serialization}; - -// https://github.com/djkoloski/rust_serialization_benchmark -pub trait AnyState -where - Self: Debug + Encode + Decode + Serialize + DeserializeOwned, -{ - fn name<'a>() -> &'a str; - - fn create_dir_all() -> color_eyre::Result<(), io::Error> { - fs::create_dir_all(Self::folder_path()) - } - - fn folder_path() -> String { - format!("{OUTPUTS_FOLDER_PATH}/states") - } - - fn full_path() -> String { - let name = Self::name(); - - let folder_path = Self::folder_path(); - - format!("{folder_path}/{name}") - } - - fn reset(&mut self) -> color_eyre::Result<(), io::Error> { - self.clear(); - - fs::remove_file(Self::full_path()) - } - - fn import() -> color_eyre::Result { - Self::create_dir_all()?; - - Serialization::Binary.import(Path::new(&Self::full_path())) - } - - fn export(&self) -> color_eyre::Result<()> { - Serialization::Binary.export(Path::new(&Self::full_path()), self) - } - - fn clear(&mut self); -} diff --git a/parser/src/structs/any_map.rs b/parser/src/structs/any_map.rs deleted file mode 100644 index 7f816b1c3..000000000 --- a/parser/src/structs/any_map.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::path::{Path, PathBuf}; - -use serde_json::Value; - -use super::MapKind; - -pub trait AnyMap { - fn path(&self) -> &Path; - fn path_last(&self) -> &Option; - - fn last_value(&self) -> Option; - - fn t_name(&self) -> &str; - - fn exported_path_with_t_name(&self) -> Vec<(&Path, &str)> { - let t_name = self.t_name(); - - if let Some(path_last) = self.path_last() { - vec![(self.path(), t_name), (path_last, t_name)] - } else { - vec![(self.path(), t_name)] - } - } - - fn pre_export(&mut self); - fn export(&self) -> color_eyre::Result<()>; - fn post_export(&mut self); - - fn delete_files(&self); - - fn kind(&self) -> MapKind; -} diff --git a/parser/src/structs/config.rs b/parser/src/structs/config.rs deleted file mode 100644 index db7287c24..000000000 --- a/parser/src/structs/config.rs +++ /dev/null @@ -1,215 +0,0 @@ -use std::{ - fs::{self}, - path::{Path, PathBuf}, -}; - -use biter::bitcoincore_rpc::Auth; -use clap::Parser; -use color_eyre::eyre::eyre; -use serde::{Deserialize, Serialize}; - -use crate::log; - -#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[command(version, about, long_about = None)] -pub struct Config { - /// Bitcoin data directory path, saved - #[arg(long, value_name = "DIR")] - pub datadir: Option, - - /// Bitcoin RPC ip, default: localhost, saved - #[arg(long, value_name = "IP")] - pub rpcconnect: Option, - - /// Bitcoin RPC port, default: 8332, saved - #[arg(long, value_name = "PORT")] - pub rpcport: Option, - - /// Bitcoin RPC cookie file, saved - #[arg(long, value_name = "PATH")] - pub rpccookiefile: Option, - - /// Bitcoin RPC username, saved - #[arg(long, value_name = "USERNAME")] - pub rpcuser: Option, - - /// Bitcoin RPC password, saved - #[arg(long, value_name = "PASSWORD")] - pub rpcpassword: Option, - - /// Delay between runs, default: 0, saved - #[arg(long, value_name = "SECONDS")] - pub delay: Option, - - // Maximum ram you want the program to use in GB, default: 50% of total, not saved - // #[arg(long, value_name = "GB")] - // pub max_ram: Option, - /// Start a dry run, default: false, not saved - #[arg(long, value_name = "BOOL")] - dry_run: Option, - - /// Record ram usage, default: false, not saved - #[arg(long, value_name = "BOOL")] - record_ram_usage: Option, - - /// Recompute all computed datasets, default: false, not saved - #[arg(long, value_name = "BOOL")] - recompute_computed: Option, - - /// Start the program by defragmenting all databases to reduce their footprint, default: false, not saved - #[arg(long, value_name = "BOOL")] - first_defragment: Option, -} - -impl Config { - const PATH: &'static str = "./config.toml"; - - pub fn import() -> color_eyre::Result { - let mut config_saved = fs::read_to_string(Self::PATH) - .map_or(Config::default(), |contents| { - toml::from_str(&contents).unwrap_or_default() - }); - - let mut config_args = Config::parse(); - - if let Some(datadir) = config_args.datadir.take() { - config_saved.datadir = Some(datadir); - } - - if let Some(rpcconnect) = config_args.rpcconnect.take() { - config_saved.rpcconnect = Some(rpcconnect); - } - - if let Some(rpcport) = config_args.rpcport.take() { - config_saved.rpcport = Some(rpcport); - } - - if let Some(rpccookiefile) = config_args.rpccookiefile.take() { - config_saved.rpccookiefile = Some(rpccookiefile); - } - - if let Some(rpcuser) = config_args.rpcuser.take() { - config_saved.rpcuser = Some(rpcuser); - } - - if let Some(rpcpassword) = config_args.rpcpassword.take() { - config_saved.rpcpassword = Some(rpcpassword); - } - - if let Some(delay) = config_args.delay.take() { - config_saved.delay = Some(delay); - } - - // if let Some(max_ram) = config_args.max_ram.take() { - // config_saved.max_ram = Some(max_ram); - // } - - // Done importing - - let mut config = config_saved; - - config.check(); - - config.write()?; - - config.dry_run = config_args.dry_run.take(); - config.record_ram_usage = config_args.record_ram_usage.take(); - config.recompute_computed = config_args.recompute_computed.take(); - config.first_defragment = config_args.first_defragment.take(); - - log("---"); - log("Configuration:"); - log(&format!("datadir: {:?}", config.datadir)); - log(&format!("rpcconnect: {:?}", config.rpcconnect)); - log(&format!("rpcport: {:?}", config.rpcport)); - log(&format!("rpccookiefile: {:?}", config.rpccookiefile)); - log(&format!("rpcuser: {:?}", config.rpcuser)); - log(&format!("rpcpassword: {:?}", config.rpcpassword)); - log(&format!("delay: {:?}", config.delay)); - // log(&format!("max_ram: {:?}", config.max_ram)); - log(&format!("dry_run: {:?}", config.dry_run)); - log(&format!("record_ram_usage: {:?}", config.record_ram_usage)); - log(&format!( - "recompute_computed: {:?}", - config.recompute_computed - )); - log(&format!("first_defragment: {:?}", config.first_defragment)); - log("---"); - - if config_args != Config::default() { - dbg!(config_args); - panic!("Didn't consume the full config") - } - - Ok(config) - } - - fn check(&self) { - if self.datadir.is_none() { - println!( - "You need to set the --datadir parameter at least once to run the parser.\nRun the program with '-h' for help." - ); - std::process::exit(1); - } - - let path = Path::new(self.datadir.as_ref().unwrap()); - if !path.is_dir() { - println!("Expect path '{:#?}' to be a directory.", path); - std::process::exit(1); - } - - if self.to_rpc_auth().is_err() { - println!( - "No way found to authenticate the RPC client, please either set --rpccookiefile or --rpcuser and --rpcpassword.\nRun the program with '-h' for help." - ); - std::process::exit(1); - } - } - - fn write(&self) -> std::io::Result<()> { - fs::write(Self::PATH, toml::to_string(self).unwrap()) - } - - pub fn to_rpc_auth(&self) -> color_eyre::Result { - let cookie = Path::new(self.datadir.as_ref().unwrap()).join(".cookie"); - - if cookie.is_file() { - Ok(Auth::CookieFile(cookie)) - } else if self - .rpccookiefile - .as_ref() - .is_some_and(|cookie| Path::new(cookie).is_file()) - { - Ok(Auth::CookieFile(PathBuf::from( - self.rpccookiefile.as_ref().unwrap(), - ))) - } else if self.rpcuser.is_some() && self.rpcpassword.is_some() { - Ok(Auth::UserPass( - self.rpcuser.clone().unwrap(), - self.rpcpassword.clone().unwrap(), - )) - } else { - Err(eyre!("Failed to find correct auth")) - } - } - - pub fn dry_run(&self) -> bool { - self.dry_run.is_some_and(|b| b) - } - - pub fn record_ram_usage(&self) -> bool { - self.record_ram_usage.is_some_and(|b| b) - } - - pub fn recompute_computed(&self) -> bool { - self.recompute_computed.is_some_and(|b| b) - } - - pub fn first_defragment(&self) -> bool { - self.first_defragment.is_some_and(|b| b) - } - - pub fn disable_defragment(&mut self) { - self.first_defragment.take(); - } -} diff --git a/parser/src/utils/log.rs b/parser/src/utils/log.rs deleted file mode 100644 index d6593f75f..000000000 --- a/parser/src/utils/log.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::{ - fs::{self, OpenOptions}, - io::Write, -}; - -use chrono::Local; -use color_eyre::owo_colors::OwoColorize; - -const LOG_PATH: &str = "./.log"; - -pub fn reset_logs() { - let _ = fs::remove_file(LOG_PATH); -} - -#[inline(always)] -pub fn log(str: &str) { - let date_time = format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S -")); - - str.lines() - .filter(|line| !line.is_empty()) - .for_each(|line| { - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(LOG_PATH) - .unwrap(); - - if let Err(e) = writeln!(file, "{} {}", date_time, line) { - eprintln!("Couldn't write to file: {}", e); - } - - println!("{} {}", date_time.bright_black(), line); - }); -} diff --git a/parser/src/utils/rpc.rs b/parser/src/utils/rpc.rs deleted file mode 100644 index 4e65bd8aa..000000000 --- a/parser/src/utils/rpc.rs +++ /dev/null @@ -1,17 +0,0 @@ -use biter::bitcoincore_rpc::Client; - -use crate::Config; - -pub fn create_rpc(config: &Config) -> color_eyre::Result { - Ok(Client::new( - &format!( - "http://{}:{}", - config - .rpcconnect - .as_ref() - .unwrap_or(&"localhost".to_owned()), - config.rpcport.unwrap_or(8332) - ), - config.to_rpc_auth().unwrap(), - )?) -} diff --git a/parser/src/utils/time.rs b/parser/src/utils/time.rs deleted file mode 100644 index ec5c090f1..000000000 --- a/parser/src/utils/time.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::time::Instant; - -use crate::utils::log; - -pub fn time(name: &str, function: F) -> T -where - F: FnOnce() -> T, -{ - let time = Instant::now(); - - let returned = function(); - - log(&format!("{name}: {} seconds", time.elapsed().as_secs_f32())); - - returned -} diff --git a/python/parse.py b/python/parse.py new file mode 100644 index 000000000..bffdf2c0f --- /dev/null +++ b/python/parse.py @@ -0,0 +1,27 @@ +# Here's an example on how to parse the output from the indexer +# We're aiming to read the first 21 values from the height_to_timestamp vec + +import sys +# import struct +import datetime + +with open("../_outputs/indexes/vecs/height_to_timestamp/vec", "rb") as file: + for x in range(0, 21): + b = file.read(4) # Need to check the rust side to find the size, at least for now + number = int.from_bytes(b, sys.byteorder) + date = datetime.date.fromtimestamp(number) + print(date) + +# print(int.from_bytes([21], sys.byteorder)) # 21 u8 native endian +# print(int.from_bytes([21, 0], sys.byteorder)) # 21 u16 native endian +# print(int.from_bytes([21, 0, 0, 0], sys.byteorder)) # 21 u32 native endian +# print(int.from_bytes([21, 0, 0, 0, 0, 0, 0, 0], sys.byteorder)) # 21 u64/usize native endian + +# # check i8, ... + +# print(struct.unpack('f', bytes([0, 0, 168, 65]))) # 21.0 f32 native endian +# print(struct.unpack('d', bytes([0, 0, 0, 0, 0, 0, 53, 64]))) # 21.0 f64 native endian +# print(struct.unpack('f', bytes([65, 168, 0, 0]))) # 21.0 f32 big endian +# print(struct.unpack('>d', bytes([64, 53, 0, 0, 0, 0, 0, 0]))) # 21.0 f64 big endian diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..753065179 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 diff --git a/server/Cargo.lock b/server/Cargo.lock deleted file mode 100644 index 540a98d4f..000000000 --- a/server/Cargo.lock +++ /dev/null @@ -1,4442 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "serde", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "allocative" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082af274fd02beef17b7f0725a49ecafe6c075ef56cac9d6363eb3916a9817ae" -dependencies = [ - "allocative_derive", - "ctor", -] - -[[package]] -name = "allocative_derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" - -[[package]] -name = "anstyle-parse" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "ast_node" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4d23a6d1d5f18bdbc06d9aa908880e5f49205156ba804751af731c51f5cf81a" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.85", -] - -[[package]] -name = "async-compression" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429" -dependencies = [ - "brotli", - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "zstd", - "zstd-safe", -] - -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "auto_impl" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "axum" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.1", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.1", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.4", - "object", - "rustc-demangle", -] - -[[package]] -name = "base58ck" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" -dependencies = [ - "bitcoin-internals", - "bitcoin_hashes", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64-simd" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" -dependencies = [ - "simd-abstraction", -] - -[[package]] -name = "bech32" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" - -[[package]] -name = "better_scoped_tls" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794edcc9b3fb07bb4aecaa11f093fd45663b4feadb782d68303a2268bc2701de" -dependencies = [ - "scoped-tls", -] - -[[package]] -name = "bincode" -version = "2.0.0-rc.3" -source = "git+https://github.com/bincode-org/bincode.git#5a91c1210168b968b957a14b743cf134f2d20719" -dependencies = [ - "bincode_derive", - "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.0-rc.3" -source = "git+https://github.com/bincode-org/bincode.git#5a91c1210168b968b957a14b743cf134f2d20719" -dependencies = [ - "virtue", -] - -[[package]] -name = "bitcoin" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0032b0e8ead7074cda7fc4f034409607e3f03a6f71d66ade8a307f79b4d99e73" -dependencies = [ - "base58ck", - "bech32", - "bitcoin-internals", - "bitcoin-io", - "bitcoin-units", - "bitcoin_hashes", - "hex-conservative", - "hex_lit", - "secp256k1", - "serde", -] - -[[package]] -name = "bitcoin-internals" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" -dependencies = [ - "serde", -] - -[[package]] -name = "bitcoin-io" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" - -[[package]] -name = "bitcoin-units" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" -dependencies = [ - "bitcoin-internals", - "serde", -] - -[[package]] -name = "bitcoin_hashes" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" -dependencies = [ - "bitcoin-io", - "hex-conservative", - "serde", -] - -[[package]] -name = "bitcoincore-rpc" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee" -dependencies = [ - "bitcoincore-rpc-json", - "jsonrpc", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "bitcoincore-rpc-json" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a" -dependencies = [ - "bitcoin", - "serde", - "serde_json", -] - -[[package]] -name = "biter" -version = "0.1.1" -dependencies = [ - "bitcoin", - "bitcoincore-rpc", - "crossbeam", - "derived-deref", - "rayon", - "serde", - "serde_json", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "browserslist-rs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14" -dependencies = [ - "ahash", - "chrono", - "either", - "indexmap", - "itertools", - "nom", - "once_cell", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -dependencies = [ - "allocator-api2", -] - -[[package]] -name = "bytemuck" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" - -[[package]] -name = "cc" -version = "1.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets", -] - -[[package]] -name = "clap" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors 3.5.0", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors 3.5.0", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "colorchoice" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ctrlc" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" -dependencies = [ - "nix", - "windows-sys 0.59.0", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "data-encoding" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" - -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "serde", - "uuid", -] - -[[package]] -name = "derive_deref" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdbcee2d9941369faba772587a565f4f534e42cb8d17e5295871de730163b2b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derived-deref" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805ef2023ccd65425743a91ecd11fc020979a0b01921db3104fb606d18a7b43e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "env_filter", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" -dependencies = [ - "crc32fast", - "miniz_oxide 0.8.0", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "from_variant" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d8947525c49c73130b5a7187b55b027b6b78fe60268d9f4c283ed690698cb1" -dependencies = [ - "proc-macro2", - "swc_macros_common", - "syn 2.0.85", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "h2" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex-conservative" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "hex_lit" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" - -[[package]] -name = "hstr" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae404c0c5d4e95d4858876ab02eecd6a196bb8caa42050dfa809938833fc412" -dependencies = [ - "hashbrown 0.14.5", - "new_debug_unreachable", - "once_cell", - "phf", - "rustc-hash", - "triomphe", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" -dependencies = [ - "equivalent", - "hashbrown 0.15.0", - "serde", -] - -[[package]] -name = "inferno" -version = "0.11.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" -dependencies = [ - "ahash", - "clap", - "crossbeam-channel", - "crossbeam-utils", - "dashmap 6.1.0", - "env_logger", - "indexmap", - "is-terminal", - "itoa", - "log", - "num-format", - "once_cell", - "quick-xml", - "rgb", - "str_stack", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "is-macro" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2069faacbe981460232f880d26bf3c7634e322d49053aa48c27e3ae642f728f1" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "jsonc-parser" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b56a20e76235284255a09fcd1f45cf55d3c524ea657ebd3854735925c57743d" -dependencies = [ - "serde_json", -] - -[[package]] -name = "jsonrpc" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf" -dependencies = [ - "base64 0.13.1", - "minreq", - "serde", - "serde_json", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.159" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "lru" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" -dependencies = [ - "hashbrown 0.13.2", -] - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - -[[package]] -name = "miette" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" -dependencies = [ - "cfg-if", - "miette-derive", - "owo-colors 4.1.0", - "textwrap", - "thiserror", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "minreq" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" -dependencies = [ - "log", - "serde", - "serde_json", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "normpath" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a9da8c9922c35a1033d76f7272dfc2e7ee20392083d75aeea6ced23c6266578" -dependencies = [ - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec", - "itoa", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "openssl" -version = "0.10.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e7ccb95e240b7c9506a3d544f10d935e142cc90b0a1d56954fb44d89ad6b97" -dependencies = [ - "num-traits", -] - -[[package]] -name = "outref" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" - -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "owo-colors" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.7", - "smallvec", - "windows-targets", -] - -[[package]] -name = "parser" -version = "0.5.0" -dependencies = [ - "allocative", - "bincode", - "bitcoin_hashes", - "biter", - "chrono", - "clap", - "color-eyre", - "ctrlc", - "derive_deref", - "inferno", - "itertools", - "ordered-float", - "rayon", - "reqwest", - "sanakirja", - "serde", - "serde_json", - "struct_iterable", - "toml", - "zstd", -] - -[[package]] -name = "path-clean" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" - -[[package]] -name = "path-clean" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" - -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "preset_env_base" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaeaf4147a07c6615d43fce02d8139e1f835e6ee53e69db2a4810823c138c26" -dependencies = [ - "ahash", - "anyhow", - "browserslist-rs", - "dashmap 5.5.3", - "from_variant", - "once_cell", - "semver 1.0.23", - "serde", - "st-map", - "tracing", -] - -[[package]] -name = "proc-macro2" -version = "1.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "psm" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" -dependencies = [ - "cc", -] - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "radix_fmt" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" -dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "regex" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.1", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", -] - -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustix" -version = "0.38.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" -dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "ryu-js" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad97d4ce1560a5e27cec89519dc8300d1aa6035b099821261c651486a19e44d5" - -[[package]] -name = "sanakirja" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81aaf70d064e2122209f04d01fd91e8908e7a327b516236e1cbc0c3f34ac6d11" -dependencies = [ - "fs2", - "log", - "memmap2", - "parking_lot 0.11.2", - "sanakirja-core", - "serde", - "thiserror", -] - -[[package]] -name = "sanakirja-core" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8376db34ae3eac6e7bd91168bc638450073b708ce9fb46940de676f552238bf5" - -[[package]] -name = "schannel" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "secp256k1" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" -dependencies = [ - "bitcoin_hashes", - "rand", - "secp256k1-sys", - "serde", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" -dependencies = [ - "cc", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.214" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.214" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "server" -version = "0.5.0" -dependencies = [ - "axum", - "bincode", - "chrono", - "color-eyre", - "derive_deref", - "itertools", - "parser", - "regex", - "reqwest", - "serde", - "serde_json", - "swc", - "swc_common", - "tokio", - "tower-http", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-abstraction" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" -dependencies = [ - "outref", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - -[[package]] -name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "sourcemap" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab08a862c70980b8e23698b507e272317ae52a608a164a844111f5372374f1f" -dependencies = [ - "base64-simd", - "bitvec", - "data-encoding", - "debugid", - "if_chain", - "rustc-hash", - "rustc_version", - "serde", - "serde_json", - "unicode-id-start", - "url", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "st-map" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8257dd592de7614be71a2342d36ba2d527ddad3f9a0c8d09d6ceed4c371531e4" -dependencies = [ - "arrayvec", - "static-map-macro", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "stacker" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - -[[package]] -name = "static-map-macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e9696ef338691287aeb937ee6ffe60022f579d3c8d2fd9d58973a9a10a466" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "str_stack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" - -[[package]] -name = "string_enum" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.85", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "struct_iterable" -version = "0.1.2" -dependencies = [ - "struct_iterable_derive", - "struct_iterable_internal", -] - -[[package]] -name = "struct_iterable_derive" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "struct_iterable_internal", - "syn 2.0.85", -] - -[[package]] -name = "struct_iterable_internal" -version = "0.1.1" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "swc" -version = "0.289.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65d51f69ac919b22a08e8654f63def43578a174132bda87309dea8fddd41a5d9" -dependencies = [ - "anyhow", - "base64 0.21.7", - "dashmap 5.5.3", - "either", - "indexmap", - "jsonc-parser", - "lru", - "once_cell", - "parking_lot 0.12.3", - "pathdiff", - "regex", - "rustc-hash", - "serde", - "serde_json", - "sourcemap", - "swc_atoms", - "swc_cached", - "swc_common", - "swc_compiler_base", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_ext_transforms", - "swc_ecma_lints", - "swc_ecma_loader", - "swc_ecma_minifier", - "swc_ecma_parser", - "swc_ecma_preset_env", - "swc_ecma_transforms", - "swc_ecma_transforms_base", - "swc_ecma_transforms_compat", - "swc_ecma_transforms_optimization", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_error_reporters", - "swc_node_comments", - "swc_timer", - "swc_transform_common", - "swc_typescript", - "swc_visit", - "tracing", - "url", -] - -[[package]] -name = "swc_allocator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7016ee7a5186d6a80e381aa1926e0f3c7b06eaf444745ff7af3632e978eb8dc5" -dependencies = [ - "bumpalo", - "hashbrown 0.14.5", - "ptr_meta", - "rustc-hash", - "triomphe", -] - -[[package]] -name = "swc_atoms" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25ff0f3fd48ab1a95d86fd0505fdd1ac904f84d0350dc8222bbc824e9d4fdf6" -dependencies = [ - "hstr", - "once_cell", - "rustc-hash", - "serde", -] - -[[package]] -name = "swc_cached" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83406221c501860fce9c27444f44125eafe9e598b8b81be7563d7036784cd05c" -dependencies = [ - "ahash", - "anyhow", - "dashmap 5.5.3", - "once_cell", - "regex", - "serde", -] - -[[package]] -name = "swc_common" -version = "0.40.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f355465eaed1104244ce918b7ffb77ceb109aabeb74b04b98acae85683b0215b" -dependencies = [ - "ahash", - "ast_node", - "better_scoped_tls", - "cfg-if", - "either", - "from_variant", - "new_debug_unreachable", - "num-bigint", - "once_cell", - "parking_lot 0.12.3", - "rustc-hash", - "serde", - "siphasher", - "sourcemap", - "swc_allocator", - "swc_atoms", - "swc_eq_ignore_macros", - "swc_visit", - "tracing", - "unicode-width", - "url", -] - -[[package]] -name = "swc_compiler_base" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b82e7c3cc5954a5b2e9c545920232a1d37a70215719f5021f090eda9fcf88a" -dependencies = [ - "anyhow", - "base64 0.21.7", - "once_cell", - "pathdiff", - "rustc-hash", - "serde", - "serde_json", - "sourcemap", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_minifier", - "swc_ecma_parser", - "swc_ecma_visit", - "swc_timer", -] - -[[package]] -name = "swc_config" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4740e53eaf68b101203c1df0937d5161a29f3c13bceed0836ddfe245b72dd000" -dependencies = [ - "anyhow", - "indexmap", - "serde", - "serde_json", - "sourcemap", - "swc_cached", - "swc_config_macro", -] - -[[package]] -name = "swc_config_macro" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f56139042c1a95b54f5ca48baa0e0172d369bcc9d3d473dad1de36bae8399" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.85", -] - -[[package]] -name = "swc_ecma_ast" -version = "0.121.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7e1b73c85ff968404867505646b3c6f26e4661e4fc831593b9e182fa59ddd4" -dependencies = [ - "bitflags 2.6.0", - "is-macro", - "num-bigint", - "phf", - "scoped-tls", - "serde", - "string_enum", - "swc_atoms", - "swc_common", - "unicode-id-start", -] - -[[package]] -name = "swc_ecma_codegen" -version = "0.158.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1953878c2f44d796ff4ee8bde727890d24fe3bed9a86a23be4c4ef9ad79b6a6c" -dependencies = [ - "memchr", - "num-bigint", - "once_cell", - "regex", - "serde", - "sourcemap", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_codegen_macros", - "tracing", -] - -[[package]] -name = "swc_ecma_codegen_macros" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859fabde36db38634f3fad548dd5e3410c1aebba1b67a3c63e67018fa57a0bca" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.85", -] - -[[package]] -name = "swc_ecma_compat_bugfixes" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff0f4ede38d4110f8c639a4c07c8d2b677d3eac968ab33da312bbaf6ad5adf0" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_es2015", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_common" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a479b2061a3b2c939a9abd34cfd3e9a6983a8bf12f7d3da89ac7ad6a64d9a4" -dependencies = [ - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", -] - -[[package]] -name = "swc_ecma_compat_es2015" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129fa363e3ea01f9e2d2dbd06675bfd515689b261c28b30296d89c91b549916a" -dependencies = [ - "arrayvec", - "indexmap", - "is-macro", - "rustc-hash", - "serde", - "serde_derive", - "smallvec", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_compat_common", - "swc_ecma_transforms_base", - "swc_ecma_transforms_classes", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2016" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0f1dd466100cd41e85be398d1f4d973f8d5760ec52376294918def88e620ad" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2017" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a3fcc3ae30628fc752d04b44c885ea6202ecfb187311d8827cc851ba7d906d" -dependencies = [ - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2018" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31160d66339dda14faefd3a2816a0436f0a1e4f33a2057d8f3663a1a567eaf2e" -dependencies = [ - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_common", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2019" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f0164ad462c81c421de2af75f408ca3a96324a3c5e81d999a90a58e6e40dbef" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2020" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da49ae2565352c179657b47e87b4879b8c1128b5447e8fbfaaa020702f740636" -dependencies = [ - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_es2022", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2021" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9fed665a9f5b2b3c55fc04f36b12c8d2148fd25fb216a4d9ae9fac331a5374" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es2022" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241bf311a26447690bbfc36ec76886dac04343f82e3fe638fc0e0cdbaeae534b" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_compat_common", - "swc_ecma_transforms_base", - "swc_ecma_transforms_classes", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_compat_es3" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0c7867bba0129c8844cf274c9a0e3f1926d7225d6dce36343a4b1626399992" -dependencies = [ - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_ext_transforms" -version = "0.123.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ea0bdb96b8d24305cf23614db4c734425c44329bd6e1734e6ebd068a88b536" -dependencies = [ - "phf", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_lints" -version = "0.104.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afb24beda205684867ba555f59e4a470afbe67d9deaacc29b1a5e3a4a1fd58d" -dependencies = [ - "auto_impl", - "dashmap 5.5.3", - "parking_lot 0.12.3", - "rayon", - "regex", - "serde", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_loader" -version = "0.52.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f521eea3476abc7ae7adcbfa3479881087e5ae8fe358d46376d761e83fdb3120" -dependencies = [ - "anyhow", - "dashmap 5.5.3", - "lru", - "normpath", - "once_cell", - "parking_lot 0.12.3", - "path-clean 0.1.0", - "pathdiff", - "serde", - "serde_json", - "swc_atoms", - "swc_cached", - "swc_common", - "tracing", -] - -[[package]] -name = "swc_ecma_minifier" -version = "0.208.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67d0fec4e2a4a00c1605f6baa2845cc05b4059c1bf9a6e45377c8ae67f04207d" -dependencies = [ - "arrayvec", - "indexmap", - "num-bigint", - "num_cpus", - "once_cell", - "parking_lot 0.12.3", - "phf", - "radix_fmt", - "regex", - "rustc-hash", - "ryu-js", - "serde", - "serde_json", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_optimization", - "swc_ecma_usage_analyzer", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_timer", - "tracing", -] - -[[package]] -name = "swc_ecma_parser" -version = "0.152.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4cfd7930abe18c6829d4adedb5249e1b9fa68e8e786c6636250637992ba0466" -dependencies = [ - "either", - "new_debug_unreachable", - "num-bigint", - "num-traits", - "phf", - "serde", - "smallvec", - "smartstring", - "stacker", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "tracing", - "typed-arena", -] - -[[package]] -name = "swc_ecma_preset_env" -version = "0.221.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b6b5a7222fe85be1732faa7a2c9812fc516d934ba320662c90c066264cd9eec" -dependencies = [ - "anyhow", - "dashmap 5.5.3", - "indexmap", - "once_cell", - "preset_env_base", - "rustc-hash", - "semver 1.0.23", - "serde", - "serde_json", - "st-map", - "string_enum", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms" -version = "0.243.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b22f584075dfbd349c107def9c52de654979f0a1e8cdc4255dd4e7ec5b39800" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_transforms_compat", - "swc_ecma_transforms_module", - "swc_ecma_transforms_optimization", - "swc_ecma_transforms_proposal", - "swc_ecma_transforms_react", - "swc_ecma_transforms_typescript", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_base" -version = "0.149.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2194d20416a03ae02058934f8387691809f86466a2f2e7a65c56dcb001bd46b" -dependencies = [ - "better_scoped_tls", - "bitflags 2.6.0", - "indexmap", - "once_cell", - "phf", - "rustc-hash", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_utils", - "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_classes" -version = "0.138.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e93ef88e45587864ef645e29c33f04f75360e47194c084f931855af3aac246" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_compat" -version = "0.175.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35dd174f6c954a4fe1bff88a64de777ef65041411f96b60daf4a78864ad47789" -dependencies = [ - "arrayvec", - "indexmap", - "is-macro", - "num-bigint", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_compat_bugfixes", - "swc_ecma_compat_common", - "swc_ecma_compat_es2015", - "swc_ecma_compat_es2016", - "swc_ecma_compat_es2017", - "swc_ecma_compat_es2018", - "swc_ecma_compat_es2019", - "swc_ecma_compat_es2020", - "swc_ecma_compat_es2021", - "swc_ecma_compat_es2022", - "swc_ecma_compat_es3", - "swc_ecma_transforms_base", - "swc_ecma_transforms_classes", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_trace_macro", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_macros" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500a1dadad1e0e41e417d633b3d6d5de677c9e0d3159b94ba3348436cdb15aab" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.85", -] - -[[package]] -name = "swc_ecma_transforms_module" -version = "0.194.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4317b5ce1f5fc00b7d72b712c822e1aa66cdb9dc155799ee2278e00b2172f918" -dependencies = [ - "Inflector", - "anyhow", - "bitflags 2.6.0", - "indexmap", - "is-macro", - "path-clean 1.0.1", - "pathdiff", - "regex", - "serde", - "swc_atoms", - "swc_cached", - "swc_common", - "swc_ecma_ast", - "swc_ecma_loader", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_optimization" -version = "0.212.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2722095aef7b6de79b170c8c4e3c885576c5394e7a20328fdc51e880d6257b" -dependencies = [ - "dashmap 5.5.3", - "indexmap", - "once_cell", - "petgraph", - "rustc-hash", - "serde_json", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_fast_graph", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_proposal" -version = "0.183.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718f1e24dd96cfb0b7ba8f8a4e61c98338cdac7a3f5f9f4a83951d776ac398bf" -dependencies = [ - "either", - "rustc-hash", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_transforms_classes", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_react" -version = "0.195.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ddcb8943976319a4f8b899862f094f6984511490e4bfa9b4c8fbd334539b62" -dependencies = [ - "base64 0.21.7", - "dashmap 5.5.3", - "indexmap", - "once_cell", - "serde", - "sha1", - "string_enum", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_transforms_typescript" -version = "0.202.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31acb773a2d82e5524d021aa77a38ae8bc708171b0005fd420b43356d8dfba25" -dependencies = [ - "ryu-js", - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_transforms_base", - "swc_ecma_transforms_react", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecma_usage_analyzer" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a1d8b627b6adc706ccd2a4a30a5413e9df91a9cff6569cb9d3b9f41c1bc8de" -dependencies = [ - "indexmap", - "rustc-hash", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_timer", - "tracing", -] - -[[package]] -name = "swc_ecma_utils" -version = "0.137.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939f21c75eff61ad3e485fc54d38988f2f9744ceda24a3feb8228dd072171d94" -dependencies = [ - "indexmap", - "num_cpus", - "once_cell", - "rustc-hash", - "ryu-js", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_visit", - "tracing", - "unicode-id", -] - -[[package]] -name = "swc_ecma_visit" -version = "0.107.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bd17e03251272ee04d1155036be5288055ab43d40ab2d9fd63ff815d326dad" -dependencies = [ - "new_debug_unreachable", - "num-bigint", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_visit", - "tracing", -] - -[[package]] -name = "swc_eq_ignore_macros" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "swc_error_reporters" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f9286183c9be40aafcbbe8c397403fb32af812a3dffe93eee9067aee4fbedb" -dependencies = [ - "anyhow", - "miette", - "once_cell", - "parking_lot 0.12.3", - "swc_common", -] - -[[package]] -name = "swc_fast_graph" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3daff8d4379be2a99ab4b146e4dd631ef2415965dc4f1d33e988a737c5ccc39a" -dependencies = [ - "indexmap", - "petgraph", - "rustc-hash", - "swc_common", -] - -[[package]] -name = "swc_macros_common" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "swc_node_comments" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c325461f5e78e7749241f917945164adccfadcc2138a9a2e5ac9d186e1c459" -dependencies = [ - "dashmap 5.5.3", - "swc_atoms", - "swc_common", -] - -[[package]] -name = "swc_timer" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f53899e4ab3f9ce3db83b4c845f8a27f37bf700b233febbc9a6da749651c2fbb" -dependencies = [ - "tracing", -] - -[[package]] -name = "swc_trace_macro" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69aa25f667e4d74ab10a17a266edeb8b354273817b20b91e60471f1c860a221b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "swc_transform_common" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda3e80e1ad638d3575bc07745a914af13dcb02215098659f864731078271f2c" -dependencies = [ - "better_scoped_tls", - "once_cell", - "rustc-hash", - "serde", - "serde_json", -] - -[[package]] -name = "swc_typescript" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b61f86dce2eeabd85d934a89cd481709918c751c44dfe433b5e4f1f40075fb5a" -dependencies = [ - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "thiserror", -] - -[[package]] -name = "swc_visit" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceb044142ba2719ef9eb3b6b454fce61ab849eb696c34d190f04651955c613d" -dependencies = [ - "either", - "new_debug_unreachable", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" -dependencies = [ - "smawk", - "unicode-linebreak", - "unicode-width", -] - -[[package]] -name = "thiserror" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot 0.12.3", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 0.1.2", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" -dependencies = [ - "async-compression", - "bitflags 2.6.0", - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "triomphe" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" -dependencies = [ - "serde", - "stable_deref_trait", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typed-arena" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - -[[package]] -name = "unicode-id" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" - -[[package]] -name = "unicode-id-start" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97e2a3c5fc9de285c0e805d98eba666adb4b2d9e1049ce44821ff7707cc34e91" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "unty" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a88342087869553c259588a3ec9ca73ce9b2d538b7051ba5789ff236b6c129" - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "virtue" -version = "0.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7302ac74a033bf17b6e609ceec0f891ca9200d502d31f02dc7908d3d98767c9d" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.85", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" -dependencies = [ - "memchr", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.85", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zstd" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index 92c9921f3..000000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "server" -version = "0.5.0" -edition = "2021" - -[dependencies] -axum = "0.7.7" -bincode = { git = "https://github.com/bincode-org/bincode.git" } -chrono = "0.4.38" -color-eyre = "0.6.3" -derive_deref = "1.1.1" -itertools = "0.13.0" -parser = { path = "../parser" } -regex = "1.11.0" -reqwest = { version = "0.12.8", features = ["json"] } -serde = { version = "1.0.210", features = ["derive"] } -serde_json = { version = "1.0.128" } -swc = "0.289.1" -swc_common = "0.40.1" -tokio = { version = "1.40.0", features = ["full"] } -tower-http = { version = "0.6.1", features = ["compression-full"] } -# oxc = { version = "0.34.0", features = ["codegen", "minifier"] } diff --git a/server/restart-cloudflared.sh b/server/restart-cloudflared.sh deleted file mode 100755 index fa1695f7a..000000000 --- a/server/restart-cloudflared.sh +++ /dev/null @@ -1,4 +0,0 @@ -sudo launchctl stop com.cloudflare.cloudflared -sudo launchctl unload /Library/LaunchDaemons/com.cloudflare.cloudflared.plist -sudo launchctl load /Library/LaunchDaemons/com.cloudflare.cloudflared.plist -sudo launchctl start com.cloudflare.cloudflared diff --git a/server/run.sh b/server/run.sh deleted file mode 100755 index 77a22baac..000000000 --- a/server/run.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -if cargo watch --help &> /dev/null; then - TRIGGER="./in/datasets_len.txt" - - if [ ! -f "$TRIGGER" ]; then - mkdir "./in" - echo "0" > $TRIGGER - fi - - cargo watch --no-vcs-ignores -w "./src" -w "$TRIGGER" -x "run -r" -else - cargo run -r -fi diff --git a/server/src/api/handlers/dataset.rs b/server/src/api/handlers/dataset.rs deleted file mode 100644 index b0ab851f8..000000000 --- a/server/src/api/handlers/dataset.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::{collections::BTreeMap, path::PathBuf}; - -use axum::{ - extract::{Path, Query, State}, - http::HeaderMap, - response::{IntoResponse, Response}, -}; -use color_eyre::{eyre::eyre, owo_colors::OwoColorize}; -use reqwest::StatusCode; -use serde::Deserialize; - -use parser::{ - log, Date, DateMap, Height, HeightMap, Json, MapChunkId, COMPRESSED_BIN_EXTENSION, - HEIGHT_MAP_CHUNK_SIZE, JSON_EXTENSION, OHLC, -}; - -use crate::{ - api::structs::{Chunk, Kind, Route}, - header_map::HeaderMapUtils, - AppState, -}; - -use super::{ - extension::Extension, - response::{typed_value_to_response, value_to_response}, -}; - -#[derive(Deserialize)] -pub struct Params { - chunk: Option, - all: Option, -} - -pub async fn dataset_handler( - headers: HeaderMap, - path: Path, - query: Query, - State(app_state): State, -) -> Response { - match _dataset_handler(headers, path, query, app_state) { - Ok(response) => response, - Err(error) => { - let mut response = - (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(); - - response.headers_mut().insert_cors(); - - response - } - } -} - -const DATE_PREFIX: &str = "date-to-"; -const HEIGHT_PREFIX: &str = "height-to-"; - -fn _dataset_handler( - headers: HeaderMap, - Path(path): Path, - query: Query, - AppState { routes }: AppState, -) -> color_eyre::Result { - if query.chunk.is_some() && query.all.is_some() { - return Err(eyre!("chunk and all are exclusive")); - } - - log(&format!( - "{}{}{}", - path, - query.chunk.map_or("".to_string(), |chunk| format!( - "{}{chunk}", - "?chunk=".bright_black() - )), - query.all.map_or("".to_string(), |all| format!( - "{}{all}", - "?all=".bright_black() - )) - )); - - let (kind, id, route) = if path.starts_with(DATE_PREFIX) { - let id = convert_path_to_id(path.strip_prefix(DATE_PREFIX).unwrap()); - let route = routes.date.get(&id); - (Kind::Date, id, route) - } else if path.starts_with(HEIGHT_PREFIX) { - let id = convert_path_to_id(path.strip_prefix(HEIGHT_PREFIX).unwrap()); - let route = routes.height.get(&id); - (Kind::Height, id, route) - } else { - let id = convert_path_to_id(&path); - let route = routes.last.get(&id); - (Kind::Last, id, route) - }; - - if route.is_none() { - return Err(eyre!("Path error")); - } - - let mut route = route.unwrap().to_owned(); - - let mut chunk = None; - - if query.all.map_or(true, |b| !b) { - match kind { - Kind::Date => { - let datasets = DateMap::::_read_dir(&route.file_path, &route.serialization); - - process_datasets(&headers, kind, &mut chunk, &mut route, query, datasets)?; - } - Kind::Height => { - let datasets = - HeightMap::::_read_dir(&route.file_path, &route.serialization); - - process_datasets(&headers, kind, &mut chunk, &mut route, query, datasets)?; - } - Kind::Last => { - if !route.values_type.ends_with("Value") { - route.file_path.set_extension(COMPRESSED_BIN_EXTENSION); - } else { - route.file_path.set_extension(JSON_EXTENSION); - } - } - } - } - - let (date, response) = headers.check_if_modified_since(&route.file_path).unwrap(); - - if let Some(response) = response { - return Ok(response); - } - - let type_name = route.values_type.split("::").last().unwrap(); - - let extension = Extension::from(&std::path::PathBuf::from(&path)); - - let mut response = match type_name { - "u8" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "u16" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "u32" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "u64" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "usize" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "f32" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "f64" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "OHLC" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "Date" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "Height" => typed_value_to_response::(kind, &route, chunk, id, extension)?, - "Value" => { - value_to_response::(Json::import(&route.file_path)?, extension) - } - _ => panic!("Incompatible type: {type_name}"), - }; - - let headers = response.headers_mut(); - headers.insert_last_modified(date); - - Ok(response) -} - -fn convert_path_to_id(s: &str) -> String { - Extension::remove_extension(s).replace('-', "_") -} - -fn process_datasets( - headers: &HeaderMap, - kind: Kind, - chunk: &mut Option, - route: &mut Route, - query: Query, - datasets: BTreeMap, -) -> color_eyre::Result<()> -where - ChunkId: MapChunkId, -{ - let (last_chunk_id, _) = datasets.last_key_value().unwrap_or_else(|| { - dbg!(&datasets, &route); - panic!() - }); - - let chunk_id = query - .chunk - .map(|id| ChunkId::from_usize(id)) - .unwrap_or(*last_chunk_id); - - let path = datasets.get(&chunk_id); - - if path.is_none() { - return Err(eyre!("Couldn't find chunk")); - } - - let path = path.unwrap(); - route.file_path = path.clone(); - - let offset = match kind { - Kind::Date => 1, - Kind::Height => HEIGHT_MAP_CHUNK_SIZE as usize, - _ => panic!(), - }; - - let offsetted_to_url = |offseted| { - datasets.get(&ChunkId::from_usize(offseted)).map(|_| { - let scheme = headers.get_scheme(); - let host = headers.get_host(); - format!("{scheme}://{host}/api/{}?chunk={offseted}", route.url_path) - }) - }; - - let chunk_id = chunk_id.to_usize(); - - chunk.replace(Chunk { - id: chunk_id, - next: chunk_id.checked_add(offset).and_then(offsetted_to_url), - previous: chunk_id.checked_sub(offset).and_then(offsetted_to_url), - }); - - Ok(()) -} diff --git a/server/src/api/handlers/extension.rs b/server/src/api/handlers/extension.rs deleted file mode 100644 index 946ad67b9..000000000 --- a/server/src/api/handlers/extension.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::path::Path; - -#[derive(PartialEq, Eq)] -pub enum Extension { - #[allow(clippy::upper_case_acronyms)] - CSV, - #[allow(clippy::upper_case_acronyms)] - JSON, -} - -impl Extension { - pub fn from(path: &Path) -> Option { - if let Some(extension) = path.extension() { - let extension = extension.to_str().unwrap(); - - if extension == Self::CSV.to_str() { - Some(Self::CSV) - } else if extension == Self::JSON.to_str() { - Some(Self::JSON) - } else { - None - } - } else { - None - } - } - - pub fn to_str(&self) -> &str { - match self { - Extension::CSV => "csv", - Extension::JSON => "json", - } - } - - pub fn to_dot_str(&self) -> String { - format!(".{}", self.to_str()) - } - - pub fn remove_extension(s: &str) -> String { - s.replace(&Self::CSV.to_dot_str(), "") - .replace(&Self::JSON.to_dot_str(), "") - } -} diff --git a/server/src/api/handlers/fallback.rs b/server/src/api/handlers/fallback.rs deleted file mode 100644 index 158261d07..000000000 --- a/server/src/api/handlers/fallback.rs +++ /dev/null @@ -1,19 +0,0 @@ -use axum::{extract::State, http::HeaderMap, response::Response}; -use reqwest::header::HOST; - -use crate::AppState; - -use super::response::{generic_to_reponse, update_reponse_headers}; - -pub async fn fallback(headers: HeaderMap, State(app_state): State) -> Response { - update_reponse_headers( - generic_to_reponse( - app_state - .routes - .to_full_paths(headers[HOST].to_str().unwrap().to_string()), - None, - ), - 60, - None, - ) -} diff --git a/server/src/api/handlers/mod.rs b/server/src/api/handlers/mod.rs deleted file mode 100644 index 33474ff3a..000000000 --- a/server/src/api/handlers/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod dataset; -mod extension; -mod fallback; - -mod response; - -pub use dataset::*; -pub use fallback::*; diff --git a/server/src/api/handlers/response.rs b/server/src/api/handlers/response.rs deleted file mode 100644 index edac89415..000000000 --- a/server/src/api/handlers/response.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::fmt::Debug; - -use axum::response::{IntoResponse, Json, Response}; -use bincode::Decode; -use parser::{Date, MapValue, SerializedBTreeMap, SerializedVec}; -use serde::de::DeserializeOwned; -use serde::Serialize; - -use crate::{ - api::structs::{Chunk, Kind, Route}, - header_map::HeaderMapUtils, -}; - -use super::extension::Extension; - -#[derive(Serialize)] -struct WrappedDataset<'a, T> -where - T: Serialize, -{ - source: &'a str, - chunk: Chunk, - dataset: T, -} - -#[derive(Serialize)] -struct WrappedValue -where - T: Serialize, -{ - value: T, -} - -pub fn typed_value_to_response( - kind: Kind, - route: &Route, - chunk: Option, - id: String, - extension: Option, -) -> color_eyre::Result -where - T: Serialize + Debug + DeserializeOwned + Decode + MapValue, -{ - Ok(match kind { - Kind::Date => { - let dataset = if chunk.is_some() { - route - .serialization - .import::>(&route.file_path)? - } else { - SerializedBTreeMap::::import_all(&route.file_path, &route.serialization) - }; - - if extension == Some(Extension::CSV) { - let mut csv = format!("date,{}\n", id); - - dataset.map.iter().for_each(|(k, v)| { - csv += &format!("{},{:?}\n", k, v); - }); - - string_to_response(csv, extension) - } else { - dataset_to_response(dataset, chunk, extension) - } - } - Kind::Height => { - let dataset = if chunk.is_some() { - route - .serialization - .import::>(&route.file_path)? - } else { - SerializedVec::::import_all(&route.file_path, &route.serialization) - }; - - if extension == Some(Extension::CSV) { - let mut csv = format!("height,{}\n", id); - - let starting_height = chunk.map_or(0, |chunk| chunk.id); - - dataset.map.iter().enumerate().for_each(|(k, v)| { - csv += &format!("{},{:?}\n", starting_height + k, v); - }); - - string_to_response(csv, extension) - } else { - dataset_to_response(dataset, chunk, extension) - } - } - Kind::Last => { - let value = route.serialization.import::(&route.file_path)?; - - if extension == Some(Extension::JSON) { - value_to_response(WrappedValue { value }, extension) - } else { - value_to_response(value, extension) - } - } - }) -} - -pub fn string_to_response(s: String, extension: Option) -> Response { - update_reponse_headers(s.into_response(), 5, extension) -} - -pub fn value_to_response(value: T, extension: Option) -> Response -where - T: Serialize, -{ - update_reponse_headers(generic_to_reponse(value, None), 1, extension) -} - -fn dataset_to_response( - dataset: T, - chunk: Option, - extension: Option, -) -> Response -where - T: Serialize, -{ - update_reponse_headers(generic_to_reponse(dataset, chunk), 5, extension) -} - -pub fn generic_to_reponse(generic: T, chunk: Option) -> Response -where - T: Serialize, -{ - if let Some(chunk) = chunk { - Json(WrappedDataset { - source: "https://kibo.money", - chunk, - dataset: generic, - }) - .into_response() - } else { - Json(generic).into_response() - } -} - -pub fn update_reponse_headers( - mut response: Response, - cache_time: u64, - extension: Option, -) -> Response { - let headers = response.headers_mut(); - - let max_age = cache_time; - let stale_while_revalidate = max_age; - - headers.insert_cors(); - headers.insert_cache_control_revalidate(max_age, stale_while_revalidate); - - match extension { - Some(extension) => { - headers.insert_content_disposition_attachment(); - - match extension { - Extension::CSV => headers.insert_content_type_text_csv(), - Extension::JSON => headers.insert_content_type_application_json(), - } - } - _ => headers.insert_content_type_application_json(), - } - - response -} diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs deleted file mode 100644 index f1690fc30..000000000 --- a/server/src/api/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use axum::{routing::get, Router}; -use handlers::{dataset_handler, fallback}; - -use crate::AppState; - -mod handlers; -pub mod structs; - -pub trait ApiRoutes { - fn add_api_routes(self) -> Self; -} - -impl ApiRoutes for Router { - fn add_api_routes(self) -> Self { - self.route("/api/*path", get(dataset_handler)) - .route("/api/", get(fallback)) - .route("/api", get(fallback)) - } -} diff --git a/server/src/api/structs/chunk.rs b/server/src/api/structs/chunk.rs deleted file mode 100644 index c375f75c5..000000000 --- a/server/src/api/structs/chunk.rs +++ /dev/null @@ -1,8 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Chunk { - pub id: usize, - pub previous: Option, - pub next: Option, -} diff --git a/server/src/api/structs/kind.rs b/server/src/api/structs/kind.rs deleted file mode 100644 index 93d4ac932..000000000 --- a/server/src/api/structs/kind.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Kind { - Date, - Height, - Last, -} diff --git a/server/src/api/structs/mod.rs b/server/src/api/structs/mod.rs deleted file mode 100644 index 94f3cc402..000000000 --- a/server/src/api/structs/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod chunk; -mod kind; -mod paths; -mod routes; - -pub use chunk::*; -pub use kind::*; -pub use paths::*; -pub use routes::*; diff --git a/server/src/api/structs/paths.rs b/server/src/api/structs/paths.rs deleted file mode 100644 index 1461e0138..000000000 --- a/server/src/api/structs/paths.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::collections::BTreeMap; - -use derive_deref::{Deref, DerefMut}; -use serde::Serialize; - -use crate::Grouped; - -#[derive(Clone, Default, Deref, DerefMut, Debug, Serialize)] -pub struct Paths(pub Grouped>); diff --git a/server/src/api/structs/routes.rs b/server/src/api/structs/routes.rs deleted file mode 100644 index fc33fbf5d..000000000 --- a/server/src/api/structs/routes.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - fs, - path::{Path, PathBuf}, -}; - -use derive_deref::{Deref, DerefMut}; -use itertools::Itertools; -use parser::{Json, Serialization}; - -use crate::Grouped; - -use super::Paths; - -#[derive(Clone, Debug)] -pub struct Route { - pub url_path: String, - pub file_path: PathBuf, - pub values_type: String, - pub serialization: Serialization, -} - -#[derive(Clone, Default, Deref, DerefMut)] -pub struct Routes(pub Grouped>); - -const INPUTS_PATH: &str = "./in"; -const WEBSITE_TYPES_PATH: &str = "../website/scripts/types"; - -impl Routes { - pub fn build() -> Self { - let path_to_type: BTreeMap = - Json::import(Path::new(&format!("{INPUTS_PATH}/disk_path_to_type.json"))).unwrap(); - - let mut routes = Routes::default(); - - path_to_type.into_iter().for_each(|(key, value)| { - let mut split_key = key.split('/').collect_vec(); - let last = split_key.pop().unwrap().to_owned(); - - let mut skip = 2; - - let mut serialization = Serialization::Binary; - - if *split_key.get(1).unwrap() == "price" { - skip = 1; - serialization = Serialization::Json; - } - - let mut split_key = split_key.iter().skip(skip).collect_vec(); - - // Use case for: "../datasets/last": "Value", - if split_key.is_empty() { - split_key.push(&"last"); - } - - let map_key = split_key.iter().join("_"); - - let url_path = split_key.iter().join("-"); - - let file_path = PathBuf::from(key.to_owned()); - let values_type = value.to_owned(); - - if last == "date" { - routes.date.insert( - map_key, - Route { - url_path: format!("date-to-{url_path}"), - file_path, - values_type, - serialization, - }, - ); - } else if last == "height" { - routes.height.insert( - map_key, - Route { - url_path: format!("height-to-{url_path}"), - file_path, - values_type, - serialization, - }, - ); - } else if last == "last" { - routes.last.insert( - map_key, - Route { - url_path, - file_path, - values_type, - serialization, - }, - ); - } else { - dbg!(&key, value, &last); - panic!("") - } - }); - - routes - } - - pub fn generate_dts_file(&self) { - let map_to_type = |name: &str, map: &HashMap| -> String { - let paths = map - .values() - .map(|route| format!("\"{}\"", route.url_path)) - .join(" | "); - - format!("export type {}Path = {};\n", name, paths) - }; - - let date_type = map_to_type("Date", &self.date); - - let height_type = map_to_type("Height", &self.height); - - let last_type = map_to_type("Last", &self.last); - - fs::write( - format!("{WEBSITE_TYPES_PATH}/paths.d.ts"), - format!("// This file is auto generated by the server\n// Manual changes are forbidden\n\n{date_type}\n{height_type}\n{last_type}"), - ) - .unwrap(); - } - - pub fn to_full_paths(&self, host: String) -> Paths { - let url = { - let scheme = if host.contains("0.0.0.0") || host.contains("localhost") { - "http" - } else { - "https" - }; - - format!("{scheme}://{host}") - }; - - let transform = |map: &HashMap| -> BTreeMap { - map.iter() - .map(|(key, route)| { - ( - key.to_owned(), - format!("{url}/api/{}", route.url_path.to_owned()), - ) - }) - .collect() - }; - - let date_paths = transform(&self.date); - let height_paths = transform(&self.height); - let last_paths = transform(&self.last); - - Paths(Grouped { - date: date_paths, - height: height_paths, - last: last_paths, - }) - } -} diff --git a/server/src/main.rs b/server/src/main.rs deleted file mode 100644 index 3aa94b4f6..000000000 --- a/server/src/main.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::sync::Arc; - -use api::{structs::Routes, ApiRoutes}; -use axum::{serve, Router}; -use parser::{log, reset_logs}; -use serde::Serialize; -use tokio::net::TcpListener; -use tower_http::compression::CompressionLayer; -use website::WebsiteRoutes; - -mod api; -mod header_map; -mod website; - -#[derive(Clone, Debug, Default, Serialize)] -pub struct Grouped { - pub date: T, - pub height: T, - pub last: T, -} - -#[derive(Clone)] -pub struct AppState { - routes: Arc, -} - -#[tokio::main] -async fn main() -> color_eyre::Result<()> { - color_eyre::install()?; - - reset_logs(); - - let routes = Routes::build(); - - routes.generate_dts_file(); - - let state = AppState { - routes: Arc::new(routes), - }; - - let compression_layer = CompressionLayer::new() - .br(true) - .deflate(true) - .gzip(true) - .zstd(true); - - let router = Router::new() - .add_api_routes() - .add_website_routes() - .with_state(state) - .layer(compression_layer); - - let mut port = 3110; - - let mut listener; - loop { - listener = TcpListener::bind(format!("0.0.0.0:{port}")).await; - if listener.is_ok() { - break; - } - port += 1; - } - - log(&format!("Starting server on port {port}...")); - let listener = listener.unwrap(); - - serve(listener, router).await?; - - Ok(()) -} diff --git a/server/src/website/handlers/file.rs b/server/src/website/handlers/file.rs deleted file mode 100644 index 6f1e4f636..000000000 --- a/server/src/website/handlers/file.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::{ - fs::{self}, - path::{Path, PathBuf}, -}; - -use axum::{ - body::Body, - extract, - http::HeaderMap, - response::{IntoResponse, Response}, -}; -use parser::log; -use reqwest::StatusCode; - -use crate::header_map::HeaderMapUtils; - -use super::minify_js; - -const WEBSITE_PATH: &str = "../website/"; - -pub async fn file_handler(headers: HeaderMap, path: extract::Path) -> Response { - let mut path = path.0.replace("..", "").replace("\\", ""); - - if path.ends_with("Cargo.toml") { - path = "../server/Cargo.toml".to_owned(); - } - - let mut path = str_to_path(&path); - - if !path.exists() { - if path.extension().is_some() { - let mut response: Response = ( - StatusCode::INTERNAL_SERVER_ERROR, - "File doesn't exist".to_string(), - ) - .into_response(); - - response.headers_mut().insert_cors(); - - return response; - } else { - path = str_to_path("index.html"); - } - } - - path_to_response(headers, &path) -} - -pub async fn index_handler(headers: HeaderMap) -> Response { - path_to_response(headers, &str_to_path("index.html")) -} - -fn path_to_response(headers: HeaderMap, path: &Path) -> Response { - log(&path.to_str().unwrap().replace(WEBSITE_PATH, "")); - - let (date, response) = headers.check_if_modified_since(path).unwrap(); - - if let Some(response) = response { - return response; - } - - let mut response; - - let is_localhost = headers.check_if_host_is_localhost(); - - if !is_localhost - && path.extension().unwrap_or_else(|| { - dbg!(path); - panic!(); - }) == "js" - { - let content = minify_js(path); - - response = Response::new(content.into()); - } else { - let content = fs::read(path).unwrap_or_else(|error| { - log(&error.to_string()); - let path = path.to_str().unwrap(); - log(&format!("Can't read file {path}")); - panic!("") - }); - - response = Response::new(content.into()); - } - - let headers = response.headers_mut(); - headers.insert_cors(); - headers.insert_content_type(path); - - if !is_localhost { - let serialized_path = path.to_str().unwrap(); - - if serialized_path.contains("fonts/") - || serialized_path.contains("assets/") - || serialized_path.contains("packages/") - || path.extension().is_some_and(|extension| { - extension == "pdf" - || extension == "jpg" - || extension == "png" - || extension == "woff2" - }) - { - headers.insert_cache_control_immutable(); - } else { - headers.insert_cache_control_revalidate(1, 1); - } - } - - headers.insert_last_modified(date); - - response -} - -fn str_to_path(path: &str) -> PathBuf { - PathBuf::from(&format!("{WEBSITE_PATH}{path}")) -} diff --git a/server/src/website/handlers/minify.rs b/server/src/website/handlers/minify.rs deleted file mode 100644 index 5c6a26074..000000000 --- a/server/src/website/handlers/minify.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Simplified version of: https://github.com/swc-project/swc/blob/main/crates/swc/examples/minify.rs - -use std::{path::Path, sync::Arc}; - -use swc::{config::JsMinifyOptions, try_with_handler, JsMinifyExtras}; -use swc_common::{SourceMap, GLOBALS}; - -pub fn minify_js(path: &Path) -> String { - let cm = Arc::::default(); - - let c = swc::Compiler::new(cm.clone()); - - let output = GLOBALS - .set(&Default::default(), || { - try_with_handler(cm.clone(), Default::default(), |handler| { - let fm = cm.load_file(path).expect("failed to load file"); - - c.minify( - fm, - handler, - &JsMinifyOptions::default(), - JsMinifyExtras::default(), - ) - }) - }) - .unwrap(); - - output.code -} diff --git a/server/src/website/handlers/mod.rs b/server/src/website/handlers/mod.rs deleted file mode 100644 index a0c087264..000000000 --- a/server/src/website/handlers/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod file; -mod minify; - -pub use file::*; -use minify::*; diff --git a/server/src/website/mod.rs b/server/src/website/mod.rs deleted file mode 100644 index 77cd1a1c9..000000000 --- a/server/src/website/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use axum::{routing::get, Router}; - -mod handlers; - -use handlers::{file_handler, index_handler}; - -use crate::AppState; - -pub trait WebsiteRoutes { - fn add_website_routes(self) -> Self; -} - -impl WebsiteRoutes for Router { - fn add_website_routes(self) -> Self { - self.route("/*path", get(file_handler)) - .route("/", get(index_handler)) - } -} diff --git a/website/assets/fonts/satoshi/2024-09/font.var.woff2 b/websites/kibo.money/assets/fonts/satoshi/2024-09/font.var.woff2 similarity index 100% rename from website/assets/fonts/satoshi/2024-09/font.var.woff2 rename to websites/kibo.money/assets/fonts/satoshi/2024-09/font.var.woff2 diff --git a/website/assets/fonts/satoshi/FFL.txt b/websites/kibo.money/assets/fonts/satoshi/FFL.txt similarity index 100% rename from website/assets/fonts/satoshi/FFL.txt rename to websites/kibo.money/assets/fonts/satoshi/FFL.txt diff --git a/website/assets/pdfs/block/2022-report.pdf b/websites/kibo.money/assets/pdfs/block/2022-report.pdf similarity index 100% rename from website/assets/pdfs/block/2022-report.pdf rename to websites/kibo.money/assets/pdfs/block/2022-report.pdf diff --git a/website/assets/pdfs/braiins/building-bitcoin-in-rust.pdf b/websites/kibo.money/assets/pdfs/braiins/building-bitcoin-in-rust.pdf similarity index 100% rename from website/assets/pdfs/braiins/building-bitcoin-in-rust.pdf rename to websites/kibo.money/assets/pdfs/braiins/building-bitcoin-in-rust.pdf diff --git a/website/assets/pdfs/glassnode/cointime-economics.pdf b/websites/kibo.money/assets/pdfs/glassnode/cointime-economics.pdf similarity index 100% rename from website/assets/pdfs/glassnode/cointime-economics.pdf rename to websites/kibo.money/assets/pdfs/glassnode/cointime-economics.pdf diff --git a/website/assets/pdfs/multi-author/bcap_v1.0.pdf b/websites/kibo.money/assets/pdfs/multi-author/bcap_v1.0.pdf similarity index 100% rename from website/assets/pdfs/multi-author/bcap_v1.0.pdf rename to websites/kibo.money/assets/pdfs/multi-author/bcap_v1.0.pdf diff --git a/website/assets/pdfs/nakamoto-project/understanding-bitcoin-adoption-in-the-united-states.pdf b/websites/kibo.money/assets/pdfs/nakamoto-project/understanding-bitcoin-adoption-in-the-united-states.pdf similarity index 100% rename from website/assets/pdfs/nakamoto-project/understanding-bitcoin-adoption-in-the-united-states.pdf rename to websites/kibo.money/assets/pdfs/nakamoto-project/understanding-bitcoin-adoption-in-the-united-states.pdf diff --git a/website/assets/pdfs/nydig/protection-under-first-amendment.pdf b/websites/kibo.money/assets/pdfs/nydig/protection-under-first-amendment.pdf similarity index 100% rename from website/assets/pdfs/nydig/protection-under-first-amendment.pdf rename to websites/kibo.money/assets/pdfs/nydig/protection-under-first-amendment.pdf diff --git a/website/assets/pdfs/satoshi-nakamoto/whitepaper.pdf b/websites/kibo.money/assets/pdfs/satoshi-nakamoto/whitepaper.pdf similarity index 100% rename from website/assets/pdfs/satoshi-nakamoto/whitepaper.pdf rename to websites/kibo.money/assets/pdfs/satoshi-nakamoto/whitepaper.pdf diff --git a/website/assets/pdfs/square/2021-bitcoin-clean-energy-initiative.pdf b/websites/kibo.money/assets/pdfs/square/2021-bitcoin-clean-energy-initiative.pdf similarity index 100% rename from website/assets/pdfs/square/2021-bitcoin-clean-energy-initiative.pdf rename to websites/kibo.money/assets/pdfs/square/2021-bitcoin-clean-energy-initiative.pdf diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg diff --git a/website/assets/pwa/2024-11-20_09-41-25/favicon-196.png b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/favicon-196.png similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/favicon-196.png rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/favicon-196.png diff --git a/website/assets/pwa/2024-11-20_09-41-25/index.html b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/index.html similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/index.html rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/index.html diff --git a/website/assets/pwa/2024-11-20_09-41-25/manifest-icon-192.maskable.png b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/manifest-icon-192.maskable.png similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/manifest-icon-192.maskable.png rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/manifest-icon-192.maskable.png diff --git a/website/assets/pwa/2024-11-20_09-41-25/manifest-icon-512.maskable.png b/websites/kibo.money/assets/pwa/2024-11-20_09-41-25/manifest-icon-512.maskable.png similarity index 100% rename from website/assets/pwa/2024-11-20_09-41-25/manifest-icon-512.maskable.png rename to websites/kibo.money/assets/pwa/2024-11-20_09-41-25/manifest-icon-512.maskable.png diff --git a/website/assets/pwa/index.html b/websites/kibo.money/assets/pwa/index.html similarity index 100% rename from website/assets/pwa/index.html rename to websites/kibo.money/assets/pwa/index.html diff --git a/website/generate-icons.sh b/websites/kibo.money/generate-icons.sh similarity index 100% rename from website/generate-icons.sh rename to websites/kibo.money/generate-icons.sh diff --git a/website/index.html b/websites/kibo.money/index.html similarity index 99% rename from website/index.html rename to websites/kibo.money/index.html index b649580b9..a48b547c3 100644 --- a/website/index.html +++ b/websites/kibo.money/index.html @@ -513,6 +513,7 @@ input { border: 0; width: 100%; + text-align: left; &:focus-visible { outline: none; @@ -834,32 +835,6 @@ } } - aside { - > header { - button { - margin: -0.5rem 0; - - &:first-child { - margin-left: -0.65rem; - } - - &:last-child { - margin-right: -0.65rem; - } - - &[data-highlight] { - * { - color: var(--orange) !important; - } - } - - > svg { - margin: 0.5rem; - } - } - } - } - #share-div { padding: 1.5rem; backdrop-filter: blur(12px); @@ -1543,6 +1518,7 @@ " >希望 + 希望.お金 Bitcoin is diff --git a/website/jsconfig.json b/websites/kibo.money/jsconfig.json similarity index 100% rename from website/jsconfig.json rename to websites/kibo.money/jsconfig.json diff --git a/website/manifest.webmanifest b/websites/kibo.money/manifest.webmanifest similarity index 100% rename from website/manifest.webmanifest rename to websites/kibo.money/manifest.webmanifest diff --git a/website/misc/tailwindTo550.js b/websites/kibo.money/misc/tailwindTo550.js similarity index 100% rename from website/misc/tailwindTo550.js rename to websites/kibo.money/misc/tailwindTo550.js diff --git a/website/packages/lean-qr/README.md b/websites/kibo.money/packages/lean-qr/README.md similarity index 100% rename from website/packages/lean-qr/README.md rename to websites/kibo.money/packages/lean-qr/README.md diff --git a/website/packages/lean-qr/v2.3.4/script.js b/websites/kibo.money/packages/lean-qr/v2.3.4/script.js similarity index 100% rename from website/packages/lean-qr/v2.3.4/script.js rename to websites/kibo.money/packages/lean-qr/v2.3.4/script.js diff --git a/website/packages/lean-qr/v2.3.4/types.d.ts b/websites/kibo.money/packages/lean-qr/v2.3.4/types.d.ts similarity index 100% rename from website/packages/lean-qr/v2.3.4/types.d.ts rename to websites/kibo.money/packages/lean-qr/v2.3.4/types.d.ts diff --git a/website/packages/lightweight-charts/NOTICE.md b/websites/kibo.money/packages/lightweight-charts/NOTICE.md similarity index 100% rename from website/packages/lightweight-charts/NOTICE.md rename to websites/kibo.money/packages/lightweight-charts/NOTICE.md diff --git a/website/packages/lightweight-charts/README.md b/websites/kibo.money/packages/lightweight-charts/README.md similarity index 100% rename from website/packages/lightweight-charts/README.md rename to websites/kibo.money/packages/lightweight-charts/README.md diff --git a/website/packages/lightweight-charts/types.d.ts b/websites/kibo.money/packages/lightweight-charts/types.d.ts similarity index 97% rename from website/packages/lightweight-charts/types.d.ts rename to websites/kibo.money/packages/lightweight-charts/types.d.ts index 02c5ab127..ca3fb9e42 100644 --- a/website/packages/lightweight-charts/types.d.ts +++ b/websites/kibo.money/packages/lightweight-charts/types.d.ts @@ -1,6 +1,5 @@ import { Signal } from "../solid-signals/types"; import { Accessor } from "../solid-signals/2024-11-02/types/signals"; -import { Owner } from "../solid-signals/2024-11-02/types/core/owner"; import { DeepPartial, BaselineStyleOptions, @@ -11,7 +10,7 @@ import { CandlestickData, ISeriesApi, BaselineData, -} from "./v4.2.0/types"; +} from "./v4.2.2/types"; import { Color, Valued, ValuedCandlestickData } from "../../scripts/types/self"; interface BaseSeriesBlueprint { diff --git a/website/packages/lightweight-charts/v4.2.0/script.js b/websites/kibo.money/packages/lightweight-charts/v4.2.0/script.js similarity index 100% rename from website/packages/lightweight-charts/v4.2.0/script.js rename to websites/kibo.money/packages/lightweight-charts/v4.2.0/script.js diff --git a/website/packages/lightweight-charts/v4.2.0/types.d.ts b/websites/kibo.money/packages/lightweight-charts/v4.2.0/types.d.ts similarity index 100% rename from website/packages/lightweight-charts/v4.2.0/types.d.ts rename to websites/kibo.money/packages/lightweight-charts/v4.2.0/types.d.ts diff --git a/websites/kibo.money/packages/lightweight-charts/v4.2.2/script.js b/websites/kibo.money/packages/lightweight-charts/v4.2.2/script.js new file mode 100644 index 000000000..3c5693745 --- /dev/null +++ b/websites/kibo.money/packages/lightweight-charts/v4.2.2/script.js @@ -0,0 +1,9 @@ +// @ts-nocheck + +/*! + * @license + * TradingView Lightweight Charts™ v4.2.2 + * Copyright (c) 2024 TradingView, Inc. + * Licensed under Apache License 2.0 https://www.apache.org/licenses/LICENSE-2.0 + */ +const t={upColor:"#26a69a",downColor:"#ef5350",wickVisible:!0,borderVisible:!0,borderColor:"#378658",borderUpColor:"#26a69a",borderDownColor:"#ef5350",wickColor:"#737375",wickUpColor:"#26a69a",wickDownColor:"#ef5350"},i={upColor:"#26a69a",downColor:"#ef5350",openVisible:!0,thinBars:!0},n={color:"#2196f3",lineStyle:0,lineWidth:3,lineType:0,lineVisible:!0,crosshairMarkerVisible:!0,crosshairMarkerRadius:4,crosshairMarkerBorderColor:"",crosshairMarkerBorderWidth:2,crosshairMarkerBackgroundColor:"",lastPriceAnimation:0,pointMarkersVisible:!1},s={topColor:"rgba( 46, 220, 135, 0.4)",bottomColor:"rgba( 40, 221, 100, 0)",invertFilledArea:!1,lineColor:"#33D778",lineStyle:0,lineWidth:3,lineType:0,lineVisible:!0,crosshairMarkerVisible:!0,crosshairMarkerRadius:4,crosshairMarkerBorderColor:"",crosshairMarkerBorderWidth:2,crosshairMarkerBackgroundColor:"",lastPriceAnimation:0,pointMarkersVisible:!1},e={baseValue:{type:"price",price:0},topFillColor1:"rgba(38, 166, 154, 0.28)",topFillColor2:"rgba(38, 166, 154, 0.05)",topLineColor:"rgba(38, 166, 154, 1)",bottomFillColor1:"rgba(239, 83, 80, 0.05)",bottomFillColor2:"rgba(239, 83, 80, 0.28)",bottomLineColor:"rgba(239, 83, 80, 1)",lineWidth:3,lineStyle:0,lineType:0,lineVisible:!0,crosshairMarkerVisible:!0,crosshairMarkerRadius:4,crosshairMarkerBorderColor:"",crosshairMarkerBorderWidth:2,crosshairMarkerBackgroundColor:"",lastPriceAnimation:0,pointMarkersVisible:!1},r={color:"#26a69a",base:0},h={color:"#2196f3"},l={title:"",visible:!0,lastValueVisible:!0,priceLineVisible:!0,priceLineSource:0,priceLineWidth:1,priceLineColor:"",priceLineStyle:2,baseLineVisible:!0,baseLineWidth:1,baseLineColor:"#B2B5BE",baseLineStyle:0,priceFormat:{type:"price",precision:2,minMove:.01}};var a,o;function _(t,i){const n={0:[],1:[t.lineWidth,t.lineWidth],2:[2*t.lineWidth,2*t.lineWidth],3:[6*t.lineWidth,6*t.lineWidth],4:[t.lineWidth,4*t.lineWidth]}[i];t.setLineDash(n)}function u(t,i,n,s){t.beginPath();const e=t.lineWidth%2?.5:0;t.moveTo(n,i+e),t.lineTo(s,i+e),t.stroke()}function c(t,i){if(!t)throw new Error("Assertion failed"+(i?": "+i:""))}function d(t){if(void 0===t)throw new Error("Value is undefined");return t}function f(t){if(null===t)throw new Error("Value is null");return t}function v(t){return f(d(t))}!function(t){t[t.Simple=0]="Simple",t[t.WithSteps=1]="WithSteps",t[t.Curved=2]="Curved"}(a||(a={})),function(t){t[t.Solid=0]="Solid",t[t.Dotted=1]="Dotted",t[t.Dashed=2]="Dashed",t[t.LargeDashed=3]="LargeDashed",t[t.SparseDotted=4]="SparseDotted"}(o||(o={}));const p={khaki:"#f0e68c",azure:"#f0ffff",aliceblue:"#f0f8ff",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gainsboro:"#dcdcdc",gray:"#808080",green:"#008000",honeydew:"#f0fff0",floralwhite:"#fffaf0",lightblue:"#add8e6",lightcoral:"#f08080",lemonchiffon:"#fffacd",hotpink:"#ff69b4",lightyellow:"#ffffe0",greenyellow:"#adff2f",lightgoldenrodyellow:"#fafad2",limegreen:"#32cd32",linen:"#faf0e6",lightcyan:"#e0ffff",magenta:"#f0f",maroon:"#800000",olive:"#808000",orange:"#ffa500",oldlace:"#fdf5e6",mediumblue:"#0000cd",transparent:"#0000",lime:"#0f0",lightpink:"#ffb6c1",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",midnightblue:"#191970",orchid:"#da70d6",mediumorchid:"#ba55d3",mediumturquoise:"#48d1cc",orangered:"#ff4500",royalblue:"#4169e1",powderblue:"#b0e0e6",red:"#f00",coral:"#ff7f50",turquoise:"#40e0d0",white:"#fff",whitesmoke:"#f5f5f5",wheat:"#f5deb3",teal:"#008080",steelblue:"#4682b4",bisque:"#ffe4c4",aquamarine:"#7fffd4",aqua:"#0ff",sienna:"#a0522d",silver:"#c0c0c0",springgreen:"#00ff7f",antiquewhite:"#faebd7",burlywood:"#deb887",brown:"#a52a2a",beige:"#f5f5dc",chocolate:"#d2691e",chartreuse:"#7fff00",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cadetblue:"#5f9ea0",tomato:"#ff6347",fuchsia:"#f0f",blue:"#00f",salmon:"#fa8072",blanchedalmond:"#ffebcd",slateblue:"#6a5acd",slategray:"#708090",thistle:"#d8bfd8",tan:"#d2b48c",cyan:"#0ff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",blueviolet:"#8a2be2",black:"#000",darkmagenta:"#8b008b",darkslateblue:"#483d8b",darkkhaki:"#bdb76b",darkorchid:"#9932cc",darkorange:"#ff8c00",darkgreen:"#006400",darkred:"#8b0000",dodgerblue:"#1e90ff",darkslategray:"#2f4f4f",dimgray:"#696969",deepskyblue:"#00bfff",firebrick:"#b22222",forestgreen:"#228b22",indigo:"#4b0082",ivory:"#fffff0",lavenderblush:"#fff0f5",feldspar:"#d19275",indianred:"#cd5c5c",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightskyblue:"#87cefa",lightslategray:"#789",lightslateblue:"#8470ff",snow:"#fffafa",lightseagreen:"#20b2aa",lightsalmon:"#ffa07a",darksalmon:"#e9967a",darkviolet:"#9400d3",mediumpurple:"#9370d8",mediumaquamarine:"#66cdaa",skyblue:"#87ceeb",lavender:"#e6e6fa",lightsteelblue:"#b0c4de",mediumvioletred:"#c71585",mintcream:"#f5fffa",navajowhite:"#ffdead",navy:"#000080",olivedrab:"#6b8e23",palevioletred:"#d87093",violetred:"#d02090",yellow:"#ff0",yellowgreen:"#9acd32",lawngreen:"#7cfc00",pink:"#ffc0cb",paleturquoise:"#afeeee",palegoldenrod:"#eee8aa",darkolivegreen:"#556b2f",darkseagreen:"#8fbc8f",darkturquoise:"#00ced1",peachpuff:"#ffdab9",deeppink:"#ff1493",violet:"#ee82ee",palegreen:"#98fb98",mediumseagreen:"#3cb371",peru:"#cd853f",saddlebrown:"#8b4513",sandybrown:"#f4a460",rosybrown:"#bc8f8f",purple:"#800080",seagreen:"#2e8b57",seashell:"#fff5ee",papayawhip:"#ffefd5",mediumslateblue:"#7b68ee",plum:"#dda0dd",mediumspringgreen:"#00fa9a"};function m(t){return t<0?0:t>255?255:Math.round(t)||0}function b(t){return t<=0||t>1?Math.min(Math.max(t,0),1):Math.round(1e4*t)/1e4}const w=/^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/i,g=/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i,M=/^rgb\(\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*\)$/,x=/^rgba\(\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*,\s*(-?\d{1,10})\s*,\s*(-?\d*\.?\d+)\s*\)$/;function S(t){(t=t.toLowerCase())in p&&(t=p[t]);{const i=x.exec(t)||M.exec(t);if(i)return[m(parseInt(i[1],10)),m(parseInt(i[2],10)),m(parseInt(i[3],10)),b(i.length<5?1:parseFloat(i[4]))]}{const i=g.exec(t);if(i)return[m(parseInt(i[1],16)),m(parseInt(i[2],16)),m(parseInt(i[3],16)),1]}{const i=w.exec(t);if(i)return[m(17*parseInt(i[1],16)),m(17*parseInt(i[2],16)),m(17*parseInt(i[3],16)),1]}throw new Error(`Cannot parse color: ${t}`)}function k(t){return.199*t[0]+.687*t[1]+.114*t[2]}function y(t){const i=S(t);return{t:`rgb(${i[0]}, ${i[1]}, ${i[2]})`,i:k(i)>160?"black":"white"}}class C{constructor(){this.h=[]}l(t,i,n){const s={o:t,_:i,u:!0===n};this.h.push(s)}v(t){const i=this.h.findIndex((i=>t===i.o));i>-1&&this.h.splice(i,1)}p(t){this.h=this.h.filter((i=>i._!==t))}m(t,i,n){const s=[...this.h];this.h=this.h.filter((t=>!t.u)),s.forEach((s=>s.o(t,i,n)))}M(){return this.h.length>0}S(){this.h=[]}}function T(t,...i){for(const n of i)for(const i in n)void 0!==n[i]&&("object"!=typeof n[i]||void 0===t[i]||Array.isArray(n[i])?t[i]=n[i]:T(t[i],n[i]));return t}function P(t){return"number"==typeof t&&isFinite(t)}function R(t){return"number"==typeof t&&t%1==0}function D(t){return"string"==typeof t}function O(t){return"boolean"==typeof t}function V(t){const i=t;if(!i||"object"!=typeof i)return i;let n,s,e;for(s in n=Array.isArray(i)?[]:{},i)i.hasOwnProperty(s)&&(e=i[s],n[s]=e&&"object"==typeof e?V(e):e);return n}function B(t){return null!==t}function A(t){return null===t?void 0:t}const z="-apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif";function E(t,i,n){return void 0===i&&(i=z),`${n=void 0!==n?`${n} `:""}${t}px ${i}`}class I{constructor(t){this.k={C:1,T:5,P:NaN,R:"",D:"",O:"",V:"",B:0,A:0,I:0,L:0,N:0},this.F=t}W(){const t=this.k,i=this.j(),n=this.H();return t.P===i&&t.D===n||(t.P=i,t.D=n,t.R=E(i,n),t.L=2.5/12*i,t.B=t.L,t.A=i/12*t.T,t.I=i/12*t.T,t.N=0),t.O=this.$(),t.V=this.U(),this.k}$(){return this.F.W().layout.textColor}U(){return this.F.q()}j(){return this.F.W().layout.fontSize}H(){return this.F.W().layout.fontFamily}}class L{constructor(){this.Y=[]}Z(t){this.Y=t}X(t,i,n){this.Y.forEach((s=>{s.X(t,i,n)}))}}class N{X(t,i,n){t.useBitmapCoordinateSpace((t=>this.K(t,i,n)))}}class F extends N{constructor(){super(...arguments),this.G=null}J(t){this.G=t}K({context:t,horizontalPixelRatio:i,verticalPixelRatio:n}){if(null===this.G||null===this.G.tt)return;const s=this.G.tt,e=this.G,r=Math.max(1,Math.floor(i))%2/2,h=h=>{t.beginPath();for(let l=s.to-1;l>=s.from;--l){const s=e.it[l],a=Math.round(s.nt*i)+r,o=s.st*n,_=h*n+r;t.moveTo(a,o),t.arc(a,o,_,0,2*Math.PI)}t.fill()};e.et>0&&(t.fillStyle=e.rt,h(e.ht+e.et)),t.fillStyle=e.lt,h(e.ht)}}function W(){return{it:[{nt:0,st:0,ot:0,_t:0}],lt:"",rt:"",ht:0,et:0,tt:null}}const j={from:0,to:1};class H{constructor(t,i){this.ut=new L,this.ct=[],this.dt=[],this.ft=!0,this.F=t,this.vt=i,this.ut.Z(this.ct)}bt(t){const i=this.F.wt();i.length!==this.ct.length&&(this.dt=i.map(W),this.ct=this.dt.map((t=>{const i=new F;return i.J(t),i})),this.ut.Z(this.ct)),this.ft=!0}gt(){return this.ft&&(this.Mt(),this.ft=!1),this.ut}Mt(){const t=2===this.vt.W().mode,i=this.F.wt(),n=this.vt.xt(),s=this.F.St();i.forEach(((i,e)=>{var r;const h=this.dt[e],l=i.kt(n);if(t||null===l||!i.yt())return void(h.tt=null);const a=f(i.Ct());h.lt=l.Tt,h.ht=l.ht,h.et=l.Pt,h.it[0]._t=l._t,h.it[0].st=i.Dt().Rt(l._t,a.Ot),h.rt=null!==(r=l.Vt)&&void 0!==r?r:this.F.Bt(h.it[0].st/i.Dt().At()),h.it[0].ot=n,h.it[0].nt=s.zt(n),h.tt=j}))}}class $ extends N{constructor(t){super(),this.Et=t}K({context:t,bitmapSize:i,horizontalPixelRatio:n,verticalPixelRatio:s}){if(null===this.Et)return;const e=this.Et.It.yt,r=this.Et.Lt.yt;if(!e&&!r)return;const h=Math.round(this.Et.nt*n),l=Math.round(this.Et.st*s);t.lineCap="butt",e&&h>=0&&(t.lineWidth=Math.floor(this.Et.It.et*n),t.strokeStyle=this.Et.It.O,t.fillStyle=this.Et.It.O,_(t,this.Et.It.Nt),function(t,i,n,s){t.beginPath();const e=t.lineWidth%2?.5:0;t.moveTo(i+e,n),t.lineTo(i+e,s),t.stroke()}(t,h,0,i.height)),r&&l>=0&&(t.lineWidth=Math.floor(this.Et.Lt.et*s),t.strokeStyle=this.Et.Lt.O,t.fillStyle=this.Et.Lt.O,_(t,this.Et.Lt.Nt),u(t,l,0,i.width))}}class U{constructor(t){this.ft=!0,this.Ft={It:{et:1,Nt:0,O:"",yt:!1},Lt:{et:1,Nt:0,O:"",yt:!1},nt:0,st:0},this.Wt=new $(this.Ft),this.jt=t}bt(){this.ft=!0}gt(){return this.ft&&(this.Mt(),this.ft=!1),this.Wt}Mt(){const t=this.jt.yt(),i=f(this.jt.Ht()),n=i.$t().W().crosshair,s=this.Ft;if(2===n.mode)return s.Lt.yt=!1,void(s.It.yt=!1);s.Lt.yt=t&&this.jt.Ut(i),s.It.yt=t&&this.jt.qt(),s.Lt.et=n.horzLine.width,s.Lt.Nt=n.horzLine.style,s.Lt.O=n.horzLine.color,s.It.et=n.vertLine.width,s.It.Nt=n.vertLine.style,s.It.O=n.vertLine.color,s.nt=this.jt.Yt(),s.st=this.jt.Zt()}}function q(t,i,n,s,e,r){t.fillRect(i+r,n,s-2*r,r),t.fillRect(i+r,n+e-r,s-2*r,r),t.fillRect(i,n,r,e),t.fillRect(i+s-r,n,r,e)}function Y(t,i,n,s,e,r){t.save(),t.globalCompositeOperation="copy",t.fillStyle=r,t.fillRect(i,n,s,e),t.restore()}function Z(t,i,n,s,e,r){t.beginPath(),t.roundRect?t.roundRect(i,n,s,e,r):(t.lineTo(i+s-r[1],n),0!==r[1]&&t.arcTo(i+s,n,i+s,n+r[1],r[1]),t.lineTo(i+s,n+e-r[2]),0!==r[2]&&t.arcTo(i+s,n+e,i+s-r[2],n+e,r[2]),t.lineTo(i+r[3],n+e),0!==r[3]&&t.arcTo(i,n+e,i,n+e-r[3],r[3]),t.lineTo(i,n+r[0]),0!==r[0]&&t.arcTo(i,n,i+r[0],n,r[0]))}function X(t,i,n,s,e,r,h=0,l=[0,0,0,0],a=""){if(t.save(),!h||!a||a===r)return Z(t,i,n,s,e,l),t.fillStyle=r,t.fill(),void t.restore();const o=h/2;var _;Z(t,i+o,n+o,s-h,e-h,(_=-o,l.map((t=>0===t?t:t+_)))),"transparent"!==r&&(t.fillStyle=r,t.fill()),"transparent"!==a&&(t.lineWidth=h,t.strokeStyle=a,t.closePath(),t.stroke()),t.restore()}function K(t,i,n,s,e,r,h){t.save(),t.globalCompositeOperation="copy";const l=t.createLinearGradient(0,0,0,e);l.addColorStop(0,r),l.addColorStop(1,h),t.fillStyle=l,t.fillRect(i,n,s,e),t.restore()}class G{constructor(t,i){this.J(t,i)}J(t,i){this.Et=t,this.Xt=i}At(t,i){return this.Et.yt?t.P+t.L+t.B:0}X(t,i,n,s){if(!this.Et.yt||0===this.Et.Kt.length)return;const e=this.Et.O,r=this.Xt.t,h=t.useBitmapCoordinateSpace((t=>{const h=t.context;h.font=i.R;const l=this.Gt(t,i,n,s),a=l.Jt;return l.Qt?X(h,a.ti,a.ii,a.ni,a.si,r,a.ei,[a.ht,0,0,a.ht],r):X(h,a.ri,a.ii,a.ni,a.si,r,a.ei,[0,a.ht,a.ht,0],r),this.Et.hi&&(h.fillStyle=e,h.fillRect(a.ri,a.li,a.ai-a.ri,a.oi)),this.Et._i&&(h.fillStyle=i.V,h.fillRect(l.Qt?a.ui-a.ei:0,a.ii,a.ei,a.ci-a.ii)),l}));t.useMediaCoordinateSpace((({context:t})=>{const n=h.di;t.font=i.R,t.textAlign=h.Qt?"right":"left",t.textBaseline="middle",t.fillStyle=e,t.fillText(this.Et.Kt,n.fi,(n.ii+n.ci)/2+n.pi)}))}Gt(t,i,n,s){var e;const{context:r,bitmapSize:h,mediaSize:l,horizontalPixelRatio:a,verticalPixelRatio:o}=t,_=this.Et.hi||!this.Et.mi?i.T:0,u=this.Et.bi?i.C:0,c=i.L+this.Xt.wi,d=i.B+this.Xt.gi,f=i.A,v=i.I,p=this.Et.Kt,m=i.P,b=n.Mi(r,p),w=Math.ceil(n.xi(r,p)),g=m+c+d,M=i.C+f+v+w+_,x=Math.max(1,Math.floor(o));let S=Math.round(g*o);S%2!=x%2&&(S+=1);const k=u>0?Math.max(1,Math.floor(u*a)):0,y=Math.round(M*a),C=Math.round(_*a),T=null!==(e=this.Xt.Si)&&void 0!==e?e:this.Xt.ki,P=Math.round(T*o)-Math.floor(.5*o),R=Math.floor(P+x/2-S/2),D=R+S,O="right"===s,V=O?l.width-u:u,B=O?h.width-k:k;let A,z,E;return O?(A=B-y,z=B-C,E=V-_-f-u):(A=B+y,z=B+C,E=V+_+f),{Qt:O,Jt:{ii:R,li:P,ci:D,ni:y,si:S,ht:2*a,ei:k,ti:A,ri:B,ai:z,oi:x,ui:h.width},di:{ii:R/o,ci:D/o,fi:E,pi:b}}}}class J{constructor(t){this.yi={ki:0,t:"#000",gi:0,wi:0},this.Ci={Kt:"",yt:!1,hi:!0,mi:!1,Vt:"",O:"#FFF",_i:!1,bi:!1},this.Ti={Kt:"",yt:!1,hi:!1,mi:!0,Vt:"",O:"#FFF",_i:!0,bi:!0},this.ft=!0,this.Pi=new(t||G)(this.Ci,this.yi),this.Ri=new(t||G)(this.Ti,this.yi)}Kt(){return this.Di(),this.Ci.Kt}ki(){return this.Di(),this.yi.ki}bt(){this.ft=!0}At(t,i=!1){return Math.max(this.Pi.At(t,i),this.Ri.At(t,i))}Oi(){return this.yi.Si||0}Vi(t){this.yi.Si=t}Bi(){return this.Di(),this.Ci.yt||this.Ti.yt}Ai(){return this.Di(),this.Ci.yt}gt(t){return this.Di(),this.Ci.hi=this.Ci.hi&&t.W().ticksVisible,this.Ti.hi=this.Ti.hi&&t.W().ticksVisible,this.Pi.J(this.Ci,this.yi),this.Ri.J(this.Ti,this.yi),this.Pi}zi(){return this.Di(),this.Pi.J(this.Ci,this.yi),this.Ri.J(this.Ti,this.yi),this.Ri}Di(){this.ft&&(this.Ci.hi=!0,this.Ti.hi=!1,this.Ei(this.Ci,this.Ti,this.yi))}}class Q extends J{constructor(t,i,n){super(),this.jt=t,this.Ii=i,this.Li=n}Ei(t,i,n){if(t.yt=!1,2===this.jt.W().mode)return;const s=this.jt.W().horzLine;if(!s.labelVisible)return;const e=this.Ii.Ct();if(!this.jt.yt()||this.Ii.Ni()||null===e)return;const r=y(s.labelBackgroundColor);n.t=r.t,t.O=r.i;const h=2/12*this.Ii.P();n.wi=h,n.gi=h;const l=this.Li(this.Ii);n.ki=l.ki,t.Kt=this.Ii.Fi(l._t,e),t.yt=!0}}const tt=/[1-9]/g;class it{constructor(){this.Et=null}J(t){this.Et=t}X(t,i){if(null===this.Et||!1===this.Et.yt||0===this.Et.Kt.length)return;const n=t.useMediaCoordinateSpace((({context:t})=>(t.font=i.R,Math.round(i.Wi.xi(t,f(this.Et).Kt,tt)))));if(n<=0)return;const s=i.ji,e=n+2*s,r=e/2,h=this.Et.Hi;let l=this.Et.ki,a=Math.floor(l-r)+.5;a<0?(l+=Math.abs(0-a),a=Math.floor(l-r)+.5):a+e>h&&(l-=Math.abs(h-(a+e)),a=Math.floor(l-r)+.5);const o=a+e,_=Math.ceil(0+i.C+i.T+i.L+i.P+i.B);t.useBitmapCoordinateSpace((({context:t,horizontalPixelRatio:n,verticalPixelRatio:s})=>{const e=f(this.Et);t.fillStyle=e.t;const r=Math.round(a*n),h=Math.round(0*s),l=Math.round(o*n),u=Math.round(_*s),c=Math.round(2*n);if(t.beginPath(),t.moveTo(r,h),t.lineTo(r,u-c),t.arcTo(r,u,r+c,u,c),t.lineTo(l-c,u),t.arcTo(l,u,l,u-c,c),t.lineTo(l,h),t.fill(),e.hi){const r=Math.round(e.ki*n),l=h,a=Math.round((l+i.T)*s);t.fillStyle=e.O;const o=Math.max(1,Math.floor(n)),_=Math.floor(.5*n);t.fillRect(r-_,l,o,a-l)}})),t.useMediaCoordinateSpace((({context:t})=>{const n=f(this.Et),e=0+i.C+i.T+i.L+i.P/2;t.font=i.R,t.textAlign="left",t.textBaseline="middle",t.fillStyle=n.O;const r=i.Wi.Mi(t,"Apr0");t.translate(a+s,e+r),t.fillText(n.Kt,0,0)}))}}class nt{constructor(t,i,n){this.ft=!0,this.Wt=new it,this.Ft={yt:!1,t:"#4c525e",O:"white",Kt:"",Hi:0,ki:NaN,hi:!0},this.vt=t,this.$i=i,this.Li=n}bt(){this.ft=!0}gt(){return this.ft&&(this.Mt(),this.ft=!1),this.Wt.J(this.Ft),this.Wt}Mt(){const t=this.Ft;if(t.yt=!1,2===this.vt.W().mode)return;const i=this.vt.W().vertLine;if(!i.labelVisible)return;const n=this.$i.St();if(n.Ni())return;t.Hi=n.Hi();const s=this.Li();if(null===s)return;t.ki=s.ki;const e=n.Ui(this.vt.xt());t.Kt=n.qi(f(e)),t.yt=!0;const r=y(i.labelBackgroundColor);t.t=r.t,t.O=r.i,t.hi=n.W().ticksVisible}}class st{constructor(){this.Yi=null,this.Zi=0}Xi(){return this.Zi}Ki(t){this.Zi=t}Dt(){return this.Yi}Gi(t){this.Yi=t}Ji(t){return[]}Qi(){return[]}yt(){return!0}}var et;!function(t){t[t.Normal=0]="Normal",t[t.Magnet=1]="Magnet",t[t.Hidden=2]="Hidden"}(et||(et={}));class rt extends st{constructor(t,i){super(),this.tn=null,this.nn=NaN,this.sn=0,this.en=!0,this.rn=new Map,this.hn=!1,this.ln=NaN,this.an=NaN,this._n=NaN,this.un=NaN,this.$i=t,this.cn=i,this.dn=new H(t,this);this.fn=((t,i)=>n=>{const s=i(),e=t();if(n===f(this.tn).vn())return{_t:e,ki:s};{const t=f(n.Ct());return{_t:n.pn(s,t),ki:s}}})((()=>this.nn),(()=>this.an));const n=((t,i)=>()=>{const n=this.$i.St().mn(t()),s=i();return n&&Number.isFinite(s)?{ot:n,ki:s}:null})((()=>this.sn),(()=>this.Yt()));this.bn=new nt(this,t,n),this.wn=new U(this)}W(){return this.cn}gn(t,i){this._n=t,this.un=i}Mn(){this._n=NaN,this.un=NaN}xn(){return this._n}Sn(){return this.un}kn(t,i,n){this.hn||(this.hn=!0),this.en=!0,this.yn(t,i,n)}xt(){return this.sn}Yt(){return this.ln}Zt(){return this.an}yt(){return this.en}Cn(){this.en=!1,this.Tn(),this.nn=NaN,this.ln=NaN,this.an=NaN,this.tn=null,this.Mn()}Pn(t){return null!==this.tn?[this.wn,this.dn]:[]}Ut(t){return t===this.tn&&this.cn.horzLine.visible}qt(){return this.cn.vertLine.visible}Rn(t,i){this.en&&this.tn===t||this.rn.clear();const n=[];return this.tn===t&&n.push(this.Dn(this.rn,i,this.fn)),n}Qi(){return this.en?[this.bn]:[]}Ht(){return this.tn}On(){this.wn.bt(),this.rn.forEach((t=>t.bt())),this.bn.bt(),this.dn.bt()}Vn(t){return t&&!t.vn().Ni()?t.vn():null}yn(t,i,n){this.Bn(t,i,n)&&this.On()}Bn(t,i,n){const s=this.ln,e=this.an,r=this.nn,h=this.sn,l=this.tn,a=this.Vn(n);this.sn=t,this.ln=isNaN(t)?NaN:this.$i.St().zt(t),this.tn=n;const o=null!==a?a.Ct():null;return null!==a&&null!==o?(this.nn=i,this.an=a.Rt(i,o)):(this.nn=NaN,this.an=NaN),s!==this.ln||e!==this.an||h!==this.sn||r!==this.nn||l!==this.tn}Tn(){const t=this.$i.wt().map((t=>t.zn().An())).filter(B),i=0===t.length?null:Math.max(...t);this.sn=null!==i?i:NaN}Dn(t,i,n){let s=t.get(i);return void 0===s&&(s=new Q(this,i,n),t.set(i,s)),s}}function ht(t){return"left"===t||"right"===t}class lt{constructor(t){this.En=new Map,this.In=[],this.Ln=t}Nn(t,i){const n=function(t,i){return void 0===t?i:{Fn:Math.max(t.Fn,i.Fn),Wn:t.Wn||i.Wn}}(this.En.get(t),i);this.En.set(t,n)}jn(){return this.Ln}Hn(t){const i=this.En.get(t);return void 0===i?{Fn:this.Ln}:{Fn:Math.max(this.Ln,i.Fn),Wn:i.Wn}}$n(){this.Un(),this.In=[{qn:0}]}Yn(t){this.Un(),this.In=[{qn:1,Ot:t}]}Zn(t){this.Xn(),this.In.push({qn:5,Ot:t})}Un(){this.Xn(),this.In.push({qn:6})}Kn(){this.Un(),this.In=[{qn:4}]}Gn(t){this.Un(),this.In.push({qn:2,Ot:t})}Jn(t){this.Un(),this.In.push({qn:3,Ot:t})}Qn(){return this.In}ts(t){for(const i of t.In)this.ns(i);this.Ln=Math.max(this.Ln,t.Ln),t.En.forEach(((t,i)=>{this.Nn(i,t)}))}static ss(){return new lt(2)}static es(){return new lt(3)}ns(t){switch(t.qn){case 0:this.$n();break;case 1:this.Yn(t.Ot);break;case 2:this.Gn(t.Ot);break;case 3:this.Jn(t.Ot);break;case 4:this.Kn();break;case 5:this.Zn(t.Ot);break;case 6:this.Xn()}}Xn(){const t=this.In.findIndex((t=>5===t.qn));-1!==t&&this.In.splice(t,1)}}const at=".";function ot(t,i){if(!P(t))return"n/a";if(!R(i))throw new TypeError("invalid length");if(i<0||i>16)throw new TypeError("invalid length");if(0===i)return t.toString();return("0000000000000000"+t.toString()).slice(-i)}class _t{constructor(t,i){if(i||(i=1),P(t)&&R(t)||(t=100),t<0)throw new TypeError("invalid base");this.Ii=t,this.rs=i,this.hs()}format(t){const i=t<0?"−":"";return t=Math.abs(t),i+this.ls(t)}hs(){if(this.os=0,this.Ii>0&&this.rs>0){let t=this.Ii;for(;t>1;)t/=10,this.os++}}ls(t){const i=this.Ii/this.rs;let n=Math.floor(t),s="";const e=void 0!==this.os?this.os:NaN;if(i>1){let r=+(Math.round(t*i)-n*i).toFixed(this.os);r>=i&&(r-=i,n+=1),s=at+ot(+r.toFixed(this.os)*this.rs,e)}else n=Math.round(n*i)/i,e>0&&(s=at+ot(0,e));return n.toFixed(0)+s}}class ut extends _t{constructor(t=100){super(t)}format(t){return`${super.format(t)}%`}}class ct{constructor(t){this._s=t}format(t){let i="";return t<0&&(i="-",t=-t),t<995?i+this.us(t):t<999995?i+this.us(t/1e3)+"K":t<999999995?(t=1e3*Math.round(t/1e3),i+this.us(t/1e6)+"M"):(t=1e6*Math.round(t/1e6),i+this.us(t/1e9)+"B")}us(t){let i;const n=Math.pow(10,this._s);return i=(t=Math.round(t*n)/n)>=1e-15&&t<1?t.toFixed(this._s).replace(/\.?0+$/,""):String(t),i.replace(/(\.[1-9]*)0+$/,((t,i)=>i))}}function dt(t,i,n,s,e,r,h){if(0===i.length||s.from>=i.length||s.to<=0)return;const{context:l,horizontalPixelRatio:a,verticalPixelRatio:o}=t,_=i[s.from];let u=r(t,_),c=_;if(s.to-s.from<2){const i=e/2;l.beginPath();const n={nt:_.nt-i,st:_.st},s={nt:_.nt+i,st:_.st};l.moveTo(n.nt*a,n.st*o),l.lineTo(s.nt*a,s.st*o),h(t,u,n,s)}else{const e=(i,n)=>{h(t,u,c,n),l.beginPath(),u=i,c=n};let d=c;l.beginPath(),l.moveTo(_.nt*a,_.st*o);for(let h=s.from+1;h=s.from;--n){const s=i[n];if(s){const i=e(t,s);i!==a&&(l.beginPath(),null!==a&&l.fill(),l.fillStyle=i,a=i);const n=Math.round(s.nt*r)+o,u=s.st*h;l.moveTo(n,u),l.arc(n,u,_,0,2*Math.PI)}}l.fill()}(t,i,l,n,o)}}class Tt extends Ct{Ds(t,i){return i.lt}}function Pt(t,i,n,s,e=0,r=i.length){let h=r-e;for(;0>1,l=e+r;s(i[l],n)===t?(e=l+1,h-=r+1):h=r}return e}const Rt=Pt.bind(null,!0),Dt=Pt.bind(null,!1);function Ot(t,i){return t.ot0&&r=s&&(l=r-1),h>0&&hObject.assign(Object.assign({},t),this.Is.$s().Hs(t.ot))))}Us(){this.Es=null}Fs(){this.Bs&&(this.qs(),this.Bs=!1),this.As&&(this.js(),this.As=!1),this.Vs&&(this.Ys(),this.Vs=!1)}Ys(){const t=this.Is.Dt(),i=this.Ls.St();if(this.Us(),i.Ni()||t.Ni())return;const n=i.Zs();if(null===n)return;if(0===this.Is.zn().Xs())return;const s=this.Is.Ct();null!==s&&(this.Es=Bt(this.zs,n,this.Ns),this.Ks(t,i,s.Ot),this.Gs())}}class zt extends At{constructor(t,i){super(t,i,!0)}Ks(t,i,n){i.Js(this.zs,A(this.Es)),t.Qs(this.zs,n,A(this.Es))}te(t,i){return{ot:t,_t:i,nt:NaN,st:NaN}}qs(){const t=this.Is.$s();this.zs=this.Is.zn().ie().map((i=>{const n=i.Ot[3];return this.ne(i.se,n,t)}))}}class Et extends zt{constructor(t,i){super(t,i),this.Ws=new L,this.ee=new kt,this.re=new Tt,this.Ws.Z([this.ee,this.re])}ne(t,i,n){return Object.assign(Object.assign({},this.te(t,i)),n.Hs(t))}Gs(){const t=this.Is.W();this.ee.J({ds:t.lineType,it:this.zs,Nt:t.lineStyle,et:t.lineWidth,fs:null,vs:t.invertFilledArea,tt:this.Es,cs:this.Ls.St().he()}),this.re.J({ds:t.lineVisible?t.lineType:void 0,it:this.zs,Nt:t.lineStyle,et:t.lineWidth,tt:this.Es,cs:this.Ls.St().he(),Rs:t.pointMarkersVisible?t.pointMarkersRadius||t.lineWidth/2+2:void 0})}}class It extends N{constructor(){super(...arguments),this.Et=null,this.le=0,this.ae=0}J(t){this.Et=t}K({context:t,horizontalPixelRatio:i,verticalPixelRatio:n}){if(null===this.Et||0===this.Et.zn.length||null===this.Et.tt)return;if(this.le=this.oe(i),this.le>=2){Math.max(1,Math.floor(i))%2!=this.le%2&&this.le--}this.ae=this.Et._e?Math.min(this.le,Math.floor(i)):this.le;let s=null;const e=this.ae<=this.le&&this.Et.he>=Math.floor(1.5*i);for(let r=this.Et.tt.from;rf+p-1&&(e=f+p-1,s=e-_+1),t.fillRect(i,s,o-i,e-s+1)}const i=a+m;let s=Math.max(f,Math.round(h.pe*n)-l),e=s+_-1;e>f+p-1&&(e=f+p-1,s=e-_+1),t.fillRect(u+1,s,i-u,e-s+1)}}}oe(t){const i=Math.floor(t);return Math.max(i,Math.floor(function(t,i){return Math.floor(.3*t*i)}(f(this.Et).he,t)))}}class Lt extends At{constructor(t,i){super(t,i,!1)}Ks(t,i,n){i.Js(this.zs,A(this.Es)),t.me(this.zs,n,A(this.Es))}be(t,i,n){return{ot:t,we:i.Ot[0],ge:i.Ot[1],Me:i.Ot[2],xe:i.Ot[3],nt:NaN,ve:NaN,ce:NaN,de:NaN,pe:NaN}}qs(){const t=this.Is.$s();this.zs=this.Is.zn().ie().map((i=>this.ne(i.se,i,t)))}}class Nt extends Lt{constructor(){super(...arguments),this.Ws=new It}ne(t,i,n){return Object.assign(Object.assign({},this.be(t,i,n)),n.Hs(t))}Gs(){const t=this.Is.W();this.Ws.J({zn:this.zs,he:this.Ls.St().he(),fe:t.openVisible,_e:t.thinBars,tt:this.Es})}}class Ft extends wt{constructor(){super(...arguments),this.Cs=new St}ps(t,i){const n=this.G;return this.Cs.bs(t,{gs:i.Se,Ms:i.ke,xs:i.ye,Ss:i.Ce,ks:t.bitmapSize.height,fs:n.fs})}}class Wt extends Ct{constructor(){super(...arguments),this.Te=new St}Ds(t,i){const n=this.G;return this.Te.bs(t,{gs:i.Pe,Ms:i.Pe,xs:i.Re,Ss:i.Re,ks:t.bitmapSize.height,fs:n.fs})}}class jt extends zt{constructor(t,i){super(t,i),this.Ws=new L,this.De=new Ft,this.Oe=new Wt,this.Ws.Z([this.De,this.Oe])}ne(t,i,n){return Object.assign(Object.assign({},this.te(t,i)),n.Hs(t))}Gs(){const t=this.Is.Ct();if(null===t)return;const i=this.Is.W(),n=this.Is.Dt().Rt(i.baseValue.price,t.Ot),s=this.Ls.St().he();this.De.J({it:this.zs,et:i.lineWidth,Nt:i.lineStyle,ds:i.lineType,fs:n,vs:!1,tt:this.Es,cs:s}),this.Oe.J({it:this.zs,et:i.lineWidth,Nt:i.lineStyle,ds:i.lineVisible?i.lineType:void 0,Rs:i.pointMarkersVisible?i.pointMarkersRadius||i.lineWidth/2+2:void 0,fs:n,tt:this.Es,cs:s})}}class Ht extends N{constructor(){super(...arguments),this.Et=null,this.le=0}J(t){this.Et=t}K(t){if(null===this.Et||0===this.Et.zn.length||null===this.Et.tt)return;const{horizontalPixelRatio:i}=t;if(this.le=function(t,i){if(t>=2.5&&t<=4)return Math.floor(3*i);const n=1-.2*Math.atan(Math.max(4,t)-4)/(.5*Math.PI),s=Math.floor(t*n*i),e=Math.floor(t*i),r=Math.min(s,e);return Math.max(Math.floor(i),r)}(this.Et.he,i),this.le>=2){Math.floor(i)%2!=this.le%2&&this.le--}const n=this.Et.zn;this.Et.Ve&&this.Be(t,n,this.Et.tt),this.Et._i&&this.Ae(t,n,this.Et.tt);const s=this.ze(i);(!this.Et._i||this.le>2*s)&&this.Ee(t,n,this.Et.tt)}Be(t,i,n){if(null===this.Et)return;const{context:s,horizontalPixelRatio:e,verticalPixelRatio:r}=t;let h="",l=Math.min(Math.floor(e),Math.floor(this.Et.he*e));l=Math.max(Math.floor(e),Math.min(l,this.le));const a=Math.floor(.5*l);let o=null;for(let t=n.from;t2*l)q(s,o,u,_-o+1,c-u+1,l);else{const t=_-o+1;s.fillRect(o,u,t,c-u+1)}a=_}}Ee(t,i,n){if(null===this.Et)return;const{context:s,horizontalPixelRatio:e,verticalPixelRatio:r}=t;let h="";const l=this.ze(e);for(let t=n.from;to||s.fillRect(_,a,u-_+1,o-a+1)}}}class $t extends Lt{constructor(){super(...arguments),this.Ws=new Ht}ne(t,i,n){return Object.assign(Object.assign({},this.be(t,i,n)),n.Hs(t))}Gs(){const t=this.Is.W();this.Ws.J({zn:this.zs,he:this.Ls.St().he(),Ve:t.wickVisible,_i:t.borderVisible,tt:this.Es})}}class Ut{constructor(t,i){this.Ne=t,this.Ii=i}X(t,i,n){this.Ne.draw(t,this.Ii,i,n)}}class qt extends At{constructor(t,i,n){super(t,i,!1),this.wn=n,this.Ws=new Ut(this.wn.renderer(),(i=>{const n=t.Ct();return null===n?null:t.Dt().Rt(i,n.Ot)}))}Fe(t){return this.wn.priceValueBuilder(t)}We(t){return this.wn.isWhitespace(t)}qs(){const t=this.Is.$s();this.zs=this.Is.zn().ie().map((i=>Object.assign(Object.assign({ot:i.se,nt:NaN},t.Hs(i.se)),{je:i.He})))}Ks(t,i){i.Js(this.zs,A(this.Es))}Gs(){this.wn.update({bars:this.zs.map(Yt),barSpacing:this.Ls.St().he(),visibleRange:this.Es},this.Is.W())}}function Yt(t){return{x:t.nt,time:t.ot,originalData:t.je,barColor:t.ue}}class Zt extends N{constructor(){super(...arguments),this.Et=null,this.$e=[]}J(t){this.Et=t,this.$e=[]}K({context:t,horizontalPixelRatio:i,verticalPixelRatio:n}){if(null===this.Et||0===this.Et.it.length||null===this.Et.tt)return;this.$e.length||this.Ue(i);const s=Math.max(1,Math.floor(n)),e=Math.round(this.Et.qe*n)-Math.floor(s/2),r=e+s;for(let i=this.Et.tt.from;is.Ze?s.ui=n.Os-i-1:n.Os=s.ui+i+1))}let s=Math.ceil(this.Et.he*t);for(let t=this.Et.tt.from;t0&&s<4)for(let t=this.Et.tt.from;ts&&(i.Ye>i.Ze?i.ui-=1:i.Os+=1)}}}class Xt extends zt{constructor(){super(...arguments),this.Ws=new Zt}ne(t,i,n){return Object.assign(Object.assign({},this.te(t,i)),n.Hs(t))}Gs(){const t={it:this.zs,he:this.Ls.St().he(),tt:this.Es,qe:this.Is.Dt().Rt(this.Is.W().base,f(this.Is.Ct()).Ot)};this.Ws.J(t)}}class Kt extends zt{constructor(){super(...arguments),this.Ws=new Tt}ne(t,i,n){return Object.assign(Object.assign({},this.te(t,i)),n.Hs(t))}Gs(){const t=this.Is.W(),i={it:this.zs,Nt:t.lineStyle,ds:t.lineVisible?t.lineType:void 0,et:t.lineWidth,Rs:t.pointMarkersVisible?t.pointMarkersRadius||t.lineWidth/2+2:void 0,tt:this.Es,cs:this.Ls.St().he()};this.Ws.J(i)}}const Gt=/[2-9]/g;class Jt{constructor(t=50){this.Xe=0,this.Ke=1,this.Ge=1,this.Je={},this.Qe=new Map,this.tr=t}ir(){this.Xe=0,this.Qe.clear(),this.Ke=1,this.Ge=1,this.Je={}}xi(t,i,n){return this.nr(t,i,n).width}Mi(t,i,n){const s=this.nr(t,i,n);return((s.actualBoundingBoxAscent||0)-(s.actualBoundingBoxDescent||0))/2}nr(t,i,n){const s=n||Gt,e=String(i).replace(s,"0");if(this.Qe.has(e))return d(this.Qe.get(e)).sr;if(this.Xe===this.tr){const t=this.Je[this.Ge];delete this.Je[this.Ge],this.Qe.delete(t),this.Ge++,this.Xe--}t.save(),t.textBaseline="middle";const r=t.measureText(e);return t.restore(),0===r.width&&i.length||(this.Qe.set(e,{sr:r,er:this.Ke}),this.Je[this.Ke]=e,this.Xe++,this.Ke++),r}}class Qt{constructor(t){this.rr=null,this.k=null,this.hr="right",this.lr=t}ar(t,i,n){this.rr=t,this.k=i,this.hr=n}X(t){null!==this.k&&null!==this.rr&&this.rr.X(t,this.k,this.lr,this.hr)}}class ti{constructor(t,i,n){this._r=t,this.lr=new Jt(50),this.ur=i,this.F=n,this.j=-1,this.Wt=new Qt(this.lr)}gt(){const t=this.F.cr(this.ur);if(null===t)return null;const i=t.dr(this.ur)?t.vr():this.ur.Dt();if(null===i)return null;const n=t.pr(i);if("overlay"===n)return null;const s=this.F.mr();return s.P!==this.j&&(this.j=s.P,this.lr.ir()),this.Wt.ar(this._r.zi(),s,n),this.Wt}}class ii extends N{constructor(){super(...arguments),this.Et=null}J(t){this.Et=t}br(t,i){var n;if(!(null===(n=this.Et)||void 0===n?void 0:n.yt))return null;const{st:s,et:e,wr:r}=this.Et;return i>=s-e-7&&i<=s+e+7?{gr:this.Et,wr:r}:null}K({context:t,bitmapSize:i,horizontalPixelRatio:n,verticalPixelRatio:s}){if(null===this.Et)return;if(!1===this.Et.yt)return;const e=Math.round(this.Et.st*s);e<0||e>i.height||(t.lineCap="butt",t.strokeStyle=this.Et.O,t.lineWidth=Math.floor(this.Et.et*n),_(t,this.Et.Nt),u(t,e,0,i.width))}}class ni{constructor(t){this.Mr={st:0,O:"rgba(0, 0, 0, 0)",et:1,Nt:0,yt:!1},this.Sr=new ii,this.ft=!0,this.Is=t,this.Ls=t.$t(),this.Sr.J(this.Mr)}bt(){this.ft=!0}gt(){return this.Is.yt()?(this.ft&&(this.kr(),this.ft=!1),this.Sr):null}}class si extends ni{constructor(t){super(t)}kr(){this.Mr.yt=!1;const t=this.Is.Dt(),i=t.yr().yr;if(2!==i&&3!==i)return;const n=this.Is.W();if(!n.baseLineVisible||!this.Is.yt())return;const s=this.Is.Ct();null!==s&&(this.Mr.yt=!0,this.Mr.st=t.Rt(s.Ot,s.Ot),this.Mr.O=n.baseLineColor,this.Mr.et=n.baseLineWidth,this.Mr.Nt=n.baseLineStyle)}}class ei extends N{constructor(){super(...arguments),this.Et=null}J(t){this.Et=t}He(){return this.Et}K({context:t,horizontalPixelRatio:i,verticalPixelRatio:n}){const s=this.Et;if(null===s)return;const e=Math.max(1,Math.floor(i)),r=e%2/2,h=Math.round(s.Ze.x*i)+r,l=s.Ze.y*n;t.fillStyle=s.Cr,t.beginPath();const a=Math.max(2,1.5*s.Tr)*i;t.arc(h,l,a,0,2*Math.PI,!1),t.fill(),t.fillStyle=s.Pr,t.beginPath(),t.arc(h,l,s.ht*i,0,2*Math.PI,!1),t.fill(),t.lineWidth=e,t.strokeStyle=s.Rr,t.beginPath(),t.arc(h,l,s.ht*i+e/2,0,2*Math.PI,!1),t.stroke()}}const ri=[{Dr:0,Or:.25,Vr:4,Br:10,Ar:.25,zr:0,Er:.4,Ir:.8},{Dr:.25,Or:.525,Vr:10,Br:14,Ar:0,zr:0,Er:.8,Ir:0},{Dr:.525,Or:1,Vr:14,Br:14,Ar:0,zr:0,Er:0,Ir:0}];function hi(t,i,n,s){return function(t,i){if("transparent"===t)return t;const n=S(t),s=n[3];return`rgba(${n[0]}, ${n[1]}, ${n[2]}, ${i*s})`}(t,n+(s-n)*i)}function li(t,i){const n=t%2600/2600;let s;for(const t of ri)if(n>=t.Dr&&n<=t.Or){s=t;break}c(void 0!==s,"Last price animation internal logic error");const e=(n-s.Dr)/(s.Or-s.Dr);return{Pr:hi(i,e,s.Ar,s.zr),Rr:hi(i,e,s.Er,s.Ir),ht:(r=e,h=s.Vr,l=s.Br,h+(l-h)*r)};var r,h,l}class ai{constructor(t){this.Wt=new ei,this.ft=!0,this.Lr=!0,this.Nr=performance.now(),this.Fr=this.Nr-1,this.Wr=t}jr(){this.Fr=this.Nr-1,this.bt()}Hr(){if(this.bt(),2===this.Wr.W().lastPriceAnimation){const t=performance.now(),i=this.Fr-t;if(i>0)return void(i<650&&(this.Fr+=2600));this.Nr=t,this.Fr=t+2600}}bt(){this.ft=!0}$r(){this.Lr=!0}yt(){return 0!==this.Wr.W().lastPriceAnimation}Ur(){switch(this.Wr.W().lastPriceAnimation){case 0:return!1;case 1:return!0;case 2:return performance.now()<=this.Fr}}gt(){return this.ft?(this.Mt(),this.ft=!1,this.Lr=!1):this.Lr&&(this.qr(),this.Lr=!1),this.Wt}Mt(){this.Wt.J(null);const t=this.Wr.$t().St(),i=t.Zs(),n=this.Wr.Ct();if(null===i||null===n)return;const s=this.Wr.Yr(!0);if(s.Zr||!i.Xr(s.se))return;const e={x:t.zt(s.se),y:this.Wr.Dt().Rt(s._t,n.Ot)},r=s.O,h=this.Wr.W().lineWidth,l=li(this.Kr(),r);this.Wt.J({Cr:r,Tr:h,Pr:l.Pr,Rr:l.Rr,ht:l.ht,Ze:e})}qr(){const t=this.Wt.He();if(null!==t){const i=li(this.Kr(),t.Cr);t.Pr=i.Pr,t.Rr=i.Rr,t.ht=i.ht}}Kr(){return this.Ur()?performance.now()-this.Nr:2599}}function oi(t,i){return xt(Math.min(Math.max(t,12),30)*i)}function _i(t,i){switch(t){case"arrowDown":case"arrowUp":return oi(i,1);case"circle":return oi(i,.8);case"square":return oi(i,.7)}}function ui(t){return function(t){const i=Math.ceil(t);return i%2!=0?i-1:i}(oi(t,1))}function ci(t){return Math.max(oi(t,.1),3)}function di(t,i,n){return i?t:n?Math.ceil(t/2):0}function fi(t,i,n,s,e){const r=_i("square",n),h=(r-1)/2,l=t-h,a=i-h;return s>=l&&s<=l+r&&e>=a&&e<=a+r}function vi(t,i,n,s){const e=(_i("arrowUp",s)-1)/2*n.Gr,r=(xt(s/2)-1)/2*n.Gr;i.beginPath(),t?(i.moveTo(n.nt-e,n.st),i.lineTo(n.nt,n.st-e),i.lineTo(n.nt+e,n.st),i.lineTo(n.nt+r,n.st),i.lineTo(n.nt+r,n.st+e),i.lineTo(n.nt-r,n.st+e),i.lineTo(n.nt-r,n.st)):(i.moveTo(n.nt-e,n.st),i.lineTo(n.nt,n.st+e),i.lineTo(n.nt+e,n.st),i.lineTo(n.nt+r,n.st),i.lineTo(n.nt+r,n.st-e),i.lineTo(n.nt-r,n.st-e),i.lineTo(n.nt-r,n.st)),i.fill()}function pi(t,i,n,s,e,r){return fi(i,n,s,e,r)}class mi extends N{constructor(){super(...arguments),this.Et=null,this.lr=new Jt,this.j=-1,this.H="",this.Jr=""}J(t){this.Et=t}ar(t,i){this.j===t&&this.H===i||(this.j=t,this.H=i,this.Jr=E(t,i),this.lr.ir())}br(t,i){if(null===this.Et||null===this.Et.tt)return null;for(let n=this.Et.tt.from;n=t&&e<=t+n&&r>=i-h&&r<=i+h}(t.Kt.nt,t.Kt.st,t.Kt.Hi,t.Kt.At,i,n))||function(t,i,n){if(0===t.Xs)return!1;switch(t.ih){case"arrowDown":case"arrowUp":return pi(0,t.nt,t.st,t.Xs,i,n);case"circle":return function(t,i,n,s,e){const r=2+_i("circle",n)/2,h=t-s,l=i-e;return Math.sqrt(h*h+l*l)<=r}(t.nt,t.st,t.Xs,i,n);case"square":return fi(t.nt,t.st,t.Xs,i,n)}}(t,i,n)}function gi(t,i,n,s,e,r,h,l,a){const o=P(n)?n:n.xe,_=P(n)?n:n.ge,u=P(n)?n:n.Me,c=P(i.size)?Math.max(i.size,0):1,d=ui(l.he())*c,f=d/2;switch(t.Xs=d,i.position){case"inBar":return t.st=h.Rt(o,a),void(void 0!==t.Kt&&(t.Kt.st=t.st+f+r+.6*e));case"aboveBar":return t.st=h.Rt(_,a)-f-s.nh,void 0!==t.Kt&&(t.Kt.st=t.st-f-.6*e,s.nh+=1.2*e),void(s.nh+=d+r);case"belowBar":return t.st=h.Rt(u,a)+f+s.sh,void 0!==t.Kt&&(t.Kt.st=t.st+f+r+.6*e,s.sh+=1.2*e),void(s.sh+=d+r)}i.position}class Mi{constructor(t,i){this.ft=!0,this.eh=!0,this.rh=!0,this.hh=null,this.ah=null,this.Wt=new mi,this.Wr=t,this.$i=i,this.Et={it:[],tt:null}}bt(t){this.ft=!0,this.rh=!0,"data"===t&&(this.eh=!0,this.ah=null)}gt(t){if(!this.Wr.yt())return null;this.ft&&this.oh();const i=this.$i.W().layout;return this.Wt.ar(i.fontSize,i.fontFamily),this.Wt.J(this.Et),this.Wt}_h(){if(this.rh){if(this.Wr.uh().length>0){const t=this.$i.St().he(),i=ci(t),n=1.5*ui(t)+2*i,s=this.dh();this.hh={above:di(n,s.aboveBar,s.inBar),below:di(n,s.belowBar,s.inBar)}}else this.hh=null;this.rh=!1}return this.hh}dh(){return null===this.ah&&(this.ah=this.Wr.uh().reduce(((t,i)=>(t[i.position]||(t[i.position]=!0),t)),{inBar:!1,aboveBar:!1,belowBar:!1})),this.ah}oh(){const t=this.Wr.Dt(),i=this.$i.St(),n=this.Wr.uh();this.eh&&(this.Et.it=n.map((t=>({ot:t.time,nt:0,st:0,Xs:0,ih:t.shape,O:t.color,Qr:t.Qr,wr:t.id,Kt:void 0}))),this.eh=!1);const s=this.$i.W().layout;this.Et.tt=null;const e=i.Zs();if(null===e)return;const r=this.Wr.Ct();if(null===r)return;if(0===this.Et.it.length)return;let h=NaN;const l=ci(i.he()),a={nh:l,sh:l};this.Et.tt=Bt(this.Et.it,e,!0);for(let e=this.Et.tt.from;e0&&(_.Kt={th:o.text,nt:0,st:0,Hi:0,At:0});const u=this.Wr.fh(o.time);null!==u&&gi(_,o,u,a,s.fontSize,l,t,i,r.Ot)}this.ft=!1}}class xi extends ni{constructor(t){super(t)}kr(){const t=this.Mr;t.yt=!1;const i=this.Is.W();if(!i.priceLineVisible||!this.Is.yt())return;const n=this.Is.Yr(0===i.priceLineSource);n.Zr||(t.yt=!0,t.st=n.ki,t.O=this.Is.ph(n.O),t.et=i.priceLineWidth,t.Nt=i.priceLineStyle)}}class Si extends J{constructor(t){super(),this.jt=t}Ei(t,i,n){t.yt=!1,i.yt=!1;const s=this.jt;if(!s.yt())return;const e=s.W(),r=e.lastValueVisible,h=""!==s.mh(),l=0===e.seriesLastValueMode,a=s.Yr(!1);if(a.Zr)return;r&&(t.Kt=this.bh(a,r,l),t.yt=0!==t.Kt.length),(h||l)&&(i.Kt=this.wh(a,r,h,l),i.yt=i.Kt.length>0);const o=s.ph(a.O),_=y(o);n.t=_.t,n.ki=a.ki,i.Vt=s.$t().Bt(a.ki/s.Dt().At()),t.Vt=o,t.O=_.i,i.O=_.i}wh(t,i,n,s){let e="";const r=this.jt.mh();return n&&0!==r.length&&(e+=`${r} `),i&&s&&(e+=this.jt.Dt().gh()?t.Mh:t.xh),e.trim()}bh(t,i,n){return i?n?this.jt.Dt().gh()?t.xh:t.Mh:t.Kt:""}}function ki(t,i,n,s){const e=Number.isFinite(i),r=Number.isFinite(n);return e&&r?t(i,n):e||r?e?i:n:s}class yi{constructor(t,i){this.Sh=t,this.kh=i}yh(t){return null!==t&&(this.Sh===t.Sh&&this.kh===t.kh)}Ch(){return new yi(this.Sh,this.kh)}Th(){return this.Sh}Ph(){return this.kh}Rh(){return this.kh-this.Sh}Ni(){return this.kh===this.Sh||Number.isNaN(this.kh)||Number.isNaN(this.Sh)}ts(t){return null===t?this:new yi(ki(Math.min,this.Th(),t.Th(),-1/0),ki(Math.max,this.Ph(),t.Ph(),1/0))}Dh(t){if(!P(t))return;if(0===this.kh-this.Sh)return;const i=.5*(this.kh+this.Sh);let n=this.kh-i,s=this.Sh-i;n*=t,s*=t,this.kh=i+n,this.Sh=i+s}Oh(t){P(t)&&(this.kh+=t,this.Sh+=t)}Vh(){return{minValue:this.Sh,maxValue:this.kh}}static Bh(t){return null===t?null:new yi(t.minValue,t.maxValue)}}class Ci{constructor(t,i){this.Ah=t,this.zh=i||null}Eh(){return this.Ah}Ih(){return this.zh}Vh(){return null===this.Ah?null:{priceRange:this.Ah.Vh(),margins:this.zh||void 0}}static Bh(t){return null===t?null:new Ci(yi.Bh(t.priceRange),t.margins)}}class Ti extends ni{constructor(t,i){super(t),this.Lh=i}kr(){const t=this.Mr;t.yt=!1;const i=this.Lh.W();if(!this.Is.yt()||!i.lineVisible)return;const n=this.Lh.Nh();null!==n&&(t.yt=!0,t.st=n,t.O=i.color,t.et=i.lineWidth,t.Nt=i.lineStyle,t.wr=this.Lh.W().id)}}class Pi extends J{constructor(t,i){super(),this.Wr=t,this.Lh=i}Ei(t,i,n){t.yt=!1,i.yt=!1;const s=this.Lh.W(),e=s.axisLabelVisible,r=""!==s.title,h=this.Wr;if(!e||!h.yt())return;const l=this.Lh.Nh();if(null===l)return;r&&(i.Kt=s.title,i.yt=!0),i.Vt=h.$t().Bt(l/h.Dt().At()),t.Kt=this.Fh(s.price),t.yt=!0;const a=y(s.axisLabelColor||s.color);n.t=a.t;const o=s.axisLabelTextColor||a.i;t.O=o,i.O=o,n.ki=l}Fh(t){const i=this.Wr.Ct();return null===i?"":this.Wr.Dt().Fi(t,i.Ot)}}class Ri{constructor(t,i){this.Wr=t,this.cn=i,this.Wh=new Ti(t,this),this._r=new Pi(t,this),this.jh=new ti(this._r,t,t.$t())}Hh(t){T(this.cn,t),this.bt(),this.Wr.$t().$h()}W(){return this.cn}Uh(){return this.Wh}qh(){return this.jh}Yh(){return this._r}bt(){this.Wh.bt(),this._r.bt()}Nh(){const t=this.Wr,i=t.Dt();if(t.$t().St().Ni()||i.Ni())return null;const n=t.Ct();return null===n?null:i.Rt(this.cn.price,n.Ot)}}class Di extends st{constructor(t){super(),this.$i=t}$t(){return this.$i}}const Oi={Bar:(t,i,n,s)=>{var e;const r=i.upColor,h=i.downColor,l=f(t(n,s)),a=v(l.Ot[0])<=v(l.Ot[3]);return{ue:null!==(e=l.O)&&void 0!==e?e:a?r:h}},Candlestick:(t,i,n,s)=>{var e,r,h;const l=i.upColor,a=i.downColor,o=i.borderUpColor,_=i.borderDownColor,u=i.wickUpColor,c=i.wickDownColor,d=f(t(n,s)),p=v(d.Ot[0])<=v(d.Ot[3]);return{ue:null!==(e=d.O)&&void 0!==e?e:p?l:a,Le:null!==(r=d.Vt)&&void 0!==r?r:p?o:_,Ie:null!==(h=d.Zh)&&void 0!==h?h:p?u:c}},Custom:(t,i,n,s)=>{var e;return{ue:null!==(e=f(t(n,s)).O)&&void 0!==e?e:i.color}},Area:(t,i,n,s)=>{var e,r,h,l;const a=f(t(n,s));return{ue:null!==(e=a.lt)&&void 0!==e?e:i.lineColor,lt:null!==(r=a.lt)&&void 0!==r?r:i.lineColor,Ts:null!==(h=a.Ts)&&void 0!==h?h:i.topColor,Ps:null!==(l=a.Ps)&&void 0!==l?l:i.bottomColor}},Baseline:(t,i,n,s)=>{var e,r,h,l,a,o;const _=f(t(n,s));return{ue:_.Ot[3]>=i.baseValue.price?i.topLineColor:i.bottomLineColor,Pe:null!==(e=_.Pe)&&void 0!==e?e:i.topLineColor,Re:null!==(r=_.Re)&&void 0!==r?r:i.bottomLineColor,Se:null!==(h=_.Se)&&void 0!==h?h:i.topFillColor1,ke:null!==(l=_.ke)&&void 0!==l?l:i.topFillColor2,ye:null!==(a=_.ye)&&void 0!==a?a:i.bottomFillColor1,Ce:null!==(o=_.Ce)&&void 0!==o?o:i.bottomFillColor2}},Line:(t,i,n,s)=>{var e,r;const h=f(t(n,s));return{ue:null!==(e=h.O)&&void 0!==e?e:i.color,lt:null!==(r=h.O)&&void 0!==r?r:i.color}},Histogram:(t,i,n,s)=>{var e;return{ue:null!==(e=f(t(n,s)).O)&&void 0!==e?e:i.color}}};class Vi{constructor(t){this.Xh=(t,i)=>void 0!==i?i.Ot:this.Wr.zn().Kh(t),this.Wr=t,this.Gh=Oi[t.Jh()]}Hs(t,i){return this.Gh(this.Xh,this.Wr.W(),t,i)}}var Bi;!function(t){t[t.NearestLeft=-1]="NearestLeft",t[t.None=0]="None",t[t.NearestRight=1]="NearestRight"}(Bi||(Bi={}));const Ai=30;class zi{constructor(){this.Qh=[],this.tl=new Map,this.il=new Map}nl(){return this.Xs()>0?this.Qh[this.Qh.length-1]:null}sl(){return this.Xs()>0?this.el(0):null}An(){return this.Xs()>0?this.el(this.Qh.length-1):null}Xs(){return this.Qh.length}Ni(){return 0===this.Xs()}Xr(t){return null!==this.rl(t,0)}Kh(t){return this.hl(t)}hl(t,i=0){const n=this.rl(t,i);return null===n?null:Object.assign(Object.assign({},this.ll(n)),{se:this.el(n)})}ie(){return this.Qh}al(t,i,n){if(this.Ni())return null;let s=null;for(const e of n){s=Ei(s,this.ol(t,i,e))}return s}J(t){this.il.clear(),this.tl.clear(),this.Qh=t}el(t){return this.Qh[t].se}ll(t){return this.Qh[t]}rl(t,i){const n=this._l(t);if(null===n&&0!==i)switch(i){case-1:return this.ul(t);case 1:return this.cl(t);default:throw new TypeError("Unknown search mode")}return n}ul(t){let i=this.dl(t);return i>0&&(i-=1),i!==this.Qh.length&&this.el(i)t.set.se>i))}vl(t,i,n){let s=null;for(let e=t;es.ml&&(s.ml=t)))}return s}ol(t,i,n){if(this.Ni())return null;let s=null;const e=f(this.sl()),r=f(this.An()),h=Math.max(t,e),l=Math.min(i,r),a=Math.ceil(h/Ai)*Ai,o=Math.max(a,Math.floor(l/Ai)*Ai);{const t=this.dl(h),e=this.fl(Math.min(l,a,i));s=Ei(s,this.vl(t,e,n))}let _=this.tl.get(n);void 0===_&&(_=new Map,this.tl.set(n,_));for(let t=Math.max(a+1,h);tnew Li(t)));return this.yl={gl:e,Ml:r},r}Qi(){var t,i,n,s;const e=null!==(n=null===(i=(t=this.Dl).timeAxisViews)||void 0===i?void 0:i.call(t))&&void 0!==n?n:[];if((null===(s=this.Cl)||void 0===s?void 0:s.gl)===e)return this.Cl.Ml;const r=this.Wr.$t().St(),h=e.map((t=>new Fi(t,r)));return this.Cl={gl:e,Ml:h},h}Rn(){var t,i,n,s;const e=null!==(n=null===(i=(t=this.Dl).priceAxisViews)||void 0===i?void 0:i.call(t))&&void 0!==n?n:[];if((null===(s=this.Tl)||void 0===s?void 0:s.gl)===e)return this.Tl.Ml;const r=this.Wr.Dt(),h=e.map((t=>new Wi(t,r)));return this.Tl={gl:e,Ml:h},h}Vl(){var t,i,n,s;const e=null!==(n=null===(i=(t=this.Dl).priceAxisPaneViews)||void 0===i?void 0:i.call(t))&&void 0!==n?n:[];if((null===(s=this.Pl)||void 0===s?void 0:s.gl)===e)return this.Pl.Ml;const r=e.map((t=>new Li(t)));return this.Pl={gl:e,Ml:r},r}Bl(){var t,i,n,s;const e=null!==(n=null===(i=(t=this.Dl).timeAxisPaneViews)||void 0===i?void 0:i.call(t))&&void 0!==n?n:[];if((null===(s=this.Rl)||void 0===s?void 0:s.gl)===e)return this.Rl.Ml;const r=e.map((t=>new Li(t)));return this.Rl={gl:e,Ml:r},r}Al(t,i){var n,s,e;return null!==(e=null===(s=(n=this.Dl).autoscaleInfo)||void 0===s?void 0:s.call(n,t,i))&&void 0!==e?e:null}br(t,i){var n,s,e;return null!==(e=null===(s=(n=this.Dl).hitTest)||void 0===s?void 0:s.call(n,t,i))&&void 0!==e?e:null}}function Hi(t,i,n,s){t.forEach((t=>{i(t).forEach((t=>{t.xl()===n&&s.push(t)}))}))}function $i(t){return t.Pn()}function Ui(t){return t.Vl()}function qi(t){return t.Bl()}class Yi extends Di{constructor(t,i,n,s,e){super(t),this.Et=new zi,this.Wh=new xi(this),this.zl=[],this.El=new si(this),this.Il=null,this.Ll=null,this.Nl=[],this.Fl=[],this.Wl=null,this.jl=[],this.cn=i,this.Hl=n;const r=new Si(this);this.rn=[r],this.jh=new ti(r,this,t),"Area"!==n&&"Line"!==n&&"Baseline"!==n||(this.Il=new ai(this)),this.$l(),this.Ul(e)}S(){null!==this.Wl&&clearTimeout(this.Wl)}ph(t){return this.cn.priceLineColor||t}Yr(t){const i={Zr:!0},n=this.Dt();if(this.$t().St().Ni()||n.Ni()||this.Et.Ni())return i;const s=this.$t().St().Zs(),e=this.Ct();if(null===s||null===e)return i;let r,h;if(t){const t=this.Et.nl();if(null===t)return i;r=t,h=t.se}else{const t=this.Et.hl(s.ui(),-1);if(null===t)return i;if(r=this.Et.Kh(t.se),null===r)return i;h=t.se}const l=r.Ot[3],a=this.$s().Hs(h,{Ot:r}),o=n.Rt(l,e.Ot);return{Zr:!1,_t:l,Kt:n.Fi(l,e.Ot),Mh:n.ql(l),xh:n.Yl(l,e.Ot),O:a.ue,ki:o,se:h}}$s(){return null!==this.Ll||(this.Ll=new Vi(this)),this.Ll}W(){return this.cn}Hh(t){const i=t.priceScaleId;void 0!==i&&i!==this.cn.priceScaleId&&this.$t().Zl(this,i),T(this.cn,t),void 0!==t.priceFormat&&(this.$l(),this.$t().Xl()),this.$t().Kl(this),this.$t().Gl(),this.wn.bt("options")}J(t,i){this.Et.J(t),this.Jl(),this.wn.bt("data"),this.dn.bt("data"),null!==this.Il&&(i&&i.Ql?this.Il.Hr():0===t.length&&this.Il.jr());const n=this.$t().cr(this);this.$t().ta(n),this.$t().Kl(this),this.$t().Gl(),this.$t().$h()}ia(t){this.Nl=t,this.Jl();const i=this.$t().cr(this);this.dn.bt("data"),this.$t().ta(i),this.$t().Kl(this),this.$t().Gl(),this.$t().$h()}na(){return this.Nl}uh(){return this.Fl}sa(t){const i=new Ri(this,t);return this.zl.push(i),this.$t().Kl(this),i}ea(t){const i=this.zl.indexOf(t);-1!==i&&this.zl.splice(i,1),this.$t().Kl(this)}Jh(){return this.Hl}Ct(){const t=this.ra();return null===t?null:{Ot:t.Ot[3],ha:t.ot}}ra(){const t=this.$t().St().Zs();if(null===t)return null;const i=t.Os();return this.Et.hl(i,1)}zn(){return this.Et}fh(t){const i=this.Et.Kh(t);return null===i?null:"Bar"===this.Hl||"Candlestick"===this.Hl||"Custom"===this.Hl?{we:i.Ot[0],ge:i.Ot[1],Me:i.Ot[2],xe:i.Ot[3]}:i.Ot[3]}la(t){const i=[];Hi(this.jl,$i,"top",i);const n=this.Il;return null!==n&&n.yt()?(null===this.Wl&&n.Ur()&&(this.Wl=setTimeout((()=>{this.Wl=null,this.$t().aa()}),0)),n.$r(),i.unshift(n),i):i}Pn(){const t=[];this.oa()||t.push(this.El),t.push(this.wn,this.Wh,this.dn);const i=this.zl.map((t=>t.Uh()));return t.push(...i),Hi(this.jl,$i,"normal",t),t}_a(){return this.ua($i,"bottom")}ca(t){return this.ua(Ui,t)}da(t){return this.ua(qi,t)}fa(t,i){return this.jl.map((n=>n.br(t,i))).filter((t=>null!==t))}Ji(t){return[this.jh,...this.zl.map((t=>t.qh()))]}Rn(t,i){if(i!==this.Yi&&!this.oa())return[];const n=[...this.rn];for(const t of this.zl)n.push(t.Yh());return this.jl.forEach((t=>{n.push(...t.Rn())})),n}Qi(){const t=[];return this.jl.forEach((i=>{t.push(...i.Qi())})),t}Al(t,i){if(void 0!==this.cn.autoscaleInfoProvider){const n=this.cn.autoscaleInfoProvider((()=>{const n=this.va(t,i);return null===n?null:n.Vh()}));return Ci.Bh(n)}return this.va(t,i)}pa(){return this.cn.priceFormat.minMove}ma(){return this.ba}On(){var t;this.wn.bt(),this.dn.bt();for(const t of this.rn)t.bt();for(const t of this.zl)t.bt();this.Wh.bt(),this.El.bt(),null===(t=this.Il)||void 0===t||t.bt(),this.jl.forEach((t=>t.On()))}Dt(){return f(super.Dt())}kt(t){if(!(("Line"===this.Hl||"Area"===this.Hl||"Baseline"===this.Hl)&&this.cn.crosshairMarkerVisible))return null;const i=this.Et.Kh(t);if(null===i)return null;return{_t:i.Ot[3],ht:this.wa(),Vt:this.ga(),Pt:this.Ma(),Tt:this.xa(t)}}mh(){return this.cn.title}yt(){return this.cn.visible}Sa(t){this.jl.push(new ji(t,this))}ka(t){this.jl=this.jl.filter((i=>i.Ol()!==t))}ya(){if(this.wn instanceof qt!=!1)return t=>this.wn.Fe(t)}Ca(){if(this.wn instanceof qt!=!1)return t=>this.wn.We(t)}oa(){return!ht(this.Dt().Ta())}va(t,i){if(!R(t)||!R(i)||this.Et.Ni())return null;const n="Line"===this.Hl||"Area"===this.Hl||"Baseline"===this.Hl||"Histogram"===this.Hl?[3]:[2,1],s=this.Et.al(t,i,n);let e=null!==s?new yi(s.pl,s.ml):null;if("Histogram"===this.Jh()){const t=this.cn.base,i=new yi(t,t);e=null!==e?e.ts(i):i}let r=this.dn._h();return this.jl.forEach((n=>{const s=n.Al(t,i);if(null==s?void 0:s.priceRange){const t=new yi(s.priceRange.minValue,s.priceRange.maxValue);e=null!==e?e.ts(t):t}var h,l,a,o;(null==s?void 0:s.margins)&&(h=r,l=s.margins,r={above:Math.max(null!==(a=null==h?void 0:h.above)&&void 0!==a?a:0,l.above),below:Math.max(null!==(o=null==h?void 0:h.below)&&void 0!==o?o:0,l.below)})})),new Ci(e,r)}wa(){switch(this.Hl){case"Line":case"Area":case"Baseline":return this.cn.crosshairMarkerRadius}return 0}ga(){switch(this.Hl){case"Line":case"Area":case"Baseline":{const t=this.cn.crosshairMarkerBorderColor;if(0!==t.length)return t}}return null}Ma(){switch(this.Hl){case"Line":case"Area":case"Baseline":return this.cn.crosshairMarkerBorderWidth}return 0}xa(t){switch(this.Hl){case"Line":case"Area":case"Baseline":{const t=this.cn.crosshairMarkerBackgroundColor;if(0!==t.length)return t}}return this.$s().Hs(t).ue}$l(){switch(this.cn.priceFormat.type){case"custom":this.ba={format:this.cn.priceFormat.formatter};break;case"volume":this.ba=new ct(this.cn.priceFormat.precision);break;case"percent":this.ba=new ut(this.cn.priceFormat.precision);break;default:{const t=Math.pow(10,this.cn.priceFormat.precision);this.ba=new _t(t,this.cn.priceFormat.minMove*t)}}null!==this.Yi&&this.Yi.Pa()}Jl(){const t=this.$t().St();if(!t.Ra()||this.Et.Ni())return void(this.Fl=[]);const i=f(this.Et.sl());this.Fl=this.Nl.map(((n,s)=>{const e=f(t.Da(n.time,!0)),r=et instanceof Yi)).reduce(((t,s)=>{if(n.dr(s)||!s.yt())return t;const e=s.Dt(),r=s.zn();if(e.Ni()||!r.Xr(i))return t;const h=r.Kh(i);if(null===h)return t;const l=v(s.Ct());return t.concat([e.Rt(h.Ot[3],l.Ot)])}),[]);if(0===l.length)return s;l.sort(((t,i)=>Math.abs(t-h)-Math.abs(i-h)));const a=l[0];return s=e.pn(a,r),s}}class Xi extends N{constructor(){super(...arguments),this.Et=null}J(t){this.Et=t}K({context:t,bitmapSize:i,horizontalPixelRatio:n,verticalPixelRatio:s}){if(null===this.Et)return;const e=Math.max(1,Math.floor(n));t.lineWidth=e,function(t,i){t.save(),t.lineWidth%2&&t.translate(.5,.5),i(),t.restore()}(t,(()=>{const r=f(this.Et);if(r.Ba){t.strokeStyle=r.Aa,_(t,r.za),t.beginPath();for(const s of r.Ea){const r=Math.round(s.Ia*n);t.moveTo(r,-e),t.lineTo(r,i.height+e)}t.stroke()}if(r.La){t.strokeStyle=r.Na,_(t,r.Fa),t.beginPath();for(const n of r.Wa){const r=Math.round(n.Ia*s);t.moveTo(-e,r),t.lineTo(i.width+e,r)}t.stroke()}}))}}class Ki{constructor(t){this.Wt=new Xi,this.ft=!0,this.tn=t}bt(){this.ft=!0}gt(){if(this.ft){const t=this.tn.$t().W().grid,i={La:t.horzLines.visible,Ba:t.vertLines.visible,Na:t.horzLines.color,Aa:t.vertLines.color,Fa:t.horzLines.style,za:t.vertLines.style,Wa:this.tn.vn().ja(),Ea:(this.tn.$t().St().ja()||[]).map((t=>({Ia:t.coord})))};this.Wt.J(i),this.ft=!1}return this.Wt}}class Gi{constructor(t){this.wn=new Ki(t)}Uh(){return this.wn}}const Ji={Ha:4,$a:1e-4};function Qi(t,i){const n=100*(t-i)/i;return i<0?-n:n}function tn(t,i){const n=Qi(t.Th(),i),s=Qi(t.Ph(),i);return new yi(n,s)}function nn(t,i){const n=100*(t-i)/i+100;return i<0?-n:n}function sn(t,i){const n=nn(t.Th(),i),s=nn(t.Ph(),i);return new yi(n,s)}function en(t,i){const n=Math.abs(t);if(n<1e-15)return 0;const s=Math.log10(n+i.$a)+i.Ha;return t<0?-s:s}function rn(t,i){const n=Math.abs(t);if(n<1e-15)return 0;const s=Math.pow(10,n-i.Ha)-i.$a;return t<0?-s:s}function hn(t,i){if(null===t)return null;const n=en(t.Th(),i),s=en(t.Ph(),i);return new yi(n,s)}function ln(t,i){if(null===t)return null;const n=rn(t.Th(),i),s=rn(t.Ph(),i);return new yi(n,s)}function an(t){if(null===t)return Ji;const i=Math.abs(t.Ph()-t.Th());if(i>=1||i<1e-15)return Ji;const n=Math.ceil(Math.abs(Math.log10(i))),s=Ji.Ha+n;return{Ha:s,$a:1/Math.pow(10,s)}}class on{constructor(t,i){if(this.Ua=t,this.qa=i,function(t){if(t<0)return!1;for(let i=t;i>1;i/=10)if(i%10!=0)return!1;return!0}(this.Ua))this.Ya=[2,2.5,2];else{this.Ya=[];for(let t=this.Ua;1!==t;){if(t%2==0)this.Ya.push(2),t/=2;else{if(t%5!=0)throw new Error("unexpected base");this.Ya.push(2,2.5),t/=5}if(this.Ya.length>100)throw new Error("something wrong with base")}}}Za(t,i,n){const s=0===this.Ua?0:1/this.Ua;let e=Math.pow(10,Math.max(0,Math.ceil(Math.log10(t-i)))),r=0,h=this.qa[0];for(;;){const t=Mt(e,s,1e-14)&&e>s+1e-14,i=Mt(e,n*h,1e-14),l=Mt(e,1,1e-14);if(!(t&&i&&l))break;e/=h,h=this.qa[++r%this.qa.length]}if(e<=s+1e-14&&(e=s),e=Math.max(1,e),this.Ya.length>0&&(l=e,a=1,o=1e-14,Math.abs(l-a)s+1e-14;)e/=h,h=this.Ya[++r%this.Ya.length];var l,a,o;return e}}class _n{constructor(t,i,n,s){this.Xa=[],this.Ii=t,this.Ua=i,this.Ka=n,this.Ga=s}Za(t,i){if(t=o?1:-1;let d=null,f=0;for(let n=a-u;n>o;n-=_){const s=this.Ga(n,i,!0);null!==d&&Math.abs(s-d)l||(ff(t.Xi())-f(i.Xi())))}var cn;!function(t){t[t.Normal=0]="Normal",t[t.Logarithmic=1]="Logarithmic",t[t.Percentage=2]="Percentage",t[t.IndexedTo100=3]="IndexedTo100"}(cn||(cn={}));const dn=new ut,fn=new _t(100,1);class vn{constructor(t,i,n,s){this.ro=0,this.ho=null,this.Ah=null,this.lo=null,this.ao={oo:!1,_o:null},this.uo=0,this.co=0,this.do=new C,this.fo=new C,this.vo=[],this.po=null,this.mo=null,this.bo=null,this.wo=null,this.ba=fn,this.Mo=an(null),this.xo=t,this.cn=i,this.So=n,this.ko=s,this.yo=new _n(this,100,this.Co.bind(this),this.To.bind(this))}Ta(){return this.xo}W(){return this.cn}Hh(t){if(T(this.cn,t),this.Pa(),void 0!==t.mode&&this.Po({yr:t.mode}),void 0!==t.scaleMargins){const i=d(t.scaleMargins.top),n=d(t.scaleMargins.bottom);if(i<0||i>1)throw new Error(`Invalid top margin - expect value between 0 and 1, given=${i}`);if(n<0||n>1)throw new Error(`Invalid bottom margin - expect value between 0 and 1, given=${n}`);if(i+n>1)throw new Error(`Invalid margins - sum of margins must be less than 1, given=${i+n}`);this.Ro(),this.mo=null}}Do(){return this.cn.autoScale}eo(){return 1===this.cn.mode}gh(){return 2===this.cn.mode}Oo(){return 3===this.cn.mode}yr(){return{Wn:this.cn.autoScale,Vo:this.cn.invertScale,yr:this.cn.mode}}Po(t){const i=this.yr();let n=null;void 0!==t.Wn&&(this.cn.autoScale=t.Wn),void 0!==t.yr&&(this.cn.mode=t.yr,2!==t.yr&&3!==t.yr||(this.cn.autoScale=!0),this.ao.oo=!1),1===i.yr&&t.yr!==i.yr&&(!function(t,i){if(null===t)return!1;const n=rn(t.Th(),i),s=rn(t.Ph(),i);return isFinite(n)&&isFinite(s)}(this.Ah,this.Mo)?this.cn.autoScale=!0:(n=ln(this.Ah,this.Mo),null!==n&&this.Bo(n))),1===t.yr&&t.yr!==i.yr&&(n=hn(this.Ah,this.Mo),null!==n&&this.Bo(n));const s=i.yr!==this.cn.mode;s&&(2===i.yr||this.gh())&&this.Pa(),s&&(3===i.yr||this.Oo())&&this.Pa(),void 0!==t.Vo&&i.Vo!==t.Vo&&(this.cn.invertScale=t.Vo,this.Ao()),this.fo.m(i,this.yr())}zo(){return this.fo}P(){return this.So.fontSize}At(){return this.ro}Eo(t){this.ro!==t&&(this.ro=t,this.Ro(),this.mo=null)}Io(){if(this.ho)return this.ho;const t=this.At()-this.Lo()-this.No();return this.ho=t,t}Eh(){return this.Fo(),this.Ah}Bo(t,i){const n=this.Ah;(i||null===n&&null!==t||null!==n&&!n.yh(t))&&(this.mo=null,this.Ah=t)}Ni(){return this.Fo(),0===this.ro||!this.Ah||this.Ah.Ni()}Wo(t){return this.Vo()?t:this.At()-1-t}Rt(t,i){return this.gh()?t=Qi(t,i):this.Oo()&&(t=nn(t,i)),this.To(t,i)}Qs(t,i,n){this.Fo();const s=this.No(),e=f(this.Eh()),r=e.Th(),h=e.Ph(),l=this.Io()-1,a=this.Vo(),o=l/(h-r),_=void 0===n?0:n.from,u=void 0===n?t.length:n.to,c=this.jo();for(let n=_;nt.On()))}Pa(){this.mo=null;const t=this.s_();let i=100;null!==t&&(i=Math.round(1/t.pa())),this.ba=fn,this.gh()?(this.ba=dn,i=100):this.Oo()?(this.ba=new _t(100,1),i=100):null!==t&&(this.ba=t.ma()),this.yo=new _n(this,i,this.Co.bind(this),this.To.bind(this)),this.yo.Qa()}qo(){this.po=null}s_(){return this.vo[0]||null}Lo(){return this.Vo()?this.cn.scaleMargins.bottom*this.At()+this.co:this.cn.scaleMargins.top*this.At()+this.uo}No(){return this.Vo()?this.cn.scaleMargins.top*this.At()+this.uo:this.cn.scaleMargins.bottom*this.At()+this.co}Fo(){this.ao.oo||(this.ao.oo=!0,this.h_())}Ro(){this.ho=null}To(t,i){if(this.Fo(),this.Ni())return 0;t=this.eo()&&t?en(t,this.Mo):t;const n=f(this.Eh()),s=this.No()+(this.Io()-1)*(t-n.Th())/n.Rh();return this.Wo(s)}Co(t,i){if(this.Fo(),this.Ni())return 0;const n=this.Wo(t),s=f(this.Eh()),e=s.Th()+s.Rh()*((n-this.No())/(this.Io()-1));return this.eo()?rn(e,this.Mo):e}Ao(){this.mo=null,this.yo.Qa()}h_(){const t=this.ao._o;if(null===t)return;let i=null;const n=this.e_();let s=0,e=0;for(const r of n){if(!r.yt())continue;const n=r.Ct();if(null===n)continue;const h=r.Al(t.Os(),t.ui());let l=h&&h.Eh();if(null!==l){switch(this.cn.mode){case 1:l=hn(l,this.Mo);break;case 2:l=tn(l,n.Ot);break;case 3:l=sn(l,n.Ot)}if(i=null===i?l:i.ts(f(l)),null!==h){const t=h.Ih();null!==t&&(s=Math.max(s,t.above),e=Math.max(e,t.below))}}}if(s===this.uo&&e===this.co||(this.uo=s,this.co=e,this.mo=null,this.Ro()),null!==i){if(i.Th()===i.Ph()){const t=this.s_(),n=5*(null===t||this.gh()||this.Oo()?1:t.pa());this.eo()&&(i=ln(i,this.Mo)),i=new yi(i.Th()-n,i.Ph()+n),this.eo()&&(i=hn(i,this.Mo))}if(this.eo()){const t=ln(i,this.Mo),n=an(t);if(r=n,h=this.Mo,r.Ha!==h.Ha||r.$a!==h.$a){const s=null!==this.lo?ln(this.lo,this.Mo):null;this.Mo=n,i=hn(t,n),null!==s&&(this.lo=hn(s,n))}}this.Bo(i)}else null===this.Ah&&(this.Bo(new yi(-.5,.5)),this.Mo=an(null));var r,h;this.ao.oo=!0}jo(){return this.gh()?Qi:this.Oo()?nn:this.eo()?t=>en(t,this.Mo):null}l_(t,i,n){return void 0===i?(void 0===n&&(n=this.ma()),n.format(t)):i(t)}Fh(t,i){return this.l_(t,this.ko.priceFormatter,i)}n_(t,i){return this.l_(t,this.ko.percentageFormatter,i)}}class pn{constructor(t,i){this.vo=[],this.a_=new Map,this.ro=0,this.o_=0,this.__=1e3,this.po=null,this.u_=new C,this.kl=t,this.$i=i,this.c_=new Gi(this);const n=i.W();this.d_=this.f_("left",n.leftPriceScale),this.v_=this.f_("right",n.rightPriceScale),this.d_.zo().l(this.p_.bind(this,this.d_),this),this.v_.zo().l(this.p_.bind(this,this.v_),this),this.m_(n)}m_(t){if(t.leftPriceScale&&this.d_.Hh(t.leftPriceScale),t.rightPriceScale&&this.v_.Hh(t.rightPriceScale),t.localization&&(this.d_.Pa(),this.v_.Pa()),t.overlayPriceScales){const i=Array.from(this.a_.values());for(const n of i){const i=f(n[0].Dt());i.Hh(t.overlayPriceScales),t.localization&&i.Pa()}}}b_(t){switch(t){case"left":return this.d_;case"right":return this.v_}return this.a_.has(t)?d(this.a_.get(t))[0].Dt():null}S(){this.$t().w_().p(this),this.d_.zo().p(this),this.v_.zo().p(this),this.vo.forEach((t=>{t.S&&t.S()})),this.u_.m()}g_(){return this.__}M_(t){this.__=t}$t(){return this.$i}Hi(){return this.o_}At(){return this.ro}x_(t){this.o_=t,this.S_()}Eo(t){this.ro=t,this.d_.Eo(t),this.v_.Eo(t),this.vo.forEach((i=>{if(this.dr(i)){const n=i.Dt();null!==n&&n.Eo(t)}})),this.S_()}Va(){return this.vo}dr(t){const i=t.Dt();return null===i||this.d_!==i&&this.v_!==i}Uo(t,i,n){const s=void 0!==n?n:this.y_().k_+1;this.C_(t,i,s)}Yo(t){const i=this.vo.indexOf(t);c(-1!==i,"removeDataSource: invalid data source"),this.vo.splice(i,1);const n=f(t.Dt()).Ta();if(this.a_.has(n)){const i=d(this.a_.get(n)),s=i.indexOf(t);-1!==s&&(i.splice(s,1),0===i.length&&this.a_.delete(n))}const s=t.Dt();s&&s.Va().indexOf(t)>=0&&s.Yo(t),null!==s&&(s.qo(),this.T_(s)),this.po=null}pr(t){return t===this.d_?"left":t===this.v_?"right":"overlay"}P_(){return this.d_}R_(){return this.v_}D_(t,i){t.Ko(i)}O_(t,i){t.Go(i),this.S_()}V_(t){t.Jo()}B_(t,i){t.Qo(i)}A_(t,i){t.t_(i),this.S_()}z_(t){t.i_()}S_(){this.vo.forEach((t=>{t.On()}))}vn(){let t=null;return this.$i.W().rightPriceScale.visible&&0!==this.v_.Va().length?t=this.v_:this.$i.W().leftPriceScale.visible&&0!==this.d_.Va().length?t=this.d_:0!==this.vo.length&&(t=this.vo[0].Dt()),null===t&&(t=this.v_),t}vr(){let t=null;return this.$i.W().rightPriceScale.visible?t=this.v_:this.$i.W().leftPriceScale.visible&&(t=this.d_),t}T_(t){null!==t&&t.Do()&&this.E_(t)}I_(t){const i=this.kl.Zs();t.Po({Wn:!0}),null!==i&&t.r_(i),this.S_()}L_(){this.E_(this.d_),this.E_(this.v_)}N_(){this.T_(this.d_),this.T_(this.v_),this.vo.forEach((t=>{this.dr(t)&&this.T_(t.Dt())})),this.S_(),this.$i.$h()}$o(){return null===this.po&&(this.po=un(this.vo)),this.po}F_(){return this.u_}W_(){return this.c_}E_(t){const i=t.e_();if(i&&i.length>0&&!this.kl.Ni()){const i=this.kl.Zs();null!==i&&t.r_(i)}t.On()}y_(){const t=this.$o();if(0===t.length)return{j_:0,k_:0};let i=0,n=0;for(let s=0;sn&&(n=e))}return{j_:i,k_:n}}C_(t,i,n){let s=this.b_(i);if(null===s&&(s=this.f_(i,this.$i.W().overlayPriceScales)),this.vo.push(t),!ht(i)){const n=this.a_.get(i)||[];n.push(t),this.a_.set(i,n)}s.Uo(t),t.Gi(s),t.Ki(n),this.T_(s),this.po=null}p_(t,i,n){i.yr!==n.yr&&this.E_(t)}f_(t,i){const n=Object.assign({visible:!0,autoScale:!0},V(i)),s=new vn(t,n,this.$i.W().layout,this.$i.W().localization);return s.Eo(this.At()),s}}class mn{constructor(t,i,n=50){this.Xe=0,this.Ke=1,this.Ge=1,this.Qe=new Map,this.Je=new Map,this.H_=t,this.U_=i,this.tr=n}q_(t){const i=t.time,n=this.U_.cacheKey(i),s=this.Qe.get(n);if(void 0!==s)return s.Y_;if(this.Xe===this.tr){const t=this.Je.get(this.Ge);this.Je.delete(this.Ge),this.Qe.delete(d(t)),this.Ge++,this.Xe--}const e=this.H_(t);return this.Qe.set(n,{Y_:e,er:this.Ke}),this.Je.set(this.Ke,n),this.Xe++,this.Ke++,e}}class bn{constructor(t,i){c(t<=i,"right should be >= left"),this.Z_=t,this.X_=i}Os(){return this.Z_}ui(){return this.X_}K_(){return this.X_-this.Z_+1}Xr(t){return this.Z_<=t&&t<=this.X_}yh(t){return this.Z_===t.Os()&&this.X_===t.ui()}}function wn(t,i){return null===t||null===i?t===i:t.yh(i)}class gn{constructor(){this.G_=new Map,this.Qe=null,this.J_=!1}Q_(t){this.J_=t,this.Qe=null}tu(t,i){this.iu(i),this.Qe=null;for(let n=i;n{t<=n[0].index?i.push(s):n.splice(Rt(n,t,(i=>i.indexi-t))){if(!this.G_.get(n))continue;const s=i;i=[];const e=s.length;let r=0;const h=d(this.G_.get(n)),l=h.length;let a=1/0,o=-1/0;for(let n=0;n=t&&_-o>=t)i.push(l),o=_;else if(this.J_)return s}for(;ri.weight?t:i}class Sn{constructor(t,i,n,s){this.o_=0,this.ou=null,this._u=[],this.wo=null,this.bo=null,this.uu=new gn,this.cu=new Map,this.du=Mn.au(),this.fu=!0,this.vu=new C,this.pu=new C,this.mu=new C,this.bu=null,this.wu=null,this.gu=[],this.cn=i,this.ko=n,this.Mu=i.rightOffset,this.xu=i.barSpacing,this.$i=t,this.U_=s,this.Su(),this.uu.Q_(i.uniformDistribution)}W(){return this.cn}ku(t){T(this.ko,t),this.yu(),this.Su()}Hh(t,i){var n;T(this.cn,t),this.cn.fixLeftEdge&&this.Cu(),this.cn.fixRightEdge&&this.Tu(),void 0!==t.barSpacing&&this.$i.Gn(t.barSpacing),void 0!==t.rightOffset&&this.$i.Jn(t.rightOffset),void 0!==t.minBarSpacing&&this.$i.Gn(null!==(n=t.barSpacing)&&void 0!==n?n:this.xu),this.yu(),this.Su(),this.mu.m()}mn(t){var i,n;return null!==(n=null===(i=this._u[t])||void 0===i?void 0:i.time)&&void 0!==n?n:null}Ui(t){var i;return null!==(i=this._u[t])&&void 0!==i?i:null}Da(t,i){if(this._u.length<1)return null;if(this.U_.key(t)>this.U_.key(this._u[this._u.length-1].time))return i?this._u.length-1:null;const n=Rt(this._u,this.U_.key(t),((t,i)=>this.U_.key(t.time)0}Zs(){return this.Pu(),this.du.hu()}Ru(){return this.Pu(),this.du.lu()}Du(){const t=this.Zs();if(null===t)return null;const i={from:t.Os(),to:t.ui()};return this.Ou(i)}Ou(t){const i=Math.round(t.from),n=Math.round(t.to),s=f(this.Vu()),e=f(this.Bu());return{from:f(this.Ui(Math.max(s,i))),to:f(this.Ui(Math.min(e,n)))}}Au(t){return{from:f(this.Da(t.from,!0)),to:f(this.Da(t.to,!0))}}Hi(){return this.o_}x_(t){if(!isFinite(t)||t<=0)return;if(this.o_===t)return;const i=this.Ru(),n=this.o_;if(this.o_=t,this.fu=!0,this.cn.lockVisibleTimeRangeOnResize&&0!==n){const i=this.xu*t/n;this.xu=i}if(this.cn.fixLeftEdge&&null!==i&&i.Os()<=0){const i=n-t;this.Mu-=Math.round(i/this.xu)+1,this.fu=!0}this.zu(),this.Eu()}zt(t){if(this.Ni()||!R(t))return 0;const i=this.Iu()+this.Mu-t;return this.o_-(i+.5)*this.xu-1}Js(t,i){const n=this.Iu(),s=void 0===i?0:i.from,e=void 0===i?t.length:i.to;for(let i=s;ii/2&&!o?n.needAlignCoordinate=!1:n.needAlignCoordinate=_&&t.index<=l||u&&t.index>=a,c++}return this.gu.length=c,this.wu=this.gu,this.gu}Uu(){this.fu=!0,this.Gn(this.cn.barSpacing),this.Jn(this.cn.rightOffset)}qu(t){this.fu=!0,this.ou=t,this.Eu(),this.Cu()}Yu(t,i){const n=this.Nu(t),s=this.he(),e=s+i*(s/10);this.Gn(e),this.cn.rightBarStaysOnScroll||this.Jn(this.ju()+(n-this.Nu(t)))}Ko(t){this.wo&&this.i_(),null===this.bo&&null===this.bu&&(this.Ni()||(this.bo=t,this.Zu()))}Go(t){if(null===this.bu)return;const i=gt(this.o_-t,0,this.o_),n=gt(this.o_-f(this.bo),0,this.o_);0!==i&&0!==n&&this.Gn(this.bu.he*i/n)}Jo(){null!==this.bo&&(this.bo=null,this.Xu())}Qo(t){null===this.wo&&null===this.bu&&(this.Ni()||(this.wo=t,this.Zu()))}t_(t){if(null===this.wo)return;const i=(this.wo-t)/this.he();this.Mu=f(this.bu).ju+i,this.fu=!0,this.Eu()}i_(){null!==this.wo&&(this.wo=null,this.Xu())}Ku(){this.Gu(this.cn.rightOffset)}Gu(t,i=400){if(!isFinite(t))throw new RangeError("offset is required and must be finite number");if(!isFinite(i)||i<=0)throw new RangeError("animationDuration (optional) must be finite positive number");const n=this.Mu,s=performance.now();this.$i.Zn({Ju:t=>(t-s)/i>=1,Qu:e=>{const r=(e-s)/i;return r>=1?t:n+(t-n)*r}})}bt(t,i){this.fu=!0,this._u=t,this.uu.tu(t,i),this.Eu()}tc(){return this.vu}nc(){return this.pu}sc(){return this.mu}Iu(){return this.ou||0}ec(t){const i=t.K_();this.Wu(this.o_/i),this.Mu=t.ui()-this.Iu(),this.Eu(),this.fu=!0,this.$i.Fu(),this.$i.$h()}rc(){const t=this.Vu(),i=this.Bu();null!==t&&null!==i&&this.ec(new bn(t,i+this.cn.rightOffset))}hc(t){const i=new bn(t.from,t.to);this.ec(i)}qi(t){return void 0!==this.ko.timeFormatter?this.ko.timeFormatter(t.originalTime):this.U_.formatHorzItem(t.time)}Hu(){const{handleScroll:t,handleScale:i}=this.$i.W();return!(t.horzTouchDrag||t.mouseWheel||t.pressedMouseMove||t.vertTouchDrag||i.axisDoubleClickReset.time||i.axisPressedMouseMove.time||i.mouseWheel||i.pinch)}Vu(){return 0===this._u.length?null:0}Bu(){return 0===this._u.length?null:this._u.length-1}lc(t){return(this.o_-1-t)/this.xu}Nu(t){const i=this.lc(t),n=this.Iu()+this.Mu-i;return Math.round(1e6*n)/1e6}Wu(t){const i=this.xu;this.xu=t,this.zu(),i!==this.xu&&(this.fu=!0,this.ac())}Pu(){if(!this.fu)return;if(this.fu=!1,this.Ni())return void this.oc(Mn.au());const t=this.Iu(),i=this.o_/this.xu,n=this.Mu+t,s=new bn(n-i+1,n);this.oc(new Mn(s))}zu(){const t=this._c();if(this.xut&&(this.xu=t,this.fu=!0)}}_c(){return this.cn.fixLeftEdge&&this.cn.fixRightEdge&&0!==this._u.length?this.o_/this._u.length:this.cn.minBarSpacing}Eu(){const t=this.uc();null!==t&&this.Mui&&(this.Mu=i,this.fu=!0)}uc(){const t=this.Vu(),i=this.ou;if(null===t||null===i)return null;return t-i-1+(this.cn.fixLeftEdge?this.o_/this.xu:Math.min(2,this._u.length))}cc(){return this.cn.fixRightEdge?0:this.o_/this.xu-Math.min(2,this._u.length)}Zu(){this.bu={he:this.he(),ju:this.ju()}}Xu(){this.bu=null}$u(t){let i=this.cu.get(t.weight);return void 0===i&&(i=new mn((t=>this.dc(t)),this.U_),this.cu.set(t.weight,i)),i.q_(t)}dc(t){return this.U_.formatTickmark(t,this.ko)}oc(t){const i=this.du;this.du=t,wn(i.hu(),this.du.hu())||this.vu.m(),wn(i.lu(),this.du.lu())||this.pu.m(),this.ac()}ac(){this.wu=null}yu(){this.ac(),this.cu.clear()}Su(){this.U_.updateFormatter(this.ko)}Cu(){if(!this.cn.fixLeftEdge)return;const t=this.Vu();if(null===t)return;const i=this.Zs();if(null===i)return;const n=i.Os()-t;if(n<0){const t=this.Mu-n-1;this.Jn(t)}this.zu()}Tu(){this.Eu(),this.zu()}}class kn{X(t,i,n){t.useMediaCoordinateSpace((t=>this.K(t,i,n)))}wl(t,i,n){t.useMediaCoordinateSpace((t=>this.fc(t,i,n)))}fc(t,i,n){}}class yn extends kn{constructor(t){super(),this.vc=new Map,this.Et=t}K(t){}fc(t){if(!this.Et.yt)return;const{context:i,mediaSize:n}=t;let s=0;for(const t of this.Et.mc){if(0===t.Kt.length)continue;i.font=t.R;const e=this.bc(i,t.Kt);e>n.width?t.Yu=n.width/e:t.Yu=1,s+=t.wc*t.Yu}let e=0;switch(this.Et.gc){case"top":e=0;break;case"center":e=Math.max((n.height-s)/2,0);break;case"bottom":e=Math.max(n.height-s,0)}i.fillStyle=this.Et.O;for(const t of this.Et.mc){i.save();let s=0;switch(this.Et.Mc){case"left":i.textAlign="left",s=t.wc/2;break;case"center":i.textAlign="center",s=n.width/2;break;case"right":i.textAlign="right",s=n.width-1-t.wc/2}i.translate(s,e),i.textBaseline="top",i.font=t.R,i.scale(t.Yu,t.Yu),i.fillText(t.Kt,0,t.xc),i.restore(),e+=t.wc*t.Yu}}bc(t,i){const n=this.Sc(t.font);let s=n.get(i);return void 0===s&&(s=t.measureText(i).width,n.set(i,s)),s}Sc(t){let i=this.vc.get(t);return void 0===i&&(i=new Map,this.vc.set(t,i)),i}}class Cn{constructor(t){this.ft=!0,this.Ft={yt:!1,O:"",mc:[],gc:"center",Mc:"center"},this.Wt=new yn(this.Ft),this.jt=t}bt(){this.ft=!0}gt(){return this.ft&&(this.Mt(),this.ft=!1),this.Wt}Mt(){const t=this.jt.W(),i=this.Ft;i.yt=t.visible,i.yt&&(i.O=t.color,i.Mc=t.horzAlign,i.gc=t.vertAlign,i.mc=[{Kt:t.text,R:E(t.fontSize,t.fontFamily,t.fontStyle),wc:1.2*t.fontSize,xc:0,Yu:0}])}}class Tn extends st{constructor(t,i){super(),this.cn=i,this.wn=new Cn(this)}Rn(){return[]}Pn(){return[this.wn]}W(){return this.cn}On(){this.wn.bt()}}var Pn,Rn,Dn,On,Vn;!function(t){t[t.OnTouchEnd=0]="OnTouchEnd",t[t.OnNextTap=1]="OnNextTap"}(Pn||(Pn={}));class Bn{constructor(t,i,n){this.kc=[],this.yc=[],this.o_=0,this.Cc=null,this.Tc=new C,this.Pc=new C,this.Rc=null,this.Dc=t,this.cn=i,this.U_=n,this.Oc=new I(this),this.kl=new Sn(this,i.timeScale,this.cn.localization,n),this.vt=new rt(this,i.crosshair),this.Vc=new Zi(i.crosshair),this.Bc=new Tn(this,i.watermark),this.Ac(),this.kc[0].M_(2e3),this.zc=this.Ec(0),this.Ic=this.Ec(1)}Xl(){this.Lc(lt.es())}$h(){this.Lc(lt.ss())}aa(){this.Lc(new lt(1))}Kl(t){const i=this.Nc(t);this.Lc(i)}Fc(){return this.Cc}Wc(t){const i=this.Cc;this.Cc=t,null!==i&&this.Kl(i.jc),null!==t&&this.Kl(t.jc)}W(){return this.cn}Hh(t){T(this.cn,t),this.kc.forEach((i=>i.m_(t))),void 0!==t.timeScale&&this.kl.Hh(t.timeScale),void 0!==t.localization&&this.kl.ku(t.localization),(t.leftPriceScale||t.rightPriceScale)&&this.Tc.m(),this.zc=this.Ec(0),this.Ic=this.Ec(1),this.Xl()}Hc(t,i){if("left"===t)return void this.Hh({leftPriceScale:i});if("right"===t)return void this.Hh({rightPriceScale:i});const n=this.$c(t);null!==n&&(n.Dt.Hh(i),this.Tc.m())}$c(t){for(const i of this.kc){const n=i.b_(t);if(null!==n)return{Ht:i,Dt:n}}return null}St(){return this.kl}Uc(){return this.kc}qc(){return this.Bc}Yc(){return this.vt}Zc(){return this.Pc}Xc(t,i){t.Eo(i),this.Fu()}x_(t){this.o_=t,this.kl.x_(this.o_),this.kc.forEach((i=>i.x_(t))),this.Fu()}Ac(t){const i=new pn(this.kl,this);void 0!==t?this.kc.splice(t,0,i):this.kc.push(i);const n=void 0===t?this.kc.length-1:t,s=lt.es();return s.Nn(n,{Fn:0,Wn:!0}),this.Lc(s),i}D_(t,i,n){t.D_(i,n)}O_(t,i,n){t.O_(i,n),this.Gl(),this.Lc(this.Kc(t,2))}V_(t,i){t.V_(i),this.Lc(this.Kc(t,2))}B_(t,i,n){i.Do()||t.B_(i,n)}A_(t,i,n){i.Do()||(t.A_(i,n),this.Gl(),this.Lc(this.Kc(t,2)))}z_(t,i){i.Do()||(t.z_(i),this.Lc(this.Kc(t,2)))}I_(t,i){t.I_(i),this.Lc(this.Kc(t,2))}Gc(t){this.kl.Ko(t)}Jc(t,i){const n=this.St();if(n.Ni()||0===i)return;const s=n.Hi();t=Math.max(1,Math.min(t,s)),n.Yu(t,i),this.Fu()}Qc(t){this.td(0),this.nd(t),this.sd()}ed(t){this.kl.Go(t),this.Fu()}rd(){this.kl.Jo(),this.$h()}td(t){this.kl.Qo(t)}nd(t){this.kl.t_(t),this.Fu()}sd(){this.kl.i_(),this.$h()}wt(){return this.yc}hd(t,i,n,s,e){this.vt.gn(t,i);let r=NaN,h=this.kl.Lu(t);const l=this.kl.Zs();null!==l&&(h=Math.min(Math.max(l.Os(),h),l.ui()));const a=s.vn(),o=a.Ct();null!==o&&(r=a.pn(i,o)),r=this.Vc.Oa(r,h,s),this.vt.kn(h,r,s),this.aa(),e||this.Pc.m(this.vt.xt(),{x:t,y:i},n)}ld(t,i,n){const s=n.vn(),e=s.Ct(),r=s.Rt(t,f(e)),h=this.kl.Da(i,!0),l=this.kl.zt(f(h));this.hd(l,r,null,n,!0)}ad(t){this.Yc().Cn(),this.aa(),t||this.Pc.m(null,null,null)}Gl(){const t=this.vt.Ht();if(null!==t){const i=this.vt.xn(),n=this.vt.Sn();this.hd(i,n,null,t)}this.vt.On()}od(t,i,n){const s=this.kl.mn(0);void 0!==i&&void 0!==n&&this.kl.bt(i,n);const e=this.kl.mn(0),r=this.kl.Iu(),h=this.kl.Zs();if(null!==h&&null!==s&&null!==e){const i=h.Xr(r),l=this.U_.key(s)>this.U_.key(e),a=null!==t&&t>r&&!l,o=this.kl.W().allowShiftVisibleRangeOnWhitespaceReplacement,_=i&&(!(void 0===n)||o)&&this.kl.W().shiftVisibleRangeOnNewBar;if(a&&!_){const i=t-r;this.kl.Jn(this.kl.ju()-i)}}this.kl.qu(t)}ta(t){null!==t&&t.N_()}cr(t){const i=this.kc.find((i=>i.$o().includes(t)));return void 0===i?null:i}Fu(){this.Bc.On(),this.kc.forEach((t=>t.N_())),this.Gl()}S(){this.kc.forEach((t=>t.S())),this.kc.length=0,this.cn.localization.priceFormatter=void 0,this.cn.localization.percentageFormatter=void 0,this.cn.localization.timeFormatter=void 0}_d(){return this.Oc}mr(){return this.Oc.W()}w_(){return this.Tc}ud(t,i,n){const s=this.kc[0],e=this.dd(i,t,s,n);return this.yc.push(e),1===this.yc.length?this.Xl():this.$h(),e}fd(t){const i=this.cr(t),n=this.yc.indexOf(t);c(-1!==n,"Series not found"),this.yc.splice(n,1),f(i).Yo(t),t.S&&t.S()}Zl(t,i){const n=f(this.cr(t));n.Yo(t);const s=this.$c(i);if(null===s){const s=t.Xi();n.Uo(t,i,s)}else{const e=s.Ht===n?t.Xi():void 0;s.Ht.Uo(t,i,e)}}rc(){const t=lt.ss();t.$n(),this.Lc(t)}vd(t){const i=lt.ss();i.Yn(t),this.Lc(i)}Kn(){const t=lt.ss();t.Kn(),this.Lc(t)}Gn(t){const i=lt.ss();i.Gn(t),this.Lc(i)}Jn(t){const i=lt.ss();i.Jn(t),this.Lc(i)}Zn(t){const i=lt.ss();i.Zn(t),this.Lc(i)}Un(){const t=lt.ss();t.Un(),this.Lc(t)}pd(){return this.cn.rightPriceScale.visible?"right":"left"}md(){return this.Ic}q(){return this.zc}Bt(t){const i=this.Ic,n=this.zc;if(i===n)return i;if(t=Math.max(0,Math.min(100,Math.round(100*t))),null===this.Rc||this.Rc.Ts!==n||this.Rc.Ps!==i)this.Rc={Ts:n,Ps:i,bd:new Map};else{const i=this.Rc.bd.get(t);if(void 0!==i)return i}const s=function(t,i,n){const[s,e,r,h]=S(t),[l,a,o,_]=S(i),u=[m(s+n*(l-s)),m(e+n*(a-e)),m(r+n*(o-r)),b(h+n*(_-h))];return`rgba(${u[0]}, ${u[1]}, ${u[2]}, ${u[3]})`}(n,i,t/100);return this.Rc.bd.set(t,s),s}Kc(t,i){const n=new lt(i);if(null!==t){const s=this.kc.indexOf(t);n.Nn(s,{Fn:i})}return n}Nc(t,i){return void 0===i&&(i=2),this.Kc(this.cr(t),i)}Lc(t){this.Dc&&this.Dc(t),this.kc.forEach((t=>t.W_().Uh().bt()))}dd(t,i,n,s){const e=new Yi(this,t,i,n,s),r=void 0!==t.priceScaleId?t.priceScaleId:this.pd();return n.Uo(e,r),ht(r)||e.Hh(t),e}Ec(t){const i=this.cn.layout;return"gradient"===i.background.type?0===t?i.background.topColor:i.background.bottomColor:i.background.color}}function An(t){return!P(t)&&!D(t)}function zn(t){return P(t)}!function(t){t[t.Disabled=0]="Disabled",t[t.Continuous=1]="Continuous",t[t.OnDataUpdate=2]="OnDataUpdate"}(Rn||(Rn={})),function(t){t[t.LastBar=0]="LastBar",t[t.LastVisible=1]="LastVisible"}(Dn||(Dn={})),function(t){t.Solid="solid",t.VerticalGradient="gradient"}(On||(On={})),function(t){t[t.Year=0]="Year",t[t.Month=1]="Month",t[t.DayOfMonth=2]="DayOfMonth",t[t.Time=3]="Time",t[t.TimeWithSeconds=4]="TimeWithSeconds"}(Vn||(Vn={}));const En=t=>t.getUTCFullYear();function In(t,i,n){return i.replace(/yyyy/g,(t=>ot(En(t),4))(t)).replace(/yy/g,(t=>ot(En(t)%100,2))(t)).replace(/MMMM/g,((t,i)=>new Date(t.getUTCFullYear(),t.getUTCMonth(),1).toLocaleString(i,{month:"long"}))(t,n)).replace(/MMM/g,((t,i)=>new Date(t.getUTCFullYear(),t.getUTCMonth(),1).toLocaleString(i,{month:"short"}))(t,n)).replace(/MM/g,(t=>ot((t=>t.getUTCMonth()+1)(t),2))(t)).replace(/dd/g,(t=>ot((t=>t.getUTCDate())(t),2))(t))}class Ln{constructor(t="yyyy-MM-dd",i="default"){this.wd=t,this.gd=i}q_(t){return In(t,this.wd,this.gd)}}class Nn{constructor(t){this.Md=t||"%h:%m:%s"}q_(t){return this.Md.replace("%h",ot(t.getUTCHours(),2)).replace("%m",ot(t.getUTCMinutes(),2)).replace("%s",ot(t.getUTCSeconds(),2))}}const Fn={xd:"yyyy-MM-dd",Sd:"%h:%m:%s",kd:" ",yd:"default"};class Wn{constructor(t={}){const i=Object.assign(Object.assign({},Fn),t);this.Cd=new Ln(i.xd,i.yd),this.Td=new Nn(i.Sd),this.Pd=i.kd}q_(t){return`${this.Cd.q_(t)}${this.Pd}${this.Td.q_(t)}`}}function jn(t){return 60*t*60*1e3}function Hn(t){return 60*t*1e3}const $n=[{Rd:(Un=1,1e3*Un),Dd:10},{Rd:Hn(1),Dd:20},{Rd:Hn(5),Dd:21},{Rd:Hn(30),Dd:22},{Rd:jn(1),Dd:30},{Rd:jn(3),Dd:31},{Rd:jn(6),Dd:32},{Rd:jn(12),Dd:33}];var Un;function qn(t,i){if(t.getUTCFullYear()!==i.getUTCFullYear())return 70;if(t.getUTCMonth()!==i.getUTCMonth())return 60;if(t.getUTCDate()!==i.getUTCDate())return 50;for(let n=$n.length-1;n>=0;--n)if(Math.floor(i.getTime()/$n[n].Rd)!==Math.floor(t.getTime()/$n[n].Rd))return $n[n].Dd;return 0}function Yn(t){let i=t;if(D(t)&&(i=Xn(t)),!An(i))throw new Error("time must be of type BusinessDay");const n=new Date(Date.UTC(i.year,i.month-1,i.day,0,0,0,0));return{Od:Math.round(n.getTime()/1e3),Vd:i}}function Zn(t){if(!zn(t))throw new Error("time must be of type isUTCTimestamp");return{Od:t}}function Xn(t){const i=new Date(t);if(isNaN(i.getTime()))throw new Error(`Invalid date string=${t}, expected format=yyyy-mm-dd`);return{day:i.getUTCDate(),month:i.getUTCMonth()+1,year:i.getUTCFullYear()}}function Kn(t){D(t.time)&&(t.time=Xn(t.time))}class Gn{options(){return this.cn}setOptions(t){this.cn=t,this.updateFormatter(t.localization)}preprocessData(t){Array.isArray(t)?function(t){t.forEach(Kn)}(t):Kn(t)}createConverterToInternalObj(t){return f(function(t){return 0===t.length?null:An(t[0].time)||D(t[0].time)?Yn:Zn}(t))}key(t){return"object"==typeof t&&"Od"in t?t.Od:this.key(this.convertHorzItemToInternal(t))}cacheKey(t){const i=t;return void 0===i.Vd?new Date(1e3*i.Od).getTime():new Date(Date.UTC(i.Vd.year,i.Vd.month-1,i.Vd.day)).getTime()}convertHorzItemToInternal(t){return zn(i=t)?Zn(i):An(i)?Yn(i):Yn(Xn(i));var i}updateFormatter(t){if(!this.cn)return;const i=t.dateFormat;this.cn.timeScale.timeVisible?this.Bd=new Wn({xd:i,Sd:this.cn.timeScale.secondsVisible?"%h:%m:%s":"%h:%m",kd:" ",yd:t.locale}):this.Bd=new Ln(i,t.locale)}formatHorzItem(t){const i=t;return this.Bd.q_(new Date(1e3*i.Od))}formatTickmark(t,i){const n=function(t,i,n){switch(t){case 0:case 10:return i?n?4:3:2;case 20:case 21:case 22:case 30:case 31:case 32:case 33:return i?3:2;case 50:return 2;case 60:return 1;case 70:return 0}}(t.weight,this.cn.timeScale.timeVisible,this.cn.timeScale.secondsVisible),s=this.cn.timeScale;if(void 0!==s.tickMarkFormatter){const e=s.tickMarkFormatter(t.originalTime,n,i.locale);if(null!==e)return e}return function(t,i,n){const s={};switch(i){case 0:s.year="numeric";break;case 1:s.month="short";break;case 2:s.day="numeric";break;case 3:s.hour12=!1,s.hour="2-digit",s.minute="2-digit";break;case 4:s.hour12=!1,s.hour="2-digit",s.minute="2-digit",s.second="2-digit"}const e=void 0===t.Vd?new Date(1e3*t.Od):new Date(Date.UTC(t.Vd.year,t.Vd.month-1,t.Vd.day));return new Date(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds()).toLocaleString(n,s)}(t.time,n,i.locale)}maxTickMarkWeight(t){let i=t.reduce(xn,t[0]).weight;return i>30&&i<50&&(i=30),i}fillWeightsForPoints(t,i){!function(t,i=0){if(0===t.length)return;let n=0===i?null:t[i-1].time.Od,s=null!==n?new Date(1e3*n):null,e=0;for(let r=i;r1){const i=Math.ceil(e/(t.length-1)),n=new Date(1e3*(t[0].time.Od-i));t[0].timeWeight=qn(new Date(1e3*t[0].time.Od),n)}}(t,i)}static Ad(t){return T({localization:{dateFormat:"dd MMM 'yy"}},null!=t?t:{})}}function Jn(t){var i=t.width,n=t.height;if(i<0)throw new Error("Negative width is not allowed for Size");if(n<0)throw new Error("Negative height is not allowed for Size");return{width:i,height:n}}function Qn(t,i){return t.width===i.width&&t.height===i.height}var ts=function(){function t(t){var i=this;this._resolutionListener=function(){return i._onResolutionChanged()},this._resolutionMediaQueryList=null,this._observers=[],this._window=t,this._installResolutionListener()}return t.prototype.dispose=function(){this._uninstallResolutionListener(),this._window=null},Object.defineProperty(t.prototype,"value",{get:function(){return this._window.devicePixelRatio},enumerable:!1,configurable:!0}),t.prototype.subscribe=function(t){var i=this,n={next:t};return this._observers.push(n),{unsubscribe:function(){i._observers=i._observers.filter((function(t){return t!==n}))}}},t.prototype._installResolutionListener=function(){if(null!==this._resolutionMediaQueryList)throw new Error("Resolution listener is already installed");var t=this._window.devicePixelRatio;this._resolutionMediaQueryList=this._window.matchMedia("all and (resolution: ".concat(t,"dppx)")),this._resolutionMediaQueryList.addListener(this._resolutionListener)},t.prototype._uninstallResolutionListener=function(){null!==this._resolutionMediaQueryList&&(this._resolutionMediaQueryList.removeListener(this._resolutionListener),this._resolutionMediaQueryList=null)},t.prototype._reinstallResolutionListener=function(){this._uninstallResolutionListener(),this._installResolutionListener()},t.prototype._onResolutionChanged=function(){var t=this;this._observers.forEach((function(i){return i.next(t._window.devicePixelRatio)})),this._reinstallResolutionListener()},t}();var is=function(){function t(t,i,n){var s;this._canvasElement=null,this._bitmapSizeChangedListeners=[],this._suggestedBitmapSize=null,this._suggestedBitmapSizeChangedListeners=[],this._devicePixelRatioObservable=null,this._canvasElementResizeObserver=null,this._canvasElement=t,this._canvasElementClientSize=Jn({width:this._canvasElement.clientWidth,height:this._canvasElement.clientHeight}),this._transformBitmapSize=null!=i?i:function(t){return t},this._allowResizeObserver=null===(s=null==n?void 0:n.allowResizeObserver)||void 0===s||s,this._chooseAndInitObserver()}return t.prototype.dispose=function(){var t,i;if(null===this._canvasElement)throw new Error("Object is disposed");null===(t=this._canvasElementResizeObserver)||void 0===t||t.disconnect(),this._canvasElementResizeObserver=null,null===(i=this._devicePixelRatioObservable)||void 0===i||i.dispose(),this._devicePixelRatioObservable=null,this._suggestedBitmapSizeChangedListeners.length=0,this._bitmapSizeChangedListeners.length=0,this._canvasElement=null},Object.defineProperty(t.prototype,"canvasElement",{get:function(){if(null===this._canvasElement)throw new Error("Object is disposed");return this._canvasElement},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"canvasElementClientSize",{get:function(){return this._canvasElementClientSize},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"bitmapSize",{get:function(){return Jn({width:this.canvasElement.width,height:this.canvasElement.height})},enumerable:!1,configurable:!0}),t.prototype.resizeCanvasElement=function(t){this._canvasElementClientSize=Jn(t),this.canvasElement.style.width="".concat(this._canvasElementClientSize.width,"px"),this.canvasElement.style.height="".concat(this._canvasElementClientSize.height,"px"),this._invalidateBitmapSize()},t.prototype.subscribeBitmapSizeChanged=function(t){this._bitmapSizeChangedListeners.push(t)},t.prototype.unsubscribeBitmapSizeChanged=function(t){this._bitmapSizeChangedListeners=this._bitmapSizeChangedListeners.filter((function(i){return i!==t}))},Object.defineProperty(t.prototype,"suggestedBitmapSize",{get:function(){return this._suggestedBitmapSize},enumerable:!1,configurable:!0}),t.prototype.subscribeSuggestedBitmapSizeChanged=function(t){this._suggestedBitmapSizeChangedListeners.push(t)},t.prototype.unsubscribeSuggestedBitmapSizeChanged=function(t){this._suggestedBitmapSizeChangedListeners=this._suggestedBitmapSizeChangedListeners.filter((function(i){return i!==t}))},t.prototype.applySuggestedBitmapSize=function(){if(null!==this._suggestedBitmapSize){var t=this._suggestedBitmapSize;this._suggestedBitmapSize=null,this._resizeBitmap(t),this._emitSuggestedBitmapSizeChanged(t,this._suggestedBitmapSize)}},t.prototype._resizeBitmap=function(t){var i=this.bitmapSize;Qn(i,t)||(this.canvasElement.width=t.width,this.canvasElement.height=t.height,this._emitBitmapSizeChanged(i,t))},t.prototype._emitBitmapSizeChanged=function(t,i){var n=this;this._bitmapSizeChangedListeners.forEach((function(s){return s.call(n,t,i)}))},t.prototype._suggestNewBitmapSize=function(t){var i=this._suggestedBitmapSize,n=Jn(this._transformBitmapSize(t,this._canvasElementClientSize)),s=Qn(this.bitmapSize,n)?null:n;null===i&&null===s||null!==i&&null!==s&&Qn(i,s)||(this._suggestedBitmapSize=s,this._emitSuggestedBitmapSizeChanged(i,s))},t.prototype._emitSuggestedBitmapSizeChanged=function(t,i){var n=this;this._suggestedBitmapSizeChangedListeners.forEach((function(s){return s.call(n,t,i)}))},t.prototype._chooseAndInitObserver=function(){var t=this;this._allowResizeObserver?new Promise((function(t){var i=new ResizeObserver((function(n){t(n.every((function(t){return"devicePixelContentBoxSize"in t}))),i.disconnect()}));i.observe(document.body,{box:"device-pixel-content-box"})})).catch((function(){return!1})).then((function(i){return i?t._initResizeObserver():t._initDevicePixelRatioObservable()})):this._initDevicePixelRatioObservable()},t.prototype._initDevicePixelRatioObservable=function(){var t=this;if(null!==this._canvasElement){var i=ns(this._canvasElement);if(null===i)throw new Error("No window is associated with the canvas");this._devicePixelRatioObservable=function(t){return new ts(t)}(i),this._devicePixelRatioObservable.subscribe((function(){return t._invalidateBitmapSize()})),this._invalidateBitmapSize()}},t.prototype._invalidateBitmapSize=function(){var t,i;if(null!==this._canvasElement){var n=ns(this._canvasElement);if(null!==n){var s=null!==(i=null===(t=this._devicePixelRatioObservable)||void 0===t?void 0:t.value)&&void 0!==i?i:n.devicePixelRatio,e=this._canvasElement.getClientRects(),r=void 0!==e[0]?function(t,i){return Jn({width:Math.round(t.left*i+t.width*i)-Math.round(t.left*i),height:Math.round(t.top*i+t.height*i)-Math.round(t.top*i)})}(e[0],s):Jn({width:this._canvasElementClientSize.width*s,height:this._canvasElementClientSize.height*s});this._suggestNewBitmapSize(r)}}},t.prototype._initResizeObserver=function(){var t=this;null!==this._canvasElement&&(this._canvasElementResizeObserver=new ResizeObserver((function(i){var n=i.find((function(i){return i.target===t._canvasElement}));if(n&&n.devicePixelContentBoxSize&&n.devicePixelContentBoxSize[0]){var s=n.devicePixelContentBoxSize[0],e=Jn({width:s.inlineSize,height:s.blockSize});t._suggestNewBitmapSize(e)}})),this._canvasElementResizeObserver.observe(this._canvasElement,{box:"device-pixel-content-box"}))},t}();function ns(t){return t.ownerDocument.defaultView}var ss=function(){function t(t,i,n){if(0===i.width||0===i.height)throw new TypeError("Rendering target could only be created on a media with positive width and height");if(this._mediaSize=i,0===n.width||0===n.height)throw new TypeError("Rendering target could only be created using a bitmap with positive integer width and height");this._bitmapSize=n,this._context=t}return t.prototype.useMediaCoordinateSpace=function(t){try{return this._context.save(),this._context.setTransform(1,0,0,1,0,0),this._context.scale(this._horizontalPixelRatio,this._verticalPixelRatio),t({context:this._context,mediaSize:this._mediaSize})}finally{this._context.restore()}},t.prototype.useBitmapCoordinateSpace=function(t){try{return this._context.save(),this._context.setTransform(1,0,0,1,0,0),t({context:this._context,mediaSize:this._mediaSize,bitmapSize:this._bitmapSize,horizontalPixelRatio:this._horizontalPixelRatio,verticalPixelRatio:this._verticalPixelRatio})}finally{this._context.restore()}},Object.defineProperty(t.prototype,"_horizontalPixelRatio",{get:function(){return this._bitmapSize.width/this._mediaSize.width},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"_verticalPixelRatio",{get:function(){return this._bitmapSize.height/this._mediaSize.height},enumerable:!1,configurable:!0}),t}();function es(t,i){var n=t.canvasElementClientSize;if(0===n.width||0===n.height)return null;var s=t.bitmapSize;if(0===s.width||0===s.height)return null;var e=t.canvasElement.getContext("2d",i);return null===e?null:new ss(e,n,s)}const rs="undefined"!=typeof window;function hs(){return!!rs&&window.navigator.userAgent.toLowerCase().indexOf("firefox")>-1}function ls(){return!!rs&&/iPhone|iPad|iPod/.test(window.navigator.platform)}function as(t){return t+t%2}function os(t,i){return t.zd-i.zd}function _s(t,i,n){const s=(t.zd-i.zd)/(t.ot-i.ot);return Math.sign(s)*Math.min(Math.abs(s),n)}class us{constructor(t,i,n,s){this.Ed=null,this.Id=null,this.Ld=null,this.Nd=null,this.Fd=null,this.Wd=0,this.jd=0,this.Hd=t,this.$d=i,this.Ud=n,this.rs=s}qd(t,i){if(null!==this.Ed){if(this.Ed.ot===i)return void(this.Ed.zd=t);if(Math.abs(this.Ed.zd-t)50)return;let n=0;const s=_s(this.Ed,this.Id,this.$d),e=os(this.Ed,this.Id),r=[s],h=[e];if(n+=e,null!==this.Ld){const t=_s(this.Id,this.Ld,this.$d);if(Math.sign(t)===Math.sign(s)){const i=os(this.Id,this.Ld);if(r.push(t),h.push(i),n+=i,null!==this.Nd){const t=_s(this.Ld,this.Nd,this.$d);if(Math.sign(t)===Math.sign(s)){const i=os(this.Ld,this.Nd);r.push(t),h.push(i),n+=i}}}}let l=0;for(let t=0;t160?"dark":"light"}nf(){return this.Jd.W().layout.attributionLogo}ef(){const t=new URL(location.href);return t.hostname?"&utm_source="+t.hostname+t.pathname:""}Qd(){this.if()&&(this.tf(),this.en=this.nf(),this.en&&(this.Kd=this.sf(),this.Xd=document.createElement("style"),this.Xd.innerText="a#tv-attr-logo{--fill:#131722;--stroke:#fff;position:absolute;left:10px;bottom:10px;height:19px;width:35px;margin:0;padding:0;border:0;z-index:3;}a#tv-attr-logo[data-dark]{--fill:#D1D4DC;--stroke:#131722;}",this.Zd=document.createElement("a"),this.Zd.href=`https://www.tradingview.com/?utm_medium=lwc-link&utm_campaign=lwc-chart${this.ef()}`,this.Zd.title="Charting by TradingView",this.Zd.id="tv-attr-logo",this.Zd.target="_blank",this.Zd.innerHTML='',this.Zd.toggleAttribute("data-dark","dark"===this.Kd),this.Gd.appendChild(this.Xd),this.Gd.appendChild(this.Zd)))}}function ds(t,i){const n=f(t.ownerDocument).createElement("canvas");t.appendChild(n);const s=function(t,i){if("device-pixel-content-box"===i.type)return new is(t,i.transform,i.options);throw new Error("Unsupported binding target")}(n,{type:"device-pixel-content-box",options:{allowResizeObserver:!1},transform:(t,i)=>({width:Math.max(t.width,i.width),height:Math.max(t.height,i.height)})});return s.resizeCanvasElement(i),s}function fs(t){var i;t.width=1,t.height=1,null===(i=t.getContext("2d"))||void 0===i||i.clearRect(0,0,1,1)}function vs(t,i,n,s){t.wl&&t.wl(i,n,s)}function ps(t,i,n,s){t.X(i,n,s)}function ms(t,i,n,s){const e=t(n,s);for(const t of e){const n=t.gt();null!==n&&i(n)}}function bs(t){rs&&void 0!==window.chrome&&t.addEventListener("mousedown",(t=>{if(1===t.button)return t.preventDefault(),!1}))}class ws{constructor(t,i,n){this.rf=0,this.hf=null,this.lf={nt:Number.NEGATIVE_INFINITY,st:Number.POSITIVE_INFINITY},this.af=0,this._f=null,this.uf={nt:Number.NEGATIVE_INFINITY,st:Number.POSITIVE_INFINITY},this.cf=null,this.df=!1,this.ff=null,this.vf=null,this.pf=!1,this.mf=!1,this.bf=!1,this.wf=null,this.gf=null,this.Mf=null,this.xf=null,this.Sf=null,this.kf=null,this.yf=null,this.Cf=0,this.Tf=!1,this.Pf=!1,this.Rf=!1,this.Df=0,this.Of=null,this.Vf=!ls(),this.Bf=t=>{this.Af(t)},this.zf=t=>{if(this.Ef(t)){const i=this.If(t);if(++this.af,this._f&&this.af>1){const{Lf:n}=this.Nf(xs(t),this.uf);n<30&&!this.bf&&this.Ff(i,this.jf.Wf),this.Hf()}}else{const i=this.If(t);if(++this.rf,this.hf&&this.rf>1){const{Lf:n}=this.Nf(xs(t),this.lf);n<5&&!this.mf&&this.$f(i,this.jf.Uf),this.qf()}}},this.Yf=t,this.jf=i,this.cn=n,this.Zf()}S(){null!==this.wf&&(this.wf(),this.wf=null),null!==this.gf&&(this.gf(),this.gf=null),null!==this.xf&&(this.xf(),this.xf=null),null!==this.Sf&&(this.Sf(),this.Sf=null),null!==this.kf&&(this.kf(),this.kf=null),null!==this.Mf&&(this.Mf(),this.Mf=null),this.Xf(),this.qf()}Kf(t){this.xf&&this.xf();const i=this.Gf.bind(this);if(this.xf=()=>{this.Yf.removeEventListener("mousemove",i)},this.Yf.addEventListener("mousemove",i),this.Ef(t))return;const n=this.If(t);this.$f(n,this.jf.Jf),this.Vf=!0}qf(){null!==this.hf&&clearTimeout(this.hf),this.rf=0,this.hf=null,this.lf={nt:Number.NEGATIVE_INFINITY,st:Number.POSITIVE_INFINITY}}Hf(){null!==this._f&&clearTimeout(this._f),this.af=0,this._f=null,this.uf={nt:Number.NEGATIVE_INFINITY,st:Number.POSITIVE_INFINITY}}Gf(t){if(this.Rf||null!==this.vf)return;if(this.Ef(t))return;const i=this.If(t);this.$f(i,this.jf.Qf),this.Vf=!0}tv(t){const i=ks(t.changedTouches,f(this.Of));if(null===i)return;if(this.Df=Ss(t),null!==this.yf)return;if(this.Pf)return;this.Tf=!0;const n=this.Nf(xs(i),f(this.vf)),{iv:s,nv:e,Lf:r}=n;if(this.pf||!(r<5)){if(!this.pf){const t=.5*s,i=e>=t&&!this.cn.sv(),n=t>e&&!this.cn.ev();i||n||(this.Pf=!0),this.pf=!0,this.bf=!0,this.Xf(),this.Hf()}if(!this.Pf){const n=this.If(t,i);this.Ff(n,this.jf.rv),Ms(t)}}}hv(t){if(0!==t.button)return;const i=this.Nf(xs(t),f(this.ff)),{Lf:n}=i;if(n>=5&&(this.mf=!0,this.qf()),this.mf){const i=this.If(t);this.$f(i,this.jf.lv)}}Nf(t,i){const n=Math.abs(i.nt-t.nt),s=Math.abs(i.st-t.st);return{iv:n,nv:s,Lf:n+s}}av(t){let i=ks(t.changedTouches,f(this.Of));if(null===i&&0===t.touches.length&&(i=t.changedTouches[0]),null===i)return;this.Of=null,this.Df=Ss(t),this.Xf(),this.vf=null,this.kf&&(this.kf(),this.kf=null);const n=this.If(t,i);if(this.Ff(n,this.jf.ov),++this.af,this._f&&this.af>1){const{Lf:t}=this.Nf(xs(i),this.uf);t<30&&!this.bf&&this.Ff(n,this.jf.Wf),this.Hf()}else this.bf||(this.Ff(n,this.jf._v),this.jf._v&&Ms(t));0===this.af&&Ms(t),0===t.touches.length&&this.df&&(this.df=!1,Ms(t))}Af(t){if(0!==t.button)return;const i=this.If(t);if(this.ff=null,this.Rf=!1,this.Sf&&(this.Sf(),this.Sf=null),hs()){this.Yf.ownerDocument.documentElement.removeEventListener("mouseleave",this.Bf)}if(!this.Ef(t))if(this.$f(i,this.jf.uv),++this.rf,this.hf&&this.rf>1){const{Lf:n}=this.Nf(xs(t),this.lf);n<5&&!this.mf&&this.$f(i,this.jf.Uf),this.qf()}else this.mf||this.$f(i,this.jf.cv)}Xf(){null!==this.cf&&(clearTimeout(this.cf),this.cf=null)}dv(t){if(null!==this.Of)return;const i=t.changedTouches[0];this.Of=i.identifier,this.Df=Ss(t);const n=this.Yf.ownerDocument.documentElement;this.bf=!1,this.pf=!1,this.Pf=!1,this.vf=xs(i),this.kf&&(this.kf(),this.kf=null);{const i=this.tv.bind(this),s=this.av.bind(this);this.kf=()=>{n.removeEventListener("touchmove",i),n.removeEventListener("touchend",s)},n.addEventListener("touchmove",i,{passive:!1}),n.addEventListener("touchend",s,{passive:!1}),this.Xf(),this.cf=setTimeout(this.fv.bind(this,t),240)}const s=this.If(t,i);this.Ff(s,this.jf.vv),this._f||(this.af=0,this._f=setTimeout(this.Hf.bind(this),500),this.uf=xs(i))}pv(t){if(0!==t.button)return;const i=this.Yf.ownerDocument.documentElement;hs()&&i.addEventListener("mouseleave",this.Bf),this.mf=!1,this.ff=xs(t),this.Sf&&(this.Sf(),this.Sf=null);{const t=this.hv.bind(this),n=this.Af.bind(this);this.Sf=()=>{i.removeEventListener("mousemove",t),i.removeEventListener("mouseup",n)},i.addEventListener("mousemove",t),i.addEventListener("mouseup",n)}if(this.Rf=!0,this.Ef(t))return;const n=this.If(t);this.$f(n,this.jf.mv),this.hf||(this.rf=0,this.hf=setTimeout(this.qf.bind(this),500),this.lf=xs(t))}Zf(){this.Yf.addEventListener("mouseenter",this.Kf.bind(this)),this.Yf.addEventListener("touchcancel",this.Xf.bind(this));{const t=this.Yf.ownerDocument,i=t=>{this.jf.bv&&(t.composed&&this.Yf.contains(t.composedPath()[0])||t.target&&this.Yf.contains(t.target)||this.jf.bv())};this.gf=()=>{t.removeEventListener("touchstart",i)},this.wf=()=>{t.removeEventListener("mousedown",i)},t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!0})}ls()&&(this.Mf=()=>{this.Yf.removeEventListener("dblclick",this.zf)},this.Yf.addEventListener("dblclick",this.zf)),this.Yf.addEventListener("mouseleave",this.wv.bind(this)),this.Yf.addEventListener("touchstart",this.dv.bind(this),{passive:!0}),bs(this.Yf),this.Yf.addEventListener("mousedown",this.pv.bind(this)),this.gv(),this.Yf.addEventListener("touchmove",(()=>{}),{passive:!1})}gv(){void 0===this.jf.Mv&&void 0===this.jf.xv&&void 0===this.jf.Sv||(this.Yf.addEventListener("touchstart",(t=>this.kv(t.touches)),{passive:!0}),this.Yf.addEventListener("touchmove",(t=>{if(2===t.touches.length&&null!==this.yf&&void 0!==this.jf.xv){const i=gs(t.touches[0],t.touches[1])/this.Cf;this.jf.xv(this.yf,i),Ms(t)}}),{passive:!1}),this.Yf.addEventListener("touchend",(t=>{this.kv(t.touches)})))}kv(t){1===t.length&&(this.Tf=!1),2!==t.length||this.Tf||this.df?this.yv():this.Cv(t)}Cv(t){const i=this.Yf.getBoundingClientRect()||{left:0,top:0};this.yf={nt:(t[0].clientX-i.left+(t[1].clientX-i.left))/2,st:(t[0].clientY-i.top+(t[1].clientY-i.top))/2},this.Cf=gs(t[0],t[1]),void 0!==this.jf.Mv&&this.jf.Mv(),this.Xf()}yv(){null!==this.yf&&(this.yf=null,void 0!==this.jf.Sv&&this.jf.Sv())}wv(t){if(this.xf&&this.xf(),this.Ef(t))return;if(!this.Vf)return;const i=this.If(t);this.$f(i,this.jf.Tv),this.Vf=!ls()}fv(t){const i=ks(t.touches,f(this.Of));if(null===i)return;const n=this.If(t,i);this.Ff(n,this.jf.Pv),this.bf=!0,this.df=!0}Ef(t){return t.sourceCapabilities&&void 0!==t.sourceCapabilities.firesTouchEvents?t.sourceCapabilities.firesTouchEvents:Ss(t){"touchstart"!==t.type&&Ms(t)}}}}function gs(t,i){const n=t.clientX-i.clientX,s=t.clientY-i.clientY;return Math.sqrt(n*n+s*s)}function Ms(t){t.cancelable&&t.preventDefault()}function xs(t){return{nt:t.pageX,st:t.pageY}}function Ss(t){return t.timeStamp||performance.now()}function ks(t,i){for(let n=0;n{var s,e,r,h;return(null!==(e=null===(s=n.Dt())||void 0===s?void 0:s.Ta())&&void 0!==e?e:"")!==i?[]:null!==(h=null===(r=n.ca)||void 0===r?void 0:r.call(n,t))&&void 0!==h?h:[]}}function Ps(t,i,n,s){if(!t.length)return;let e=0;const r=n/2,h=t[0].At(s,!0);let l=1===i?r-(t[0].Oi()-h/2):t[0].Oi()-h/2-r;l=Math.max(0,l);for(let r=1;ru-o:_n)&&l>0){const s=1===i?-1-r:r-n,h=Math.min(s,l);for(let n=e;n{this.Wv||this.tn.Hv().$t().$h()},this.$v=()=>{this.Wv||this.tn.Hv().$t().$h()},this.tn=t,this.cn=i,this.So=i.layout,this.Oc=n,this.Uv="left"===s,this.qv=Ts("normal",s),this.Yv=Ts("top",s),this.Zv=Ts("bottom",s),this.Xv=document.createElement("div"),this.Xv.style.height="100%",this.Xv.style.overflow="hidden",this.Xv.style.width="25px",this.Xv.style.left="0",this.Xv.style.position="relative",this.Kv=ds(this.Xv,Jn({width:16,height:16})),this.Kv.subscribeSuggestedBitmapSizeChanged(this.jv);const e=this.Kv.canvasElement;e.style.position="absolute",e.style.zIndex="1",e.style.left="0",e.style.top="0",this.Gv=ds(this.Xv,Jn({width:16,height:16})),this.Gv.subscribeSuggestedBitmapSizeChanged(this.$v);const r=this.Gv.canvasElement;r.style.position="absolute",r.style.zIndex="2",r.style.left="0",r.style.top="0";const h={mv:this.Jv.bind(this),vv:this.Jv.bind(this),lv:this.Qv.bind(this),rv:this.Qv.bind(this),bv:this.tp.bind(this),uv:this.ip.bind(this),ov:this.ip.bind(this),Uf:this.np.bind(this),Wf:this.np.bind(this),Jf:this.sp.bind(this),Tv:this.ep.bind(this)};this.rp=new ws(this.Gv.canvasElement,h,{sv:()=>!this.cn.handleScroll.vertTouchDrag,ev:()=>!0})}S(){this.rp.S(),this.Gv.unsubscribeSuggestedBitmapSizeChanged(this.$v),fs(this.Gv.canvasElement),this.Gv.dispose(),this.Kv.unsubscribeSuggestedBitmapSizeChanged(this.jv),fs(this.Kv.canvasElement),this.Kv.dispose(),null!==this.Ii&&this.Ii.Xo().p(this),this.Ii=null}hp(){return this.Xv}P(){return this.So.fontSize}lp(){const t=this.Oc.W();return this.Jr!==t.R&&(this.Nv.ir(),this.Jr=t.R),t}ap(){if(null===this.Ii)return 0;let t=0;const i=this.lp(),n=f(this.Kv.canvasElement.getContext("2d"));n.save();const s=this.Ii.ja();n.font=this.op(),s.length>0&&(t=Math.max(this.Nv.xi(n,s[0].no),this.Nv.xi(n,s[s.length-1].no)));const e=this._p();for(let i=e.length;i--;){const s=this.Nv.xi(n,e[i].Kt());s>t&&(t=s)}const r=this.Ii.Ct();if(null!==r&&null!==this.Iv&&2!==this.cn.crosshair.mode){const i=this.Ii.pn(1,r),s=this.Ii.pn(this.Iv.height-2,r);t=Math.max(t,this.Nv.xi(n,this.Ii.Fi(Math.floor(Math.min(i,s))+.11111111111111,r)),this.Nv.xi(n,this.Ii.Fi(Math.ceil(Math.max(i,s))-.11111111111111,r)))}n.restore();const h=t||34;return as(Math.ceil(i.C+i.T+i.A+i.I+5+h))}up(t){null!==this.Iv&&Qn(this.Iv,t)||(this.Iv=t,this.Wv=!0,this.Kv.resizeCanvasElement(t),this.Gv.resizeCanvasElement(t),this.Wv=!1,this.Xv.style.width=`${t.width}px`,this.Xv.style.height=`${t.height}px`)}cp(){return f(this.Iv).width}Gi(t){this.Ii!==t&&(null!==this.Ii&&this.Ii.Xo().p(this),this.Ii=t,t.Xo().l(this.do.bind(this),this))}Dt(){return this.Ii}ir(){const t=this.tn.dp();this.tn.Hv().$t().I_(t,f(this.Dt()))}fp(t){if(null===this.Iv)return;if(1!==t){this.vp(),this.Kv.applySuggestedBitmapSize();const t=es(this.Kv);null!==t&&(t.useBitmapCoordinateSpace((t=>{this.pp(t),this.Ae(t)})),this.tn.mp(t,this.Zv),this.bp(t),this.tn.mp(t,this.qv),this.wp(t))}this.Gv.applySuggestedBitmapSize();const i=es(this.Gv);null!==i&&(i.useBitmapCoordinateSpace((({context:t,bitmapSize:i})=>{t.clearRect(0,0,i.width,i.height)})),this.gp(i),this.tn.mp(i,this.Yv))}Mp(){return this.Kv.bitmapSize}xp(t,i,n){const s=this.Mp();s.width>0&&s.height>0&&t.drawImage(this.Kv.canvasElement,i,n)}bt(){var t;null===(t=this.Ii)||void 0===t||t.ja()}Jv(t){if(null===this.Ii||this.Ii.Ni()||!this.cn.handleScale.axisPressedMouseMove.price)return;const i=this.tn.Hv().$t(),n=this.tn.dp();this.Lv=!0,i.D_(n,this.Ii,t.localY)}Qv(t){if(null===this.Ii||!this.cn.handleScale.axisPressedMouseMove.price)return;const i=this.tn.Hv().$t(),n=this.tn.dp(),s=this.Ii;i.O_(n,s,t.localY)}tp(){if(null===this.Ii||!this.cn.handleScale.axisPressedMouseMove.price)return;const t=this.tn.Hv().$t(),i=this.tn.dp(),n=this.Ii;this.Lv&&(this.Lv=!1,t.V_(i,n))}ip(t){if(null===this.Ii||!this.cn.handleScale.axisPressedMouseMove.price)return;const i=this.tn.Hv().$t(),n=this.tn.dp();this.Lv=!1,i.V_(n,this.Ii)}np(t){this.cn.handleScale.axisDoubleClickReset.price&&this.ir()}sp(t){if(null===this.Ii)return;!this.tn.Hv().$t().W().handleScale.axisPressedMouseMove.price||this.Ii.gh()||this.Ii.Oo()||this.Sp(1)}ep(t){this.Sp(0)}_p(){const t=[],i=null===this.Ii?void 0:this.Ii;return(n=>{for(let s=0;s{t.fillStyle=n.borderColor;const l=Math.max(1,Math.floor(h)),a=Math.floor(.5*h),o=Math.round(s.T*r);t.beginPath();for(const n of i)t.rect(Math.floor(e*r),Math.round(n.Ia*h)-a,o,l);t.fill()})),t.useMediaCoordinateSpace((({context:t})=>{var r;t.font=this.op(),t.fillStyle=null!==(r=n.textColor)&&void 0!==r?r:this.So.textColor,t.textAlign=this.Uv?"right":"left",t.textBaseline="middle";const h=this.Uv?Math.round(e-s.A):Math.round(e+s.T+s.A),l=i.map((i=>this.Nv.Mi(t,i.no)));for(let n=i.length;n--;){const s=i[n];t.fillText(s.no,h,s.Ia+l[n])}}))}vp(){if(null===this.Iv||null===this.Ii)return;const t=[],i=this.Ii.$o().slice(),n=this.tn.dp(),s=this.lp();this.Ii===n.vr()&&this.tn.dp().$o().forEach((t=>{n.dr(t)&&i.push(t)}));const e=this.Ii;i.forEach((i=>{i.Rn(n,e).forEach((i=>{i.Vi(null),i.Bi()&&t.push(i)}))})),t.forEach((t=>t.Vi(t.ki())));this.Ii.W().alignLabels&&this.kp(t,s)}kp(t,i){if(null===this.Iv)return;const n=this.Iv.height/2,s=t.filter((t=>t.ki()<=n)),e=t.filter((t=>t.ki()>n));s.sort(((t,i)=>i.ki()-t.ki())),e.sort(((t,i)=>t.ki()-i.ki()));for(const n of t){const t=Math.floor(n.At(i)/2),s=n.ki();s>-t&&sthis.Iv.height-t&&s{if(i.Ai()){i.gt(f(this.Ii)).X(t,n,this.Nv,s)}}))}gp(t){if(null===this.Iv||null===this.Ii)return;const i=this.tn.Hv().$t(),n=[],s=this.tn.dp(),e=i.Yc().Rn(s,this.Ii);e.length&&n.push(e);const r=this.lp(),h=this.Uv?"right":"left";n.forEach((i=>{i.forEach((i=>{i.gt(f(this.Ii)).X(t,r,this.Nv,h)}))}))}Sp(t){this.Xv.style.cursor=1===t?"ns-resize":"default"}do(){const t=this.ap();this.Fv{this.Wv||null===this.Lp||this.$i().$h()},this.$v=()=>{this.Wv||null===this.Lp||this.$i().$h()},this.Jd=t,this.Lp=i,this.Lp.F_().l(this.Np.bind(this),this,!0),this.Fp=document.createElement("td"),this.Fp.style.padding="0",this.Fp.style.position="relative";const n=document.createElement("div");n.style.width="100%",n.style.height="100%",n.style.position="relative",n.style.overflow="hidden",this.Wp=document.createElement("td"),this.Wp.style.padding="0",this.jp=document.createElement("td"),this.jp.style.padding="0",this.Fp.appendChild(n),this.Kv=ds(n,Jn({width:16,height:16})),this.Kv.subscribeSuggestedBitmapSizeChanged(this.jv);const s=this.Kv.canvasElement;s.style.position="absolute",s.style.zIndex="1",s.style.left="0",s.style.top="0",this.Gv=ds(n,Jn({width:16,height:16})),this.Gv.subscribeSuggestedBitmapSizeChanged(this.$v);const e=this.Gv.canvasElement;e.style.position="absolute",e.style.zIndex="2",e.style.left="0",e.style.top="0",this.Hp=document.createElement("tr"),this.Hp.appendChild(this.Wp),this.Hp.appendChild(this.Fp),this.Hp.appendChild(this.jp),this.$p(),this.rp=new ws(this.Gv.canvasElement,this,{sv:()=>null===this.Ap&&!this.Jd.W().handleScroll.vertTouchDrag,ev:()=>null===this.Ap&&!this.Jd.W().handleScroll.horzTouchDrag})}S(){null!==this.yp&&this.yp.S(),null!==this.Cp&&this.Cp.S(),this.Tp=null,this.Gv.unsubscribeSuggestedBitmapSizeChanged(this.$v),fs(this.Gv.canvasElement),this.Gv.dispose(),this.Kv.unsubscribeSuggestedBitmapSizeChanged(this.jv),fs(this.Kv.canvasElement),this.Kv.dispose(),null!==this.Lp&&this.Lp.F_().p(this),this.rp.S()}dp(){return f(this.Lp)}Up(t){var i,n;null!==this.Lp&&this.Lp.F_().p(this),this.Lp=t,null!==this.Lp&&this.Lp.F_().l(As.prototype.Np.bind(this),this,!0),this.$p(),this.Jd.qp().indexOf(this)===this.Jd.qp().length-1?(this.Tp=null!==(i=this.Tp)&&void 0!==i?i:new cs(this.Fp,this.Jd),this.Tp.bt()):(null===(n=this.Tp)||void 0===n||n.tf(),this.Tp=null)}Hv(){return this.Jd}hp(){return this.Hp}$p(){if(null!==this.Lp&&(this.Yp(),0!==this.$i().wt().length)){if(null!==this.yp){const t=this.Lp.P_();this.yp.Gi(f(t))}if(null!==this.Cp){const t=this.Lp.R_();this.Cp.Gi(f(t))}}}Zp(){null!==this.yp&&this.yp.bt(),null!==this.Cp&&this.Cp.bt()}g_(){return null!==this.Lp?this.Lp.g_():0}M_(t){this.Lp&&this.Lp.M_(t)}Jf(t){if(!this.Lp)return;this.Xp();const i=t.localX,n=t.localY;this.Kp(i,n,t)}mv(t){this.Xp(),this.Gp(),this.Kp(t.localX,t.localY,t)}Qf(t){var i;if(!this.Lp)return;this.Xp();const n=t.localX,s=t.localY;this.Kp(n,s,t);const e=this.br(n,s);this.Jd.Jp(null!==(i=null==e?void 0:e.Ev)&&void 0!==i?i:null),this.$i().Wc(e&&{jc:e.jc,Av:e.Av})}cv(t){null!==this.Lp&&(this.Xp(),this.Qp(t))}Uf(t){null!==this.Lp&&this.tm(this.Op,t)}Wf(t){this.Uf(t)}lv(t){this.Xp(),this.im(t),this.Kp(t.localX,t.localY,t)}uv(t){null!==this.Lp&&(this.Xp(),this.Bp=!1,this.nm(t))}_v(t){null!==this.Lp&&this.Qp(t)}Pv(t){if(this.Bp=!0,null===this.Ap){const i={x:t.localX,y:t.localY};this.sm(i,i,t)}}Tv(t){null!==this.Lp&&(this.Xp(),this.Lp.$t().Wc(null),this.rm())}hm(){return this.Dp}lm(){return this.Op}Mv(){this.Vp=1,this.$i().Un()}xv(t,i){if(!this.Jd.W().handleScale.pinch)return;const n=5*(i-this.Vp);this.Vp=i,this.$i().Jc(t.nt,n)}vv(t){this.Bp=!1,this.zp=null!==this.Ap,this.Gp();const i=this.$i().Yc();null!==this.Ap&&i.yt()&&(this.Ep={x:i.Yt(),y:i.Zt()},this.Ap={x:t.localX,y:t.localY})}rv(t){if(null===this.Lp)return;const i=t.localX,n=t.localY;if(null===this.Ap)this.im(t);else{this.zp=!1;const s=f(this.Ep),e=s.x+(i-this.Ap.x),r=s.y+(n-this.Ap.y);this.Kp(e,r,t)}}ov(t){0===this.Hv().W().trackingMode.exitMode&&(this.zp=!0),this.am(),this.nm(t)}br(t,i){const n=this.Lp;return null===n?null:function(t,i,n){const s=t.$o(),e=function(t,i,n){var s,e;let r,h;for(const o of t){const t=null!==(e=null===(s=o.fa)||void 0===s?void 0:s.call(o,i,n))&&void 0!==e?e:[];for(const i of t)l=i.zOrder,(!(a=null==r?void 0:r.zOrder)||"top"===l&&"top"!==a||"normal"===l&&"bottom"===a)&&(r=i,h=o)}var l,a;return r&&h?{zv:r,jc:h}:null}(s,i,n);if("top"===(null==e?void 0:e.zv.zOrder))return ys(e);for(const r of s){if(e&&e.jc===r&&"bottom"!==e.zv.zOrder&&!e.zv.isBackground)return ys(e);const s=Cs(r.Pn(t),i,n);if(null!==s)return{jc:r,Vv:s.Vv,Av:s.Av};if(e&&e.jc===r&&"bottom"!==e.zv.zOrder&&e.zv.isBackground)return ys(e)}return(null==e?void 0:e.zv)?ys(e):null}(n,t,i)}om(t,i){f("left"===i?this.yp:this.Cp).up(Jn({width:t,height:this.Iv.height}))}_m(){return this.Iv}up(t){Qn(this.Iv,t)||(this.Iv=t,this.Wv=!0,this.Kv.resizeCanvasElement(t),this.Gv.resizeCanvasElement(t),this.Wv=!1,this.Fp.style.width=t.width+"px",this.Fp.style.height=t.height+"px")}um(){const t=f(this.Lp);t.T_(t.P_()),t.T_(t.R_());for(const i of t.Va())if(t.dr(i)){const n=i.Dt();null!==n&&t.T_(n),i.On()}}Mp(){return this.Kv.bitmapSize}xp(t,i,n){const s=this.Mp();s.width>0&&s.height>0&&t.drawImage(this.Kv.canvasElement,i,n)}fp(t){if(0===t)return;if(null===this.Lp)return;if(t>1&&this.um(),null!==this.yp&&this.yp.fp(t),null!==this.Cp&&this.Cp.fp(t),1!==t){this.Kv.applySuggestedBitmapSize();const t=es(this.Kv);null!==t&&(t.useBitmapCoordinateSpace((t=>{this.pp(t)})),this.Lp&&(this.dm(t,Ds),this.fm(t),this.vm(t),this.dm(t,Os),this.dm(t,Vs)))}this.Gv.applySuggestedBitmapSize();const i=es(this.Gv);null!==i&&(i.useBitmapCoordinateSpace((({context:t,bitmapSize:i})=>{t.clearRect(0,0,i.width,i.height)})),this.pm(i),this.dm(i,Bs))}bm(){return this.yp}wm(){return this.Cp}mp(t,i){this.dm(t,i)}Np(){null!==this.Lp&&this.Lp.F_().p(this),this.Lp=null}Qp(t){this.tm(this.Dp,t)}tm(t,i){const n=i.localX,s=i.localY;t.M()&&t.m(this.$i().St().Lu(n),{x:n,y:s},i)}pp({context:t,bitmapSize:i}){const{width:n,height:s}=i,e=this.$i(),r=e.q(),h=e.md();r===h?Y(t,0,0,n,s,h):K(t,0,0,n,s,r,h)}fm(t){const i=f(this.Lp).W_().Uh().gt();null!==i&&i.X(t,!1)}vm(t){const i=this.$i().qc();this.gm(t,Os,vs,i),this.gm(t,Os,ps,i)}pm(t){this.gm(t,Os,ps,this.$i().Yc())}dm(t,i){const n=f(this.Lp).$o();for(const s of n)this.gm(t,i,vs,s);for(const s of n)this.gm(t,i,ps,s)}gm(t,i,n,s){const e=f(this.Lp),r=e.$t().Fc(),h=null!==r&&r.jc===s,l=null!==r&&h&&void 0!==r.Av?r.Av.gr:void 0;ms(i,(i=>n(i,t,h,l)),s,e)}Yp(){if(null===this.Lp)return;const t=this.Jd,i=this.Lp.P_().W().visible,n=this.Lp.R_().W().visible;i||null===this.yp||(this.Wp.removeChild(this.yp.hp()),this.yp.S(),this.yp=null),n||null===this.Cp||(this.jp.removeChild(this.Cp.hp()),this.Cp.S(),this.Cp=null);const s=t.$t()._d();i&&null===this.yp&&(this.yp=new Rs(this,t.W(),s,"left"),this.Wp.appendChild(this.yp.hp())),n&&null===this.Cp&&(this.Cp=new Rs(this,t.W(),s,"right"),this.jp.appendChild(this.Cp.hp()))}Mm(t){return t.Rv&&this.Bp||null!==this.Ap}xm(t){return Math.max(0,Math.min(t,this.Iv.width-1))}Sm(t){return Math.max(0,Math.min(t,this.Iv.height-1))}Kp(t,i,n){this.$i().hd(this.xm(t),this.Sm(i),n,f(this.Lp))}rm(){this.$i().ad()}am(){this.zp&&(this.Ap=null,this.rm())}sm(t,i,n){this.Ap=t,this.zp=!1,this.Kp(i.x,i.y,n);const s=this.$i().Yc();this.Ep={x:s.Yt(),y:s.Zt()}}$i(){return this.Jd.$t()}nm(t){if(!this.Rp)return;const i=this.$i(),n=this.dp();if(i.z_(n,n.vn()),this.Pp=null,this.Rp=!1,i.sd(),null!==this.Ip){const t=performance.now(),n=i.St();this.Ip.Dr(n.ju(),t),this.Ip.Ju(t)||i.Zn(this.Ip)}}Xp(){this.Ap=null}Gp(){if(!this.Lp)return;if(this.$i().Un(),document.activeElement!==document.body&&document.activeElement!==document.documentElement)f(document.activeElement).blur();else{const t=document.getSelection();null!==t&&t.removeAllRanges()}!this.Lp.vn().Ni()&&this.$i().St().Ni()}im(t){if(null===this.Lp)return;const i=this.$i(),n=i.St();if(n.Ni())return;const s=this.Jd.W(),e=s.handleScroll,r=s.kineticScroll;if((!e.pressedMouseMove||t.Rv)&&(!e.horzTouchDrag&&!e.vertTouchDrag||!t.Rv))return;const h=this.Lp.vn(),l=performance.now();if(null!==this.Pp||this.Mm(t)||(this.Pp={x:t.clientX,y:t.clientY,Od:l,km:t.localX,ym:t.localY}),null!==this.Pp&&!this.Rp&&(this.Pp.x!==t.clientX||this.Pp.y!==t.clientY)){if(t.Rv&&r.touch||!t.Rv&&r.mouse){const t=n.he();this.Ip=new us(.2/t,7/t,.997,15/t),this.Ip.qd(n.ju(),this.Pp.Od)}else this.Ip=null;h.Ni()||i.B_(this.Lp,h,t.localY),i.td(t.localX),this.Rp=!0}this.Rp&&(h.Ni()||i.A_(this.Lp,h,t.localY),i.nd(t.localX),null!==this.Ip&&this.Ip.qd(n.ju(),l))}}class zs{constructor(t,i,n,s,e){this.ft=!0,this.Iv=Jn({width:0,height:0}),this.jv=()=>this.fp(3),this.Uv="left"===t,this.Oc=n._d,this.cn=i,this.Cm=s,this.Tm=e,this.Xv=document.createElement("div"),this.Xv.style.width="25px",this.Xv.style.height="100%",this.Xv.style.overflow="hidden",this.Kv=ds(this.Xv,Jn({width:16,height:16})),this.Kv.subscribeSuggestedBitmapSizeChanged(this.jv)}S(){this.Kv.unsubscribeSuggestedBitmapSizeChanged(this.jv),fs(this.Kv.canvasElement),this.Kv.dispose()}hp(){return this.Xv}_m(){return this.Iv}up(t){Qn(this.Iv,t)||(this.Iv=t,this.Kv.resizeCanvasElement(t),this.Xv.style.width=`${t.width}px`,this.Xv.style.height=`${t.height}px`,this.ft=!0)}fp(t){if(t<3&&!this.ft)return;if(0===this.Iv.width||0===this.Iv.height)return;this.ft=!1,this.Kv.applySuggestedBitmapSize();const i=es(this.Kv);null!==i&&i.useBitmapCoordinateSpace((t=>{this.pp(t),this.Ae(t)}))}Mp(){return this.Kv.bitmapSize}xp(t,i,n){const s=this.Mp();s.width>0&&s.height>0&&t.drawImage(this.Kv.canvasElement,i,n)}Ae({context:t,bitmapSize:i,horizontalPixelRatio:n,verticalPixelRatio:s}){if(!this.Cm())return;t.fillStyle=this.cn.timeScale.borderColor;const e=Math.floor(this.Oc.W().C*n),r=Math.floor(this.Oc.W().C*s),h=this.Uv?i.width-e:0;t.fillRect(h,0,e,r)}pp({context:t,bitmapSize:i}){Y(t,0,0,i.width,i.height,this.Tm())}}function Es(t){return i=>{var n,s;return null!==(s=null===(n=i.da)||void 0===n?void 0:n.call(i,t))&&void 0!==s?s:[]}}const Is=Es("normal"),Ls=Es("top"),Ns=Es("bottom");class Fs{constructor(t,i){this.Pm=null,this.Rm=null,this.k=null,this.Dm=!1,this.Iv=Jn({width:0,height:0}),this.Om=new C,this.Nv=new Jt(5),this.Wv=!1,this.jv=()=>{this.Wv||this.Jd.$t().$h()},this.$v=()=>{this.Wv||this.Jd.$t().$h()},this.Jd=t,this.U_=i,this.cn=t.W().layout,this.Zd=document.createElement("tr"),this.Vm=document.createElement("td"),this.Vm.style.padding="0",this.Bm=document.createElement("td"),this.Bm.style.padding="0",this.Xv=document.createElement("td"),this.Xv.style.height="25px",this.Xv.style.padding="0",this.Am=document.createElement("div"),this.Am.style.width="100%",this.Am.style.height="100%",this.Am.style.position="relative",this.Am.style.overflow="hidden",this.Xv.appendChild(this.Am),this.Kv=ds(this.Am,Jn({width:16,height:16})),this.Kv.subscribeSuggestedBitmapSizeChanged(this.jv);const n=this.Kv.canvasElement;n.style.position="absolute",n.style.zIndex="1",n.style.left="0",n.style.top="0",this.Gv=ds(this.Am,Jn({width:16,height:16})),this.Gv.subscribeSuggestedBitmapSizeChanged(this.$v);const s=this.Gv.canvasElement;s.style.position="absolute",s.style.zIndex="2",s.style.left="0",s.style.top="0",this.Zd.appendChild(this.Vm),this.Zd.appendChild(this.Xv),this.Zd.appendChild(this.Bm),this.zm(),this.Jd.$t().w_().l(this.zm.bind(this),this),this.rp=new ws(this.Gv.canvasElement,this,{sv:()=>!0,ev:()=>!this.Jd.W().handleScroll.horzTouchDrag})}S(){this.rp.S(),null!==this.Pm&&this.Pm.S(),null!==this.Rm&&this.Rm.S(),this.Gv.unsubscribeSuggestedBitmapSizeChanged(this.$v),fs(this.Gv.canvasElement),this.Gv.dispose(),this.Kv.unsubscribeSuggestedBitmapSizeChanged(this.jv),fs(this.Kv.canvasElement),this.Kv.dispose()}hp(){return this.Zd}Em(){return this.Pm}Im(){return this.Rm}mv(t){if(this.Dm)return;this.Dm=!0;const i=this.Jd.$t();!i.St().Ni()&&this.Jd.W().handleScale.axisPressedMouseMove.time&&i.Gc(t.localX)}vv(t){this.mv(t)}bv(){const t=this.Jd.$t();!t.St().Ni()&&this.Dm&&(this.Dm=!1,this.Jd.W().handleScale.axisPressedMouseMove.time&&t.rd())}lv(t){const i=this.Jd.$t();!i.St().Ni()&&this.Jd.W().handleScale.axisPressedMouseMove.time&&i.ed(t.localX)}rv(t){this.lv(t)}uv(){this.Dm=!1;const t=this.Jd.$t();t.St().Ni()&&!this.Jd.W().handleScale.axisPressedMouseMove.time||t.rd()}ov(){this.uv()}Uf(){this.Jd.W().handleScale.axisDoubleClickReset.time&&this.Jd.$t().Kn()}Wf(){this.Uf()}Jf(){this.Jd.$t().W().handleScale.axisPressedMouseMove.time&&this.Sp(1)}Tv(){this.Sp(0)}_m(){return this.Iv}Lm(){return this.Om}Nm(t,i,n){Qn(this.Iv,t)||(this.Iv=t,this.Wv=!0,this.Kv.resizeCanvasElement(t),this.Gv.resizeCanvasElement(t),this.Wv=!1,this.Xv.style.width=`${t.width}px`,this.Xv.style.height=`${t.height}px`,this.Om.m(t)),null!==this.Pm&&this.Pm.up(Jn({width:i,height:t.height})),null!==this.Rm&&this.Rm.up(Jn({width:n,height:t.height}))}Fm(){const t=this.Wm();return Math.ceil(t.C+t.T+t.P+t.L+t.B+t.jm)}bt(){this.Jd.$t().St().ja()}Mp(){return this.Kv.bitmapSize}xp(t,i,n){const s=this.Mp();s.width>0&&s.height>0&&t.drawImage(this.Kv.canvasElement,i,n)}fp(t){if(0===t)return;if(1!==t){this.Kv.applySuggestedBitmapSize();const i=es(this.Kv);null!==i&&(i.useBitmapCoordinateSpace((t=>{this.pp(t),this.Ae(t),this.Hm(i,Ns)})),this.bp(i),this.Hm(i,Is)),null!==this.Pm&&this.Pm.fp(t),null!==this.Rm&&this.Rm.fp(t)}this.Gv.applySuggestedBitmapSize();const i=es(this.Gv);null!==i&&(i.useBitmapCoordinateSpace((({context:t,bitmapSize:i})=>{t.clearRect(0,0,i.width,i.height)})),this.$m([...this.Jd.$t().wt(),this.Jd.$t().Yc()],i),this.Hm(i,Ls))}Hm(t,i){const n=this.Jd.$t().wt();for(const s of n)ms(i,(i=>vs(i,t,!1,void 0)),s,void 0);for(const s of n)ms(i,(i=>ps(i,t,!1,void 0)),s,void 0)}pp({context:t,bitmapSize:i}){Y(t,0,0,i.width,i.height,this.Jd.$t().md())}Ae({context:t,bitmapSize:i,verticalPixelRatio:n}){if(this.Jd.W().timeScale.borderVisible){t.fillStyle=this.Um();const s=Math.max(1,Math.floor(this.Wm().C*n));t.fillRect(0,0,i.width,s)}}bp(t){const i=this.Jd.$t().St(),n=i.ja();if(!n||0===n.length)return;const s=this.U_.maxTickMarkWeight(n),e=this.Wm(),r=i.W();r.borderVisible&&r.ticksVisible&&t.useBitmapCoordinateSpace((({context:t,horizontalPixelRatio:i,verticalPixelRatio:s})=>{t.strokeStyle=this.Um(),t.fillStyle=this.Um();const r=Math.max(1,Math.floor(i)),h=Math.floor(.5*i);t.beginPath();const l=Math.round(e.T*s);for(let s=n.length;s--;){const e=Math.round(n[s].coord*i);t.rect(e-h,0,r,l)}t.fill()})),t.useMediaCoordinateSpace((({context:t})=>{const i=e.C+e.T+e.L+e.P/2;t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.$(),t.font=this.op();for(const e of n)if(e.weight=s){const n=e.needAlignCoordinate?this.qm(t,e.coord,e.label):e.coord;t.fillText(e.label,n,i)}}))}qm(t,i,n){const s=this.Nv.xi(t,n),e=s/2,r=Math.floor(i-e)+.5;return r<0?i+=Math.abs(0-r):r+s>this.Iv.width&&(i-=Math.abs(this.Iv.width-(r+s))),i}$m(t,i){const n=this.Wm();for(const s of t)for(const t of s.Qi())t.gt().X(i,n)}Um(){return this.Jd.W().timeScale.borderColor}$(){return this.cn.textColor}j(){return this.cn.fontSize}op(){return E(this.j(),this.cn.fontFamily)}Ym(){return E(this.j(),this.cn.fontFamily,"bold")}Wm(){null===this.k&&(this.k={C:1,N:NaN,L:NaN,B:NaN,ji:NaN,T:5,P:NaN,R:"",Wi:new Jt,jm:0});const t=this.k,i=this.op();if(t.R!==i){const n=this.j();t.P=n,t.R=i,t.L=3*n/12,t.B=3*n/12,t.ji=9*n/12,t.N=0,t.jm=4*n/12,t.Wi.ir()}return this.k}Sp(t){this.Xv.style.cursor=1===t?"ew-resize":"default"}zm(){const t=this.Jd.$t(),i=t.W();i.leftPriceScale.visible||null===this.Pm||(this.Vm.removeChild(this.Pm.hp()),this.Pm.S(),this.Pm=null),i.rightPriceScale.visible||null===this.Rm||(this.Bm.removeChild(this.Rm.hp()),this.Rm.S(),this.Rm=null);const n={_d:this.Jd.$t()._d()},s=()=>i.leftPriceScale.borderVisible&&t.St().W().borderVisible,e=()=>t.md();i.leftPriceScale.visible&&null===this.Pm&&(this.Pm=new zs("left",i,n,s,e),this.Vm.appendChild(this.Pm.hp())),i.rightPriceScale.visible&&null===this.Rm&&(this.Rm=new zs("right",i,n,s,e),this.Bm.appendChild(this.Rm.hp()))}}const Ws=!!rs&&!!navigator.userAgentData&&navigator.userAgentData.brands.some((t=>t.brand.includes("Chromium")))&&!!rs&&((null===(js=null===navigator||void 0===navigator?void 0:navigator.userAgentData)||void 0===js?void 0:js.platform)?"Windows"===navigator.userAgentData.platform:navigator.userAgent.toLowerCase().indexOf("win")>=0);var js;class Hs{constructor(t,i,n){var s;this.Zm=[],this.Xm=0,this.ro=0,this.o_=0,this.Km=0,this.Gm=0,this.Jm=null,this.Qm=!1,this.Dp=new C,this.Op=new C,this.Pc=new C,this.tb=null,this.ib=null,this.Gd=t,this.cn=i,this.U_=n,this.Zd=document.createElement("div"),this.Zd.classList.add("tv-lightweight-charts"),this.Zd.style.overflow="hidden",this.Zd.style.direction="ltr",this.Zd.style.width="100%",this.Zd.style.height="100%",(s=this.Zd).style.userSelect="none",s.style.webkitUserSelect="none",s.style.msUserSelect="none",s.style.MozUserSelect="none",s.style.webkitTapHighlightColor="transparent",this.nb=document.createElement("table"),this.nb.setAttribute("cellspacing","0"),this.Zd.appendChild(this.nb),this.sb=this.eb.bind(this),$s(this.cn)&&this.rb(!0),this.$i=new Bn(this.Dc.bind(this),this.cn,n),this.$t().Zc().l(this.hb.bind(this),this),this.lb=new Fs(this,this.U_),this.nb.appendChild(this.lb.hp());const e=i.autoSize&&this.ab();let r=this.cn.width,h=this.cn.height;if(e||0===r||0===h){const i=t.getBoundingClientRect();r=r||i.width,h=h||i.height}this.ob(r,h),this._b(),t.appendChild(this.Zd),this.ub(),this.$i.St().sc().l(this.$i.Xl.bind(this.$i),this),this.$i.w_().l(this.$i.Xl.bind(this.$i),this)}$t(){return this.$i}W(){return this.cn}qp(){return this.Zm}cb(){return this.lb}S(){this.rb(!1),0!==this.Xm&&window.cancelAnimationFrame(this.Xm),this.$i.Zc().p(this),this.$i.St().sc().p(this),this.$i.w_().p(this),this.$i.S();for(const t of this.Zm)this.nb.removeChild(t.hp()),t.hm().p(this),t.lm().p(this),t.S();this.Zm=[],f(this.lb).S(),null!==this.Zd.parentElement&&this.Zd.parentElement.removeChild(this.Zd),this.Pc.S(),this.Dp.S(),this.Op.S(),this.fb()}ob(t,i,n=!1){if(this.ro===i&&this.o_===t)return;const s=function(t){const i=Math.floor(t.width),n=Math.floor(t.height);return Jn({width:i-i%2,height:n-n%2})}(Jn({width:t,height:i}));this.ro=s.height,this.o_=s.width;const e=this.ro+"px",r=this.o_+"px";f(this.Zd).style.height=e,f(this.Zd).style.width=r,this.nb.style.height=e,this.nb.style.width=r,n?this.pb(lt.es(),performance.now()):this.$i.Xl()}fp(t){void 0===t&&(t=lt.es());for(let i=0;i{let s=0;for(let e=0;e{f("left"===i?this.lb.Em():this.lb.Im()).xp(f(t),n,s)};if(this.cn.timeScale.visible){const i=this.lb.Mp();if(null!==t){let e=0;this.Mb()&&(r("left",e,n),e=f(s.bm()).Mp().width),this.lb.xp(t,e,n),e+=i.width,this.xb()&&r("right",e,n)}n+=i.height}return Jn({width:i,height:n})}Tb(){let t=0,i=0,n=0;for(const s of this.Zm)this.Mb()&&(i=Math.max(i,f(s.bm()).ap(),this.cn.leftPriceScale.minimumWidth)),this.xb()&&(n=Math.max(n,f(s.wm()).ap(),this.cn.rightPriceScale.minimumWidth)),t+=s.g_();i=as(i),n=as(n);const s=this.o_,e=this.ro,r=Math.max(s-i-n,0),h=this.cn.timeScale.visible;let l=h?Math.max(this.lb.Fm(),this.cn.timeScale.minimumHeight):0;var a;l=(a=l)+a%2;const o=0+l,_=e{t.Zp()})),3===(null===(n=this.Jm)||void 0===n?void 0:n.jn())&&(this.Jm.ts(t),this.Rb(),this.Db(this.Jm),this.Ob(this.Jm,i),t=this.Jm,this.Jm=null)),this.fp(t)}Ob(t,i){for(const n of t.Qn())this.ns(n,i)}Db(t){const i=this.$i.Uc();for(let n=0;n{if(this.Qm=!1,this.Xm=0,null!==this.Jm){const i=this.Jm;this.Jm=null,this.pb(i,t);for(const n of i.Qn())if(5===n.qn&&!n.Ot.Ju(t)){this.$t().Zn(n.Ot);break}}})))}Rb(){this._b()}_b(){const t=this.$i.Uc(),i=t.length,n=this.Zm.length;for(let t=i;t{const n=i.zn().hl(t);null!==n&&e.set(i,n)}))}let r;if(null!==t){const i=null===(s=this.$i.St().Ui(t))||void 0===s?void 0:s.originalTime;void 0!==i&&(r=i)}const h=this.$t().Fc(),l=null!==h&&h.jc instanceof Yi?h.jc:void 0,a=null!==h&&void 0!==h.Av?h.Av.wr:void 0;return{zb:r,se:null!=t?t:void 0,Eb:null!=i?i:void 0,Ib:l,Lb:e,Nb:a,Fb:null!=n?n:void 0}}Vb(t,i,n){this.Dp.m((()=>this.Ab(t,i,n)))}Bb(t,i,n){this.Op.m((()=>this.Ab(t,i,n)))}hb(t,i,n){this.Pc.m((()=>this.Ab(t,i,n)))}ub(){const t=this.cn.timeScale.visible?"":"none";this.lb.hp().style.display=t}Mb(){return this.Zm[0].dp().P_().W().visible}xb(){return this.Zm[0].dp().R_().W().visible}ab(){return"ResizeObserver"in window&&(this.tb=new ResizeObserver((t=>{const i=t.find((t=>t.target===this.Gd));i&&this.ob(i.contentRect.width,i.contentRect.height)})),this.tb.observe(this.Gd,{box:"border-box"}),!0)}fb(){null!==this.tb&&this.tb.disconnect(),this.tb=null}}function $s(t){return Boolean(t.handleScroll.mouseWheel||t.handleScale.mouseWheel)}function Us(t){return function(t){return void 0!==t.open}(t)||function(t){return void 0!==t.value}(t)}function qs(t,i){var n={};for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&i.indexOf(s)<0&&(n[s]=t[s]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var e=0;for(s=Object.getOwnPropertySymbols(t);efunction(t,i){return i?i(t):void 0===(n=t).open&&void 0===n.value;var n}(s,h)?te({ot:i,se:n,zb:e},s):te(t(i,n,s,e,r),s)}function ne(t){return{Candlestick:ie(Gs),Bar:ie(Ks),Area:ie(Zs),Baseline:ie(Xs),Histogram:ie(Ys),Line:ie(Ys),Custom:ie(Js)}[t]}function se(t){return{se:0,jb:new Map,ha:t}}function ee(t,i){if(void 0!==t&&0!==t.length)return{Hb:i.key(t[0].ot),$b:i.key(t[t.length-1].ot)}}function re(t){let i;return t.forEach((t=>{void 0===i&&(i=t.zb)})),d(i)}class he{constructor(t){this.Ub=new Map,this.qb=new Map,this.Yb=new Map,this.Zb=[],this.U_=t}S(){this.Ub.clear(),this.qb.clear(),this.Yb.clear(),this.Zb=[]}Xb(t,i){let n=0!==this.Ub.size,s=!1;const e=this.qb.get(t);if(void 0!==e)if(1===this.qb.size)n=!1,s=!0,this.Ub.clear();else for(const i of this.Zb)i.pointData.jb.delete(t)&&(s=!0);let r=[];if(0!==i.length){const n=i.map((t=>t.time)),e=this.U_.createConverterToInternalObj(i),h=ne(t.Jh()),l=t.ya(),a=t.Ca();r=i.map(((i,r)=>{const o=e(i.time),_=this.U_.key(o);let u=this.Ub.get(_);void 0===u&&(u=se(o),this.Ub.set(_,u),s=!0);const c=h(o,u.se,i,n[r],l,a);return u.jb.set(t,c),c}))}n&&this.Kb(),this.Gb(t,r);let h=-1;if(s){const t=[];this.Ub.forEach((i=>{t.push({timeWeight:0,time:i.ha,pointData:i,originalTime:re(i.jb)})})),t.sort(((t,i)=>this.U_.key(t.time)-this.U_.key(i.time))),h=this.Jb(t)}return this.Qb(t,h,function(t,i,n){const s=ee(t,n),e=ee(i,n);if(void 0!==s&&void 0!==e)return{Ql:s.$b>=e.$b&&s.Hb>=e.Hb}}(this.qb.get(t),e,this.U_))}fd(t){return this.Xb(t,[])}tw(t,i){const n=i;!function(t){void 0===t.zb&&(t.zb=t.time)}(n),this.U_.preprocessData(i);const s=this.U_.createConverterToInternalObj([i])(i.time),e=this.Yb.get(t);if(void 0!==e&&this.U_.key(s)this.U_.key(t.time)this.U_.key(s.ot)?Qs(i)&&n.push(i):Qs(i)?n[n.length-1]=i:n.splice(-1,1),this.Yb.set(t,i.ot)}Gb(t,i){0!==i.length?(this.qb.set(t,i.filter(Qs)),this.Yb.set(t,i[i.length-1].ot)):(this.qb.delete(t),this.Yb.delete(t))}Kb(){for(const t of this.Zb)0===t.pointData.jb.size&&this.Ub.delete(this.U_.key(t.time))}Jb(t){let i=-1;for(let n=0;n{0!==i.length&&(t=Math.max(t,i[i.length-1].se))})),t}Qb(t,i,n){const s={sw:new Map,St:{Iu:this.nw()}};if(-1!==i)this.qb.forEach(((i,e)=>{s.sw.set(e,{He:i,ew:e===t?n:void 0})})),this.qb.has(t)||s.sw.set(t,{He:[],ew:n}),s.St.rw=this.Zb,s.St.hw=i;else{const i=this.qb.get(t);s.sw.set(t,{He:i||[],ew:n})}return s}}function le(t,i){t.se=i,t.jb.forEach((t=>{t.se=i}))}function ae(t){const i={value:t.Ot[3],time:t.zb};return void 0!==t.Wb&&(i.customValues=t.Wb),i}function oe(t){const i=ae(t);return void 0!==t.O&&(i.color=t.O),i}function _e(t){const i=ae(t);return void 0!==t.lt&&(i.lineColor=t.lt),void 0!==t.Ts&&(i.topColor=t.Ts),void 0!==t.Ps&&(i.bottomColor=t.Ps),i}function ue(t){const i=ae(t);return void 0!==t.Pe&&(i.topLineColor=t.Pe),void 0!==t.Re&&(i.bottomLineColor=t.Re),void 0!==t.Se&&(i.topFillColor1=t.Se),void 0!==t.ke&&(i.topFillColor2=t.ke),void 0!==t.ye&&(i.bottomFillColor1=t.ye),void 0!==t.Ce&&(i.bottomFillColor2=t.Ce),i}function ce(t){const i={open:t.Ot[0],high:t.Ot[1],low:t.Ot[2],close:t.Ot[3],time:t.zb};return void 0!==t.Wb&&(i.customValues=t.Wb),i}function de(t){const i=ce(t);return void 0!==t.O&&(i.color=t.O),i}function fe(t){const i=ce(t),{O:n,Vt:s,Zh:e}=t;return void 0!==n&&(i.color=n),void 0!==s&&(i.borderColor=s),void 0!==e&&(i.wickColor=e),i}function ve(t){return{Area:_e,Line:oe,Baseline:ue,Histogram:oe,Bar:de,Candlestick:fe,Custom:pe}[t]}function pe(t){const i=t.zb;return Object.assign(Object.assign({},t.He),{time:i})}const me={vertLine:{color:"#9598A1",width:1,style:3,visible:!0,labelVisible:!0,labelBackgroundColor:"#131722"},horzLine:{color:"#9598A1",width:1,style:3,visible:!0,labelVisible:!0,labelBackgroundColor:"#131722"},mode:1},be={vertLines:{color:"#D6DCDE",style:0,visible:!0},horzLines:{color:"#D6DCDE",style:0,visible:!0}},we={background:{type:"solid",color:"#FFFFFF"},textColor:"#191919",fontSize:12,fontFamily:z,attributionLogo:!0},ge={autoScale:!0,mode:0,invertScale:!1,alignLabels:!0,borderVisible:!0,borderColor:"#2B2B43",entireTextOnly:!1,visible:!1,ticksVisible:!1,scaleMargins:{bottom:.1,top:.2},minimumWidth:0},Me={rightOffset:0,barSpacing:6,minBarSpacing:.5,fixLeftEdge:!1,fixRightEdge:!1,lockVisibleTimeRangeOnResize:!1,rightBarStaysOnScroll:!1,borderVisible:!0,borderColor:"#2B2B43",visible:!0,timeVisible:!1,secondsVisible:!0,shiftVisibleRangeOnNewBar:!0,allowShiftVisibleRangeOnWhitespaceReplacement:!1,ticksVisible:!1,uniformDistribution:!1,minimumHeight:0,allowBoldLabels:!0},xe={color:"rgba(0, 0, 0, 0)",visible:!1,fontSize:48,fontFamily:z,fontStyle:"",text:"",horzAlign:"center",vertAlign:"center"};function Se(){return{width:0,height:0,autoSize:!1,layout:we,crosshair:me,grid:be,overlayPriceScales:Object.assign({},ge),leftPriceScale:Object.assign(Object.assign({},ge),{visible:!1}),rightPriceScale:Object.assign(Object.assign({},ge),{visible:!0}),timeScale:Me,watermark:xe,localization:{locale:rs?navigator.language:"",dateFormat:"dd MMM 'yy"},handleScroll:{mouseWheel:!0,pressedMouseMove:!0,horzTouchDrag:!0,vertTouchDrag:!0},handleScale:{axisPressedMouseMove:{time:!0,price:!0},axisDoubleClickReset:{time:!0,price:!0},mouseWheel:!0,pinch:!0},kineticScroll:{mouse:!1,touch:!0},trackingMode:{exitMode:1}}}class ke{constructor(t,i){this.lw=t,this.aw=i}applyOptions(t){this.lw.$t().Hc(this.aw,t)}options(){return this.Ii().W()}width(){return ht(this.aw)?this.lw.gb(this.aw):0}Ii(){return f(this.lw.$t().$c(this.aw)).Dt}}function ye(t,i,n){const s=qs(t,["time","originalTime"]),e=Object.assign({time:i},s);return void 0!==n&&(e.originalTime=n),e}const Ce={color:"#FF0000",price:0,lineStyle:2,lineWidth:1,lineVisible:!0,axisLabelVisible:!0,title:"",axisLabelColor:"",axisLabelTextColor:""};class Te{constructor(t){this.Lh=t}applyOptions(t){this.Lh.Hh(t)}options(){return this.Lh.W()}ow(){return this.Lh}}class Pe{constructor(t,i,n,s,e){this._w=new C,this.Is=t,this.uw=i,this.cw=n,this.U_=e,this.dw=s}S(){this._w.S()}priceFormatter(){return this.Is.ma()}priceToCoordinate(t){const i=this.Is.Ct();return null===i?null:this.Is.Dt().Rt(t,i.Ot)}coordinateToPrice(t){const i=this.Is.Ct();return null===i?null:this.Is.Dt().pn(t,i.Ot)}barsInLogicalRange(t){if(null===t)return null;const i=new Mn(new bn(t.from,t.to)).hu(),n=this.Is.zn();if(n.Ni())return null;const s=n.hl(i.Os(),1),e=n.hl(i.ui(),-1),r=f(n.sl()),h=f(n.An());if(null!==s&&null!==e&&s.se>e.se)return{barsBefore:t.from-r,barsAfter:h-t.to};const l={barsBefore:null===s||s.se===r?t.from-r:s.se-r,barsAfter:null===e||e.se===h?h-t.to:h-e.se};return null!==s&&null!==e&&(l.from=s.zb,l.to=e.zb),l}setData(t){this.U_,this.Is.Jh(),this.uw.fw(this.Is,t),this.pw("full")}update(t){this.Is.Jh(),this.uw.mw(this.Is,t),this.pw("update")}dataByIndex(t,i){const n=this.Is.zn().hl(t,i);if(null===n)return null;return ve(this.seriesType())(n)}data(){const t=ve(this.seriesType());return this.Is.zn().ie().map((i=>t(i)))}subscribeDataChanged(t){this._w.l(t)}unsubscribeDataChanged(t){this._w.v(t)}setMarkers(t){this.U_;const i=t.map((t=>ye(t,this.U_.convertHorzItemToInternal(t.time),t.time)));this.Is.ia(i)}markers(){return this.Is.na().map((t=>ye(t,t.originalTime,void 0)))}applyOptions(t){this.Is.Hh(t)}options(){return V(this.Is.W())}priceScale(){return this.cw.priceScale(this.Is.Dt().Ta())}createPriceLine(t){const i=T(V(Ce),t),n=this.Is.sa(i);return new Te(n)}removePriceLine(t){this.Is.ea(t.ow())}seriesType(){return this.Is.Jh()}attachPrimitive(t){this.Is.Sa(t),t.attached&&t.attached({chart:this.dw,series:this,requestUpdate:()=>this.Is.$t().Xl()})}detachPrimitive(t){this.Is.ka(t),t.detached&&t.detached()}pw(t){this._w.M()&&this._w.m(t)}}class Re{constructor(t,i,n){this.bw=new C,this.pu=new C,this.Om=new C,this.$i=t,this.kl=t.St(),this.lb=i,this.kl.tc().l(this.ww.bind(this)),this.kl.nc().l(this.gw.bind(this)),this.lb.Lm().l(this.Mw.bind(this)),this.U_=n}S(){this.kl.tc().p(this),this.kl.nc().p(this),this.lb.Lm().p(this),this.bw.S(),this.pu.S(),this.Om.S()}scrollPosition(){return this.kl.ju()}scrollToPosition(t,i){i?this.kl.Gu(t,1e3):this.$i.Jn(t)}scrollToRealTime(){this.kl.Ku()}getVisibleRange(){const t=this.kl.Du();return null===t?null:{from:t.from.originalTime,to:t.to.originalTime}}setVisibleRange(t){const i={from:this.U_.convertHorzItemToInternal(t.from),to:this.U_.convertHorzItemToInternal(t.to)},n=this.kl.Au(i);this.$i.vd(n)}getVisibleLogicalRange(){const t=this.kl.Ru();return null===t?null:{from:t.Os(),to:t.ui()}}setVisibleLogicalRange(t){c(t.from<=t.to,"The from index cannot be after the to index."),this.$i.vd(t)}resetTimeScale(){this.$i.Kn()}fitContent(){this.$i.rc()}logicalToCoordinate(t){const i=this.$i.St();return i.Ni()?null:i.zt(t)}coordinateToLogical(t){return this.kl.Ni()?null:this.kl.Lu(t)}timeToCoordinate(t){const i=this.U_.convertHorzItemToInternal(t),n=this.kl.Da(i,!1);return null===n?null:this.kl.zt(n)}coordinateToTime(t){const i=this.$i.St(),n=i.Lu(t),s=i.Ui(n);return null===s?null:s.originalTime}width(){return this.lb._m().width}height(){return this.lb._m().height}subscribeVisibleTimeRangeChange(t){this.bw.l(t)}unsubscribeVisibleTimeRangeChange(t){this.bw.v(t)}subscribeVisibleLogicalRangeChange(t){this.pu.l(t)}unsubscribeVisibleLogicalRangeChange(t){this.pu.v(t)}subscribeSizeChange(t){this.Om.l(t)}unsubscribeSizeChange(t){this.Om.v(t)}applyOptions(t){this.kl.Hh(t)}options(){return Object.assign(Object.assign({},V(this.kl.W())),{barSpacing:this.kl.he()})}ww(){this.bw.M()&&this.bw.m(this.getVisibleRange())}gw(){this.pu.M()&&this.pu.m(this.getVisibleLogicalRange())}Mw(t){this.Om.m(t.width,t.height)}}function De(t){if(void 0===t||"custom"===t.type)return;const i=t;void 0!==i.minMove&&void 0===i.precision&&(i.precision=function(t){if(t>=1)return 0;let i=0;for(;i<8;i++){const n=Math.round(t);if(Math.abs(n-t)<1e-8)return i;t*=10}return i}(i.minMove))}function Oe(t){return function(t){if(O(t.handleScale)){const i=t.handleScale;t.handleScale={axisDoubleClickReset:{time:i,price:i},axisPressedMouseMove:{time:i,price:i},mouseWheel:i,pinch:i}}else if(void 0!==t.handleScale){const{axisPressedMouseMove:i,axisDoubleClickReset:n}=t.handleScale;O(i)&&(t.handleScale.axisPressedMouseMove={time:i,price:i}),O(n)&&(t.handleScale.axisDoubleClickReset={time:n,price:n})}const i=t.handleScroll;O(i)&&(t.handleScroll={horzTouchDrag:i,vertTouchDrag:i,mouseWheel:i,pressedMouseMove:i})}(t),t}class Ve{constructor(t,i,n){this.xw=new Map,this.Sw=new Map,this.kw=new C,this.yw=new C,this.Cw=new C,this.Tw=new he(i);const s=void 0===n?V(Se()):T(V(Se()),Oe(n));this.U_=i,this.lw=new Hs(t,s,i),this.lw.hm().l((t=>{this.kw.M()&&this.kw.m(this.Pw(t()))}),this),this.lw.lm().l((t=>{this.yw.M()&&this.yw.m(this.Pw(t()))}),this),this.lw.Zc().l((t=>{this.Cw.M()&&this.Cw.m(this.Pw(t()))}),this);const e=this.lw.$t();this.Rw=new Re(e,this.lw.cb(),this.U_)}remove(){this.lw.hm().p(this),this.lw.lm().p(this),this.lw.Zc().p(this),this.Rw.S(),this.lw.S(),this.xw.clear(),this.Sw.clear(),this.kw.S(),this.yw.S(),this.Cw.S(),this.Tw.S()}resize(t,i,n){this.autoSizeActive()||this.lw.ob(t,i,n)}addCustomSeries(t,i){const n=v(t),s=Object.assign(Object.assign({},h),n.defaultOptions());return this.Dw("Custom",s,i,n)}addAreaSeries(t){return this.Dw("Area",s,t)}addBaselineSeries(t){return this.Dw("Baseline",e,t)}addBarSeries(t){return this.Dw("Bar",i,t)}addCandlestickSeries(i={}){return function(t){void 0!==t.borderColor&&(t.borderUpColor=t.borderColor,t.borderDownColor=t.borderColor),void 0!==t.wickColor&&(t.wickUpColor=t.wickColor,t.wickDownColor=t.wickColor)}(i),this.Dw("Candlestick",t,i)}addHistogramSeries(t){return this.Dw("Histogram",r,t)}addLineSeries(t){return this.Dw("Line",n,t)}removeSeries(t){const i=d(this.xw.get(t)),n=this.Tw.fd(i);this.lw.$t().fd(i),this.Ow(n),this.xw.delete(t),this.Sw.delete(i)}fw(t,i){this.Ow(this.Tw.Xb(t,i))}mw(t,i){this.Ow(this.Tw.tw(t,i))}subscribeClick(t){this.kw.l(t)}unsubscribeClick(t){this.kw.v(t)}subscribeCrosshairMove(t){this.Cw.l(t)}unsubscribeCrosshairMove(t){this.Cw.v(t)}subscribeDblClick(t){this.yw.l(t)}unsubscribeDblClick(t){this.yw.v(t)}priceScale(t){return new ke(this.lw,t)}timeScale(){return this.Rw}applyOptions(t){this.lw.Hh(Oe(t))}options(){return this.lw.W()}takeScreenshot(){return this.lw.bb()}autoSizeActive(){return this.lw.Sb()}chartElement(){return this.lw.kb()}paneSize(){const t=this.lw.Cb();return{height:t.height,width:t.width}}setCrosshairPosition(t,i,n){const s=this.xw.get(n);if(void 0===s)return;const e=this.lw.$t().cr(s);null!==e&&this.lw.$t().ld(t,i,e)}clearCrosshairPosition(){this.lw.$t().ad(!0)}Dw(t,i,n={},s){De(n.priceFormat);const e=T(V(l),V(i),n),r=this.lw.$t().ud(t,e,s),h=new Pe(r,this,this,this,this.U_);return this.xw.set(h,r),this.Sw.set(r,h),h}Ow(t){const i=this.lw.$t();i.od(t.St.Iu,t.St.rw,t.St.hw),t.sw.forEach(((t,i)=>i.J(t.He,t.ew))),i.Fu()}Vw(t){return d(this.Sw.get(t))}Pw(t){const i=new Map;t.Lb.forEach(((t,n)=>{const s=n.Jh(),e=ve(s)(t);if("Custom"!==s)c(Us(e));else{const t=n.Ca();c(!t||!1===t(e))}i.set(this.Vw(n),e)}));const n=void 0!==t.Ib&&this.Sw.has(t.Ib)?this.Vw(t.Ib):void 0;return{time:t.zb,logical:t.se,point:t.Eb,hoveredSeries:n,hoveredObjectId:t.Nb,seriesData:i,sourceEvent:t.Fb}}}function Be(t,i,n){let s;if(D(t)){const i=document.getElementById(t);c(null!==i,`Cannot find element in DOM with id=${t}`),s=i}else s=t;const e=new Ve(s,i,n);return i.setOptions(e.options()),e}function Ae(t,i){return Be(t,new Gn,Gn.Ad(i))}function ze(){return Gn}const Ee=Object.assign(Object.assign({},l),h);function Ie(){return"4.2.2"}export{On as ColorType,et as CrosshairMode,Rn as LastPriceAnimationMode,o as LineStyle,a as LineType,Bi as MismatchDirection,Dn as PriceLineSource,cn as PriceScaleMode,Vn as TickMarkType,Pn as TrackingModeExitMode,Ae as createChart,Be as createChartEx,Ee as customSeriesDefaultOptions,ze as defaultHorzScaleBehavior,An as isBusinessDay,zn as isUTCTimestamp,Ie as version}; diff --git a/websites/kibo.money/packages/lightweight-charts/v4.2.2/types.d.ts b/websites/kibo.money/packages/lightweight-charts/v4.2.2/types.d.ts new file mode 100644 index 000000000..39fbd07c7 --- /dev/null +++ b/websites/kibo.money/packages/lightweight-charts/v4.2.2/types.d.ts @@ -0,0 +1,3882 @@ +// Generated by dts-bundle-generator v8.0.1 + +type CanvasRenderingTarget2D = any; + +export declare const customSeriesDefaultOptions: CustomSeriesOptions; +/** + * Represents a type of color. + */ +export declare enum ColorType { + /** Solid color */ + Solid = "solid", + /** Vertical gradient color */ + VerticalGradient = "gradient", +} +/** + * Represents the crosshair mode. + */ +export declare enum CrosshairMode { + /** + * This mode allows crosshair to move freely on the chart. + */ + Normal = 0, + /** + * This mode sticks crosshair's horizontal line to the price value of a single-value series or to the close price of OHLC-based series. + */ + Magnet = 1, + /** + * This mode disables rendering of the crosshair. + */ + Hidden = 2, +} +/** + * Represents the type of the last price animation for series such as area or line. + */ +export declare enum LastPriceAnimationMode { + /** + * Animation is always disabled + */ + Disabled = 0, + /** + * Animation is always enabled. + */ + Continuous = 1, + /** + * Animation is active after new data. + */ + OnDataUpdate = 2, +} +/** + * Represents the possible line styles. + */ +export declare enum LineStyle { + /** + * A solid line. + */ + Solid = 0, + /** + * A dotted line. + */ + Dotted = 1, + /** + * A dashed line. + */ + Dashed = 2, + /** + * A dashed line with bigger dashes. + */ + LargeDashed = 3, + /** + * A dotted line with more space between dots. + */ + SparseDotted = 4, +} +/** + * Represents the possible line types. + */ +export declare enum LineType { + /** + * A line. + */ + Simple = 0, + /** + * A stepped line. + */ + WithSteps = 1, + /** + * A curved line. + */ + Curved = 2, +} +/** + * Search direction if no data found at provided index + */ +export declare enum MismatchDirection { + /** + * Search the nearest left item + */ + NearestLeft = -1, + /** + * Do not search + */ + None = 0, + /** + * Search the nearest right item + */ + NearestRight = 1, +} +/** + * Represents the source of data to be used for the horizontal price line. + */ +export declare enum PriceLineSource { + /** + * Use the last bar data. + */ + LastBar = 0, + /** + * Use the last visible data of the chart viewport. + */ + LastVisible = 1, +} +/** + * Represents the price scale mode. + */ +export declare enum PriceScaleMode { + /** + * Price scale shows prices. Price range changes linearly. + */ + Normal = 0, + /** + * Price scale shows prices. Price range changes logarithmically. + */ + Logarithmic = 1, + /** + * Price scale shows percentage values according the first visible value of the price scale. + * The first visible value is 0% in this mode. + */ + Percentage = 2, + /** + * The same as percentage mode, but the first value is moved to 100. + */ + IndexedTo100 = 3, +} +/** + * Represents the type of a tick mark on the time axis. + */ +export declare enum TickMarkType { + /** + * The start of the year (e.g. it's the first tick mark in a year). + */ + Year = 0, + /** + * The start of the month (e.g. it's the first tick mark in a month). + */ + Month = 1, + /** + * A day of the month. + */ + DayOfMonth = 2, + /** + * A time without seconds. + */ + Time = 3, + /** + * A time with seconds. + */ + TimeWithSeconds = 4, +} +/** + * Determine how to exit the tracking mode. + * + * By default, mobile users will long press to deactivate the scroll and have the ability to check values and dates. + * Another press is required to activate the scroll, be able to move left/right, zoom, etc. + */ +export declare enum TrackingModeExitMode { + /** + * Tracking Mode will be deactivated on touch end event. + */ + OnTouchEnd = 0, + /** + * Tracking Mode will be deactivated on the next tap event. + */ + OnNextTap = 1, +} +/** + * This function is the simplified main entry point of the Lightweight Charting Library with time points for the horizontal scale. + * + * @param container - ID of HTML element or element itself + * @param options - Any subset of options to be applied at start. + * @returns An interface to the created chart + */ +export declare function createChart( + container: string | HTMLElement, + options?: DeepPartial, +): IChartApi; +/** + * This function is the main entry point of the Lightweight Charting Library. If you are using time values + * for the horizontal scale then it is recommended that you rather use the {@link createChart} function. + * + * @template HorzScaleItem - type of points on the horizontal scale + * @template THorzScaleBehavior - type of horizontal axis strategy that encapsulate all the specific behaviors of the horizontal scale type + * + * @param container - ID of HTML element or element itself + * @param horzScaleBehavior - Horizontal scale behavior + * @param options - Any subset of options to be applied at start. + * @returns An interface to the created chart + */ +export declare function createChartEx< + HorzScaleItem, + THorzScaleBehavior extends IHorzScaleBehavior, +>( + container: string | HTMLElement, + horzScaleBehavior: THorzScaleBehavior, + options?: DeepPartial>, +): IChartApiBase; +/** + * Provides the default implementation of the horizontal scale (time-based) that can be used as a base for extending the horizontal scale with custom behavior. + * This allows for the introduction of custom functionality without re-implementing the entire {@link IHorzScaleBehavior}<{@link Time}> interface. + * + * For further details, refer to the {@link createChartEx} chart constructor method. + * + * @returns An uninitialized class implementing the {@link IHorzScaleBehavior}<{@link Time}> interface + */ +export declare function defaultHorzScaleBehavior(): new () => IHorzScaleBehavior