Compare commits

...

58 Commits

Author SHA1 Message Date
nym21 c5b16e7048 release: v0.3.0-beta.3 2026-04-22 15:56:15 +02:00
nym21 c1335cec31 docs: update generated docs 2026-04-22 15:55:53 +02:00
nym21 bdc3ba1df6 mempool: snap 2026-04-22 15:30:08 +02:00
nym21 6afce0bbdc mempool: fixes 2026-04-21 12:43:50 +02:00
nym21 327873d010 website: fixes 2026-04-20 18:11:30 +02:00
nym21 08175009d2 website: snap 2026-04-19 20:28:12 +02:00
nym21 a5d3be465e website: snap 2026-04-19 17:13:47 +02:00
nym21 fd2b93367d global: snap 2026-04-18 17:23:12 +02:00
nym21 2a93f51e81 global: snap 2026-04-17 21:23:11 +02:00
nym21 008143ff00 mempool: fix pending tx info 2026-04-16 23:35:55 +02:00
nym21 d340855c8b global: snap 2026-04-16 22:17:41 +02:00
nym21 78d6d9d6f1 changelog: updated 2026-04-16 10:15:23 +02:00
nym21 5cc85b0619 release: v0.3.0-beta.2 2026-04-15 19:49:13 +02:00
nym21 7433ce0d0e docs: update generated docs 2026-04-15 19:48:50 +02:00
nym21 75a97b4da9 client: js: add cache support to text fetches 2026-04-15 19:19:59 +02:00
nym21 c23e0f2a3c global: snap 2026-04-15 13:04:22 +02:00
nym21 08ba4ad996 global: snap 2026-04-15 12:51:30 +02:00
nym21 39da441d14 global: snapshot 2026-04-14 22:53:10 +02:00
nym21 904ec93668 deps: bumped 2026-04-14 01:42:24 +02:00
nym21 4cd8d9eb56 reader: snap 2026-04-14 01:37:04 +02:00
nym21 283baca848 global: big snapshot part 2 2026-04-13 22:47:08 +02:00
nym21 765261648d global: big snapshot 2026-04-13 22:46:56 +02:00
nym21 c3cef71aa3 global: snap 2026-04-12 18:00:02 +02:00
nym21 18d9c166d8 computer: snap 2026-04-11 22:11:48 +02:00
nym21 286256ebf0 global: veccached change 2026-04-10 11:30:29 +02:00
nym21 12aae503c9 global: snapshot pre cached change 2026-04-10 10:27:07 +02:00
nym21 95e5168244 deps: bumped 2026-04-09 15:08:06 +02:00
nym21 5fd9fff9cf global: speed improvement part4 2026-04-09 15:06:19 +02:00
nym21 db5b3887f9 global: speed improvement part3 2026-04-09 14:58:25 +02:00
nym21 5a3e1b4e6e global: speed improvement part2 2026-04-09 14:02:26 +02:00
nym21 21a0226a19 global: speed improvement 2026-04-09 11:52:01 +02:00
nym21 c5c49f62d1 clients: bump versions 2026-04-08 22:19:28 +02:00
nym21 dac66c988d release: v0.3.0-beta.1 2026-04-08 17:12:32 +02:00
nym21 303d168681 docs: update generated docs 2026-04-08 17:12:03 +02:00
nym21 1ddb3385e2 website: css 2026-04-08 17:06:54 +02:00
nym21 eb75274dbf website: fixes 2026-04-08 13:11:03 +02:00
nym21 3a7887348c global: snapshot 2026-04-08 12:09:35 +02:00
nym21 0a4cb0601f release: v0.3.0-beta.0 2026-04-08 01:46:43 +02:00
nym21 861e29277c docs: update generated docs 2026-04-08 01:46:13 +02:00
nym21 c76b149ef9 website: fixes 2026-04-08 01:43:58 +02:00
nym21 4c4c6fc840 global: snapshot 2026-04-08 01:38:03 +02:00
nym21 0c14dfe924 query: more optimizations 2026-04-07 17:57:57 +02:00
nym21 17e531b4ee query: more optimizations 2026-04-07 17:43:11 +02:00
nym21 f022f62cce global: snap 2026-04-07 13:49:02 +02:00
nym21 e91f1386b1 website: snap 2026-04-06 22:30:02 +02:00
nym21 02f543af38 release: v0.3.0-alpha.6 2026-04-05 22:48:37 +02:00
nym21 20c96fb551 docs: update generated docs 2026-04-05 22:48:10 +02:00
nym21 acd3d6f425 server: cache fixes 2026-04-05 22:43:30 +02:00
nym21 2b15a24b6d website: add pool logos 2026-04-05 19:46:41 +02:00
nym21 7fac0bc613 global: snap 2026-04-04 20:13:03 +02:00
nym21 62f51761ee global: snap 2026-04-04 18:19:11 +02:00
nym21 5340cc288e release: v0.3.0-alpha.5 2026-04-04 13:10:48 +02:00
nym21 befe3c8fb7 docs: update generated docs 2026-04-04 13:10:28 +02:00
nym21 41ec24c81e server: ms endpoint fixes 2026-04-04 13:05:39 +02:00
nym21 42b497ff65 server: ms endpoint fixes 2026-04-04 12:16:15 +02:00
nym21 01d908a560 release: v0.3.0-alpha.4 2026-04-04 11:59:17 +02:00
nym21 42debcce80 docs: update generated docs 2026-04-04 11:58:50 +02:00
nym21 8bc993eceb global: fixes 2026-04-04 11:53:27 +02:00
1145 changed files with 30005 additions and 13014 deletions
+1
View File
@@ -39,6 +39,7 @@ flamegraph.svg
# AI # AI
.claude/settings* .claude/settings*
!CLAUDE.md
# Expand # Expand
expand.rs expand.rs
+1
View File
@@ -0,0 +1 @@
Codex will review your output once you are done.
Generated
+123 -159
View File
@@ -41,9 +41,9 @@ dependencies = [
[[package]] [[package]]
name = "aide" name = "aide"
version = "0.16.0-alpha.3" version = "0.16.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87a769cd3a8984b7236931cd48f4d1f6b99c0475d60987a1f69490b079116306" checksum = "390515b47251185fa076ac92a7a582d9d383b03e13cef0c801e7670cf928229b"
dependencies = [ dependencies = [
"axum", "axum",
"bytes", "bytes",
@@ -122,9 +122,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.8" version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@@ -233,7 +233,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools", "itertools",
@@ -332,13 +332,13 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]] [[package]]
name = "brk" name = "brk"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_bencher", "brk_bencher",
"brk_bindgen", "brk_bindgen",
@@ -363,9 +363,9 @@ dependencies = [
[[package]] [[package]]
name = "brk-corepc-client" name = "brk-corepc-client"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c15bc86010adb0c3118d88a531a7d2633b726fe97a6c2888ce200946d198e7b" checksum = "a735b1f9b9e14301a267946f66b5e72bb147ce6e002ac1a34f5857f6849a5e24"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"brk-corepc-jsonrpc", "brk-corepc-jsonrpc",
@@ -377,9 +377,9 @@ dependencies = [
[[package]] [[package]]
name = "brk-corepc-jsonrpc" name = "brk-corepc-jsonrpc"
version = "0.19.0" version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb72c73b4c3aafd6ab3d0a0ad07b2961543929452d7c5d8f11b88e7a1ca7725" checksum = "4eb0983c8009f2d34fa8dce3f08c2ab3aa1ea0cf092cc47be8934219b0b383eb"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"serde", "serde",
@@ -388,9 +388,9 @@ dependencies = [
[[package]] [[package]]
name = "brk-corepc-types" name = "brk-corepc-types"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca4662e4e22838c09a6f80d1677f6177295eddbb524515be9156a8f8412c4147" checksum = "b3f711507c9872a538ab57241486a3c59bf5827651a725928b862f296828f9b0"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"serde", "serde",
@@ -399,7 +399,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_alloc" name = "brk_alloc"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"libmimalloc-sys", "libmimalloc-sys",
"mimalloc", "mimalloc",
@@ -407,7 +407,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_bencher" name = "brk_bencher"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_error", "brk_error",
"brk_logger", "brk_logger",
@@ -417,14 +417,14 @@ dependencies = [
[[package]] [[package]]
name = "brk_bencher_visualizer" name = "brk_bencher_visualizer"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"plotters", "plotters",
] ]
[[package]] [[package]]
name = "brk_bindgen" name = "brk_bindgen"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_cohort", "brk_cohort",
"brk_query", "brk_query",
@@ -437,7 +437,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_cli" name = "brk_cli"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"brk_alloc", "brk_alloc",
@@ -462,7 +462,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_client" name = "brk_client"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_cohort", "brk_cohort",
"brk_types", "brk_types",
@@ -473,7 +473,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_cohort" name = "brk_cohort"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_error", "brk_error",
"brk_traversable", "brk_traversable",
@@ -485,7 +485,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_computer" name = "brk_computer"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"brk_alloc", "brk_alloc",
@@ -514,7 +514,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_error" name = "brk_error"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -531,7 +531,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_fetcher" name = "brk_fetcher"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_error", "brk_error",
"brk_logger", "brk_logger",
@@ -543,7 +543,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_indexer" name = "brk_indexer"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"brk_alloc", "brk_alloc",
@@ -560,7 +560,6 @@ dependencies = [
"fjall", "fjall",
"parking_lot", "parking_lot",
"rayon", "rayon",
"rlimit",
"rustc-hash", "rustc-hash",
"schemars", "schemars",
"serde", "serde",
@@ -570,7 +569,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_iterator" name = "brk_iterator"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_error", "brk_error",
"brk_reader", "brk_reader",
@@ -580,7 +579,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_logger" name = "brk_logger"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"jiff", "jiff",
"owo-colors", "owo-colors",
@@ -591,8 +590,9 @@ dependencies = [
[[package]] [[package]]
name = "brk_mempool" name = "brk_mempool"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin",
"brk_error", "brk_error",
"brk_logger", "brk_logger",
"brk_rpc", "brk_rpc",
@@ -606,7 +606,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_oracle" name = "brk_oracle"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_indexer", "brk_indexer",
"brk_types", "brk_types",
@@ -616,7 +616,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_query" name = "brk_query"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"brk_computer", "brk_computer",
@@ -631,6 +631,7 @@ dependencies = [
"jiff", "jiff",
"parking_lot", "parking_lot",
"quickmatch", "quickmatch",
"rustc-hash",
"serde_json", "serde_json",
"tokio", "tokio",
"vecdb", "vecdb",
@@ -638,7 +639,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_reader" name = "brk_reader"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"brk_error", "brk_error",
@@ -647,13 +648,14 @@ dependencies = [
"crossbeam", "crossbeam",
"derive_more", "derive_more",
"parking_lot", "parking_lot",
"rayon", "rlimit",
"rustc-hash",
"tracing", "tracing",
] ]
[[package]] [[package]]
name = "brk_rpc" name = "brk_rpc"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -670,7 +672,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_server" name = "brk_server"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"aide", "aide",
"axum", "axum",
@@ -705,7 +707,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_store" name = "brk_store"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_error", "brk_error",
"brk_types", "brk_types",
@@ -716,7 +718,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_traversable" name = "brk_traversable"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"brk_traversable_derive", "brk_traversable_derive",
"brk_types", "brk_types",
@@ -729,7 +731,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_traversable_derive" name = "brk_traversable_derive"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -738,7 +740,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_types" name = "brk_types"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"brk_error", "brk_error",
@@ -761,7 +763,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_website" name = "brk_website"
version = "0.3.0-alpha.3" version = "0.3.0-beta.3"
dependencies = [ dependencies = [
"axum", "axum",
"brk_logger", "brk_logger",
@@ -833,9 +835,9 @@ checksum = "1c53ba0f290bfc610084c05582d9c5d421662128fc69f4bf236707af6fd321b9"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.58" version = "1.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -1284,9 +1286,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]] [[package]]
name = "fdeflate" name = "fdeflate"
@@ -1357,7 +1359,7 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"byteorder", "byteorder",
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
@@ -1423,21 +1425,6 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.32" version = "0.3.32"
@@ -1445,7 +1432,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -1454,34 +1440,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.32" version = "0.3.32"
@@ -1500,13 +1458,8 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"slab", "slab",
] ]
@@ -1601,6 +1554,12 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@@ -1887,12 +1846,12 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.0" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"serde", "serde",
"serde_core", "serde_core",
] ]
@@ -1964,9 +1923,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.94" version = "0.3.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@@ -2004,9 +1963,9 @@ checksum = "803ec87c9cfb29b9d2633f20cba1f488db3fd53f2158b1024cbefb47ba05d413"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.184" version = "0.2.185"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -2020,13 +1979,12 @@ dependencies = [
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
version = "0.1.44" version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6"
dependencies = [ dependencies = [
"cc", "cc",
"cty", "cty",
"libc",
] ]
[[package]] [[package]]
@@ -2042,9 +2000,9 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -2142,9 +2100,9 @@ dependencies = [
[[package]] [[package]]
name = "mimalloc" name = "mimalloc"
version = "0.1.48" version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640"
dependencies = [ dependencies = [
"libmimalloc-sys", "libmimalloc-sys",
] ]
@@ -2329,9 +2287,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.32" version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]] [[package]]
name = "plotters" name = "plotters"
@@ -2400,9 +2358,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]] [[package]]
name = "portable-atomic-util" name = "portable-atomic-util"
version = "0.2.6" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [ dependencies = [
"portable-atomic", "portable-atomic",
] ]
@@ -2494,9 +2452,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha",
@@ -2542,9 +2500,9 @@ dependencies = [
[[package]] [[package]]
name = "rawdb" name = "rawdb"
version = "0.9.3" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23b5d5fae99af33e8d0c82763b890c469dcf18b48600ed78b0d70fce4dbe189" checksum = "e4ed1c5accd49323c74351b29118b82b456268580e2a61d259d1a859265c82e2"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
@@ -2557,9 +2515,9 @@ dependencies = [
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [ dependencies = [
"either", "either",
"rayon-core", "rayon-core",
@@ -2581,7 +2539,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
] ]
[[package]] [[package]]
@@ -2694,7 +2652,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@@ -2703,9 +2661,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.37" version = "0.23.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@@ -2727,9 +2685,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.10" version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -2818,9 +2776,9 @@ checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]] [[package]]
name = "serde" name = "serde"
@@ -2900,15 +2858,15 @@ dependencies = [
[[package]] [[package]]
name = "serde_qs" name = "serde_qs"
version = "0.14.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" checksum = "c2316d01592c3382277c5062105510e35e0a6bfb2851e30028485f7af8cf1240"
dependencies = [ dependencies = [
"axum", "axum",
"futures", "itoa",
"percent-encoding", "percent-encoding",
"ryu",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]
@@ -3141,9 +3099,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.50.0" version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [ dependencies = [
"libc", "libc",
"mio", "mio",
@@ -3155,9 +3113,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.6.1" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3238,7 +3196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"bitflags 2.11.0", "bitflags 2.11.1",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -3436,9 +3394,9 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
[[package]] [[package]]
name = "vecdb" name = "vecdb"
version = "0.9.3" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5fe60956ddba8c141ca8020aaf5bea55683475b83d19006c5f44b85c71bf974" checksum = "055727d024d7b99d298378b0fa30cf11ff4511ccce9d299bc792e2a213a85bba"
dependencies = [ dependencies = [
"itoa", "itoa",
"libc", "libc",
@@ -3459,9 +3417,9 @@ dependencies = [
[[package]] [[package]]
name = "vecdb_derive" name = "vecdb_derive"
version = "0.9.3" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "789897c1999d5d74f977020ad3d449846df046194103a4afcbac6d49baeaaffc" checksum = "63e2c28b3de48dff1708bd4217da39ef94c1b554cd2799109b88745069fe6728"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@@ -3491,11 +3449,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.2+wasi-0.2.9" version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen 0.57.1",
] ]
[[package]] [[package]]
@@ -3504,14 +3462,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen 0.51.0",
] ]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.117" version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -3522,9 +3480,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.117" version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -3532,9 +3490,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.117" version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@@ -3545,9 +3503,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.117" version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -3580,7 +3538,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap", "indexmap",
"semver", "semver",
@@ -3588,9 +3546,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.94" version = "0.3.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -3598,9 +3556,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
@@ -3785,9 +3743,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
[[package]] [[package]]
name = "wio" name = "wio"
@@ -3807,6 +3765,12 @@ dependencies = [
"wit-bindgen-rust-macro", "wit-bindgen-rust-macro",
] ]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "wit-bindgen-core" name = "wit-bindgen-core"
version = "0.51.0" version = "0.51.0"
@@ -3856,7 +3820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.1",
"indexmap", "indexmap",
"log", "log",
"serde", "serde",
+34 -32
View File
@@ -4,7 +4,7 @@ members = ["crates/*"]
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node" package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
package.license = "MIT" package.license = "MIT"
package.edition = "2024" package.edition = "2024"
package.version = "0.3.0-alpha.3" package.version = "0.3.0-beta.3"
package.homepage = "https://bitcoinresearchkit.org" package.homepage = "https://bitcoinresearchkit.org"
package.repository = "https://github.com/bitcoinresearchkit/brk" package.repository = "https://github.com/bitcoinresearchkit/brk"
package.readme = "README.md" package.readme = "README.md"
@@ -36,45 +36,47 @@ inherits = "release"
debug = true debug = true
[workspace.dependencies] [workspace.dependencies]
aide = { version = "0.16.0-alpha.3", features = ["axum-json", "axum-query"] } aide = { version = "0.16.0-alpha.4", features = ["axum-json", "axum-query"] }
axum = { version = "0.8.8", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] } axum = { version = "0.8.9", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] }
bitcoin = { version = "0.32.8", features = ["serde"] } bitcoin = { version = "0.32.8", features = ["serde"] }
bitcoincore-rpc = "0.19.0" bitcoincore-rpc = "0.19.0"
brk_alloc = { version = "0.3.0-alpha.3", path = "crates/brk_alloc" } brk_alloc = { version = "0.3.0-beta.3", path = "crates/brk_alloc" }
brk_bencher = { version = "0.3.0-alpha.3", path = "crates/brk_bencher" } brk_bencher = { version = "0.3.0-beta.3", path = "crates/brk_bencher" }
brk_bindgen = { version = "0.3.0-alpha.3", path = "crates/brk_bindgen" } brk_bindgen = { version = "0.3.0-beta.3", path = "crates/brk_bindgen" }
brk_cli = { version = "0.3.0-alpha.3", path = "crates/brk_cli" } brk_cli = { version = "0.3.0-beta.3", path = "crates/brk_cli" }
brk_client = { version = "0.3.0-alpha.3", path = "crates/brk_client" } brk_client = { version = "0.3.0-beta.3", path = "crates/brk_client" }
brk_cohort = { version = "0.3.0-alpha.3", path = "crates/brk_cohort" } brk_cohort = { version = "0.3.0-beta.3", path = "crates/brk_cohort" }
brk_computer = { version = "0.3.0-alpha.3", path = "crates/brk_computer" } brk_computer = { version = "0.3.0-beta.3", path = "crates/brk_computer" }
brk_error = { version = "0.3.0-alpha.3", path = "crates/brk_error" } brk_error = { version = "0.3.0-beta.3", path = "crates/brk_error" }
brk_fetcher = { version = "0.3.0-alpha.3", path = "crates/brk_fetcher" } brk_fetcher = { version = "0.3.0-beta.3", path = "crates/brk_fetcher" }
brk_indexer = { version = "0.3.0-alpha.3", path = "crates/brk_indexer" } brk_indexer = { version = "0.3.0-beta.3", path = "crates/brk_indexer" }
brk_iterator = { version = "0.3.0-alpha.3", path = "crates/brk_iterator" } brk_iterator = { version = "0.3.0-beta.3", path = "crates/brk_iterator" }
brk_logger = { version = "0.3.0-alpha.3", path = "crates/brk_logger" } brk_logger = { version = "0.3.0-beta.3", path = "crates/brk_logger" }
brk_mempool = { version = "0.3.0-alpha.3", path = "crates/brk_mempool" } brk_mempool = { version = "0.3.0-beta.3", path = "crates/brk_mempool" }
brk_oracle = { version = "0.3.0-alpha.3", path = "crates/brk_oracle" } brk_oracle = { version = "0.3.0-beta.3", path = "crates/brk_oracle" }
brk_query = { version = "0.3.0-alpha.3", path = "crates/brk_query", features = ["tokio"] } brk_query = { version = "0.3.0-beta.3", path = "crates/brk_query", features = ["tokio"] }
brk_reader = { version = "0.3.0-alpha.3", path = "crates/brk_reader" } brk_reader = { version = "0.3.0-beta.3", path = "crates/brk_reader" }
brk_rpc = { version = "0.3.0-alpha.3", path = "crates/brk_rpc" } brk_rpc = { version = "0.3.0-beta.3", path = "crates/brk_rpc" }
brk_server = { version = "0.3.0-alpha.3", path = "crates/brk_server" } brk_server = { version = "0.3.0-beta.3", path = "crates/brk_server" }
brk_store = { version = "0.3.0-alpha.3", path = "crates/brk_store" } brk_store = { version = "0.3.0-beta.3", path = "crates/brk_store" }
brk_traversable = { version = "0.3.0-alpha.3", path = "crates/brk_traversable", features = ["pco", "derive"] } brk_traversable = { version = "0.3.0-beta.3", path = "crates/brk_traversable", features = ["pco", "derive"] }
brk_traversable_derive = { version = "0.3.0-alpha.3", path = "crates/brk_traversable_derive" } brk_traversable_derive = { version = "0.3.0-beta.3", path = "crates/brk_traversable_derive" }
brk_types = { version = "0.3.0-alpha.3", path = "crates/brk_types" } brk_types = { version = "0.3.0-beta.3", path = "crates/brk_types" }
brk_website = { version = "0.3.0-alpha.3", path = "crates/brk_website" } brk_website = { version = "0.3.0-beta.3", path = "crates/brk_website" }
byteview = "0.10.1" byteview = "0.10.1"
color-eyre = "0.6.5" color-eyre = "0.6.5"
corepc-client = { package = "brk-corepc-client", version = "0.11.0", features = ["client-sync"] } corepc-client = { package = "brk-corepc-client", version = "0.11.1", features = ["client-sync"] }
corepc-jsonrpc = { package = "brk-corepc-jsonrpc", version = "0.19.0", features = ["simple_http"], default-features = false } # corepc-client = { package = "brk-corepc-client", path = "../corepc/client", features = ["client-sync"] }
corepc-jsonrpc = { package = "brk-corepc-jsonrpc", version = "0.19.1", features = ["simple_http"], default-features = false }
# corepc-jsonrpc = { package = "brk-corepc-jsonrpc", path = "../corepc/jsonrpc", features = ["simple_http"], default-features = false }
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] } derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
fjall = "=3.0.4" fjall = "=3.0.4"
indexmap = { version = "2.13.0", features = ["serde"] } indexmap = { version = "2.14.0", features = ["serde"] }
jiff = { version = "0.2.23", features = ["perf-inline", "tz-system"], default-features = false } jiff = { version = "0.2.23", features = ["perf-inline", "tz-system"], default-features = false }
owo-colors = "4.3.0" owo-colors = "4.3.0"
parking_lot = "0.12.5" parking_lot = "0.12.5"
pco = "1.0.1" pco = "1.0.1"
rayon = "1.11.0" rayon = "1.12.0"
rustc-hash = "2.1.2" rustc-hash = "2.1.2"
schemars = { version = "1.2.1", features = ["indexmap2"] } schemars = { version = "1.2.1", features = ["indexmap2"] }
serde = "1.0.228" serde = "1.0.228"
@@ -82,12 +84,12 @@ serde_bytes = "0.11.19"
serde_derive = "1.0.228" serde_derive = "1.0.228"
serde_json = { version = "1.0.149", features = ["float_roundtrip", "preserve_order"] } serde_json = { version = "1.0.149", features = ["float_roundtrip", "preserve_order"] }
smallvec = "1.15.1" smallvec = "1.15.1"
tokio = { version = "1.50.0", features = ["rt-multi-thread"] } tokio = { version = "1.52.1", features = ["rt-multi-thread"] }
tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] } tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
tower-layer = "0.3" tower-layer = "0.3"
tracing = { version = "0.1", default-features = false, features = ["std"] } tracing = { version = "0.1", default-features = false, features = ["std"] }
ureq = { version = "3.3.0", features = ["json"] } ureq = { version = "3.3.0", features = ["json"] }
vecdb = { version = "0.9.3", features = ["derive", "serde_json", "pco", "schemars"] } vecdb = { version = "0.10.1", features = ["derive", "serde_json", "pco", "schemars"] }
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } # vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
[workspace.metadata.release] [workspace.metadata.release]
+2 -2
View File
@@ -8,5 +8,5 @@ homepage.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
libmimalloc-sys = { version = "0.1.44", features = ["extended"] } libmimalloc-sys = { version = "0.1.47", features = ["extended"] }
mimalloc = { version = "0.1.48", features = ["v3"] } mimalloc = { version = "0.1.50" }
+5 -2
View File
@@ -3,7 +3,10 @@
//! This module detects repeating tree structures and analyzes them //! This module detects repeating tree structures and analyzes them
//! using the bottom-up name deconstruction algorithm. //! using the bottom-up name deconstruction algorithm.
use std::collections::{BTreeMap, BTreeSet}; use std::{
cmp::Reverse,
collections::{BTreeMap, BTreeSet},
};
use brk_types::{TreeNode, extract_json_type}; use brk_types::{TreeNode, extract_json_type};
@@ -111,7 +114,7 @@ pub fn detect_structural_patterns(
// Also collects node bases for each tree path // Also collects node bases for each tree path
let node_bases = analyze_pattern_modes(tree, &mut patterns, &pattern_lookup); let node_bases = analyze_pattern_modes(tree, &mut patterns, &pattern_lookup);
patterns.sort_by(|a, b| b.fields.len().cmp(&a.fields.len())); patterns.sort_by_key(|p| Reverse(p.fields.len()));
(patterns, concrete_to_pattern, type_mappings, node_bases) (patterns, concrete_to_pattern, type_mappings, node_bases)
} }
+1 -1
View File
@@ -31,7 +31,7 @@ impl ClientConstants {
let pools = pools(); let pools = pools();
let mut sorted_pools: Vec<_> = pools.iter().collect(); let mut sorted_pools: Vec<_> = pools.iter().collect();
sorted_pools.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); sorted_pools.sort_by_key(|p| p.name.to_lowercase());
let pool_map: BTreeMap<PoolSlug, &'static str> = let pool_map: BTreeMap<PoolSlug, &'static str> =
sorted_pools.iter().map(|p| (p.slug(), p.name)).collect(); sorted_pools.iter().map(|p| (p.slug(), p.name)).collect();
@@ -69,16 +69,33 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
.unwrap(); .unwrap();
} }
writeln!(
output,
" * @param {{{{ signal?: AbortSignal, onUpdate?: (value: {}) => void }}}} [options]",
return_type
)
.unwrap();
writeln!(output, " * @returns {{Promise<{}>}}", return_type).unwrap(); writeln!(output, " * @returns {{Promise<{}>}}", return_type).unwrap();
writeln!(output, " */").unwrap(); writeln!(output, " */").unwrap();
let params = build_method_params(endpoint); let params = build_method_params(endpoint);
writeln!(output, " async {}({}) {{", method_name, params).unwrap(); let params_with_opts = if params.is_empty() {
"{ signal, onUpdate } = {}".to_string()
} else {
format!("{}, {{ signal, onUpdate }} = {{}}", params)
};
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
let path = build_path_template(&endpoint.path, &endpoint.path_params); let path = build_path_template(&endpoint.path, &endpoint.path_params);
let fetch_call = if endpoint.returns_json() {
"this.getJson(path, { signal, onUpdate })"
} else {
"this.getText(path, { signal, onUpdate })"
};
if endpoint.query_params.is_empty() { if endpoint.query_params.is_empty() {
writeln!(output, " return this.getJson(`{}`);", path).unwrap(); writeln!(output, " const path = `{}`;", path).unwrap();
} else { } else {
writeln!(output, " const params = new URLSearchParams();").unwrap(); writeln!(output, " const params = new URLSearchParams();").unwrap();
for param in &endpoint.query_params { for param in &endpoint.query_params {
@@ -106,17 +123,13 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
path path
) )
.unwrap(); .unwrap();
if endpoint.supports_csv {
writeln!(output, " if (format === 'csv') {{").unwrap();
writeln!(output, " return this.getText(path);").unwrap();
writeln!(output, " }}").unwrap();
writeln!(output, " return this.getJson(path);").unwrap();
} else {
writeln!(output, " return this.getJson(path);").unwrap();
}
} }
if endpoint.supports_csv {
writeln!(output, " if (format === 'csv') return this.getText(path, {{ signal, onUpdate }});").unwrap();
}
writeln!(output, " return {};", fetch_call).unwrap();
writeln!(output, " }}\n").unwrap(); writeln!(output, " }}\n").unwrap();
} }
} }
@@ -404,23 +404,28 @@ class BrkClientBase {{
/** /**
* @param {{string}} path * @param {{string}} path
* @param {{{{ signal?: AbortSignal }}}} [options]
* @returns {{Promise<Response>}} * @returns {{Promise<Response>}}
*/ */
async get(path) {{ async get(path, {{ signal }} = {{}}) {{
const url = `${{this.baseUrl}}${{path}}`; const url = `${{this.baseUrl}}${{path}}`;
const res = await fetch(url, {{ signal: AbortSignal.timeout(this.timeout) }}); const signals = [AbortSignal.timeout(this.timeout)];
if (signal) signals.push(signal);
const res = await fetch(url, {{ signal: AbortSignal.any(signals) }});
if (!res.ok) throw new BrkError(`HTTP ${{res.status}}: ${{url}}`, res.status); if (!res.ok) throw new BrkError(`HTTP ${{res.status}}: ${{url}}`, res.status);
return res; return res;
}} }}
/** /**
* Make a GET request - races cache vs network, first to resolve calls onUpdate * Make a GET request - races cache vs network, first to resolve calls onUpdate.
* Shared implementation backing `getJson` and `getText`.
* @template T * @template T
* @param {{string}} path * @param {{string}} path
* @param {{(value: T) => void}} [onUpdate] - Called when data is available (may be called twice: cache then network) * @param {{(res: Response) => Promise<T>}} parse - Response body reader
* @param {{{{ onUpdate?: (value: T) => void, signal?: AbortSignal }}}} [options]
* @returns {{Promise<T>}} * @returns {{Promise<T>}}
*/ */
async getJson(path, onUpdate) {{ async _getCached(path, parse, {{ onUpdate, signal }} = {{}}) {{
const url = `${{this.baseUrl}}${{path}}`; const url = `${{this.baseUrl}}${{path}}`;
const cache = this._cache ?? await this._cachePromise; const cache = this._cache ?? await this._cachePromise;
@@ -432,51 +437,61 @@ class BrkClientBase {{
const cachePromise = cache?.match(url).then(async (res) => {{ const cachePromise = cache?.match(url).then(async (res) => {{
cachedRes = res ?? null; cachedRes = res ?? null;
if (!res) return null; if (!res) return null;
const json = _addCamelGetters(await res.json()); const value = await parse(res);
if (!resolved && onUpdate) {{ if (!resolved && onUpdate) {{
resolved = true; resolved = true;
onUpdate(json); onUpdate(value);
}} }}
return json; return value;
}}); }});
const networkPromise = this.get(path).then(async (res) => {{ const networkPromise = this.get(path, {{ signal }}).then(async (res) => {{
const cloned = res.clone(); const cloned = res.clone();
const json = _addCamelGetters(await res.json()); const value = await parse(res);
// Skip update if ETag matches and cache already delivered // Skip update if ETag matches and cache already delivered
if (cachedRes?.headers.get('ETag') === res.headers.get('ETag')) {{ if (cachedRes?.headers.get('ETag') === res.headers.get('ETag')) {{
if (!resolved && onUpdate) {{ if (!resolved && onUpdate) {{
resolved = true; resolved = true;
onUpdate(json); onUpdate(value);
}} }}
return json; return value;
}} }}
resolved = true; resolved = true;
if (onUpdate) {{ if (onUpdate) onUpdate(value);
onUpdate(json);
}}
if (cache) _runIdle(() => cache.put(url, cloned)); if (cache) _runIdle(() => cache.put(url, cloned));
return json; return value;
}}); }});
try {{ try {{
return await networkPromise; return await networkPromise;
}} catch (e) {{ }} catch (e) {{
// Network failed - wait for cache // Network failed - wait for cache
const cachedJson = await cachePromise?.catch(() => null); const cachedValue = await cachePromise?.catch(() => null);
if (cachedJson) return cachedJson; if (cachedValue != null) return cachedValue;
throw e; throw e;
}} }}
}} }}
/** /**
* Make a GET request and return raw text (for CSV responses) * Make a GET request expecting a JSON response. Cached and supports `onUpdate`.
* @template T
* @param {{string}} path * @param {{string}} path
* @param {{{{ onUpdate?: (value: T) => void, signal?: AbortSignal }}}} [options]
* @returns {{Promise<T>}}
*/
getJson(path, options) {{
return this._getCached(path, async (res) => _addCamelGetters(await res.json()), options);
}}
/**
* Make a GET request expecting a text response (text/plain, text/csv, ...).
* Cached and supports `onUpdate`, same as `getJson`.
* @param {{string}} path
* @param {{{{ onUpdate?: (value: string) => void, signal?: AbortSignal }}}} [options]
* @returns {{Promise<string>}} * @returns {{Promise<string>}}
*/ */
async getText(path) {{ getText(path, options) {{
const res = await this.get(path); return this._getCached(path, (res) => res.text(), options);
return res.text();
}} }}
/** /**
@@ -488,7 +503,7 @@ class BrkClientBase {{
*/ */
async _fetchSeriesData(path, onUpdate) {{ async _fetchSeriesData(path, onUpdate) {{
const wrappedOnUpdate = onUpdate ? (/** @type {{SeriesData<T>}} */ raw) => onUpdate(_wrapSeriesData(raw)) : undefined; const wrappedOnUpdate = onUpdate ? (/** @type {{SeriesData<T>}} */ raw) => onUpdate(_wrapSeriesData(raw)) : undefined;
const raw = await this.getJson(path, wrappedOnUpdate); const raw = await this.getJson(path, {{ onUpdate: wrappedOnUpdate }});
return _wrapSeriesData(raw); return _wrapSeriesData(raw);
}} }}
}} }}
@@ -108,15 +108,14 @@ pub fn generate_main_client(
writeln!(output, " constructor(options) {{").unwrap(); writeln!(output, " constructor(options) {{").unwrap();
writeln!(output, " super(options);").unwrap(); writeln!(output, " super(options);").unwrap();
writeln!(output, " /** @type {{SeriesTree}} */").unwrap(); writeln!(output, " /** @type {{SeriesTree}} */").unwrap();
writeln!(output, " this.series = this._buildTree('');").unwrap(); writeln!(output, " this.series = this._buildTree();").unwrap();
writeln!(output, " }}\n").unwrap(); writeln!(output, " }}\n").unwrap();
writeln!(output, " /**").unwrap(); writeln!(output, " /**").unwrap();
writeln!(output, " * @private").unwrap(); writeln!(output, " * @private").unwrap();
writeln!(output, " * @param {{string}} basePath").unwrap();
writeln!(output, " * @returns {{SeriesTree}}").unwrap(); writeln!(output, " * @returns {{SeriesTree}}").unwrap();
writeln!(output, " */").unwrap(); writeln!(output, " */").unwrap();
writeln!(output, " _buildTree(basePath) {{").unwrap(); writeln!(output, " _buildTree() {{").unwrap();
writeln!(output, " return {{").unwrap(); writeln!(output, " return {{").unwrap();
let mut generated = BTreeSet::new(); let mut generated = BTreeSet::new();
generate_tree_initializer( generate_tree_initializer(
+1054 -540
View File
File diff suppressed because it is too large Load Diff
+63 -2
View File
@@ -1,16 +1,34 @@
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use brk_traversable::Traversable;
use brk_types::OutputType; use brk_types::OutputType;
use rayon::prelude::*;
use super::{SpendableType, UnspendableType}; use super::{Filter, SpendableType, UnspendableType};
#[derive(Default, Clone, Debug)] pub const OP_RETURN: &str = "op_return";
#[derive(Default, Clone, Debug, Traversable)]
pub struct ByType<T> { pub struct ByType<T> {
#[traversable(flatten)]
pub spendable: SpendableType<T>, pub spendable: SpendableType<T>,
#[traversable(flatten)]
pub unspendable: UnspendableType<T>, pub unspendable: UnspendableType<T>,
} }
impl<T> ByType<T> { impl<T> ByType<T> {
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
where
F: FnMut(Filter, &'static str) -> Result<T, E>,
{
Ok(Self {
spendable: SpendableType::try_new(&mut create)?,
unspendable: UnspendableType {
op_return: create(Filter::Type(OutputType::OpReturn), OP_RETURN)?,
},
})
}
pub fn get(&self, output_type: OutputType) -> &T { pub fn get(&self, output_type: OutputType) -> &T {
match output_type { match output_type {
OutputType::P2PK65 => &self.spendable.p2pk65, OutputType::P2PK65 => &self.spendable.p2pk65,
@@ -44,6 +62,49 @@ impl<T> ByType<T> {
OutputType::OpReturn => &mut self.unspendable.op_return, OutputType::OpReturn => &mut self.unspendable.op_return,
} }
} }
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.spendable
.iter()
.chain(std::iter::once(&self.unspendable.op_return))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.spendable
.iter_mut()
.chain(std::iter::once(&mut self.unspendable.op_return))
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut T>
where
T: Send + Sync,
{
let Self {
spendable,
unspendable,
} = self;
spendable
.par_iter_mut()
.chain([&mut unspendable.op_return].into_par_iter())
}
pub fn iter_typed(&self) -> impl Iterator<Item = (OutputType, &T)> {
self.spendable
.iter_typed()
.chain(std::iter::once((
OutputType::OpReturn,
&self.unspendable.op_return,
)))
}
pub fn iter_typed_mut(&mut self) -> impl Iterator<Item = (OutputType, &mut T)> {
self.spendable
.iter_typed_mut()
.chain(std::iter::once((
OutputType::OpReturn,
&mut self.unspendable.op_return,
)))
}
} }
impl<T> Add for ByType<T> impl<T> Add for ByType<T>
+17
View File
@@ -116,6 +116,23 @@ impl<T> SpendableType<T> {
}) })
} }
pub fn get(&self, output_type: OutputType) -> &T {
match output_type {
OutputType::P2PK65 => &self.p2pk65,
OutputType::P2PK33 => &self.p2pk33,
OutputType::P2PKH => &self.p2pkh,
OutputType::P2MS => &self.p2ms,
OutputType::P2SH => &self.p2sh,
OutputType::P2WPKH => &self.p2wpkh,
OutputType::P2WSH => &self.p2wsh,
OutputType::P2TR => &self.p2tr,
OutputType::P2A => &self.p2a,
OutputType::Unknown => &self.unknown,
OutputType::Empty => &self.empty,
_ => unreachable!(),
}
}
pub fn get_mut(&mut self, output_type: OutputType) -> &mut T { pub fn get_mut(&mut self, output_type: OutputType) -> &mut T {
match output_type { match output_type {
OutputType::P2PK65 => &mut self.p2pk65, OutputType::P2PK65 => &mut self.p2pk65,
@@ -7,7 +7,7 @@ use crate::{
indexes, indexes,
internal::{ internal::{
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h, BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
CachedWindowStarts, ConstantVecs, PerBlockCumulativeRolling, Windows, ConstantVecs, PerBlockCumulativeRolling, WindowStartVec, Windows,
}, },
}; };
@@ -16,7 +16,7 @@ impl Vecs {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
target: Windows { target: Windows {
@@ -30,7 +30,7 @@ impl Vecs {
self.blocks_to_retarget.height.compute_transform( self.blocks_to_retarget.height.compute_transform(
starting_indexes.height, starting_indexes.height,
&indexes.height.identity, &indexes.height.epoch,
|(h, ..)| (h, StoredU32::from(h.left_before_next_diff_adj())), |(h, ..)| (h, StoredU32::from(h.left_before_next_diff_adj())),
exit, exit,
)?; )?;
@@ -20,10 +20,10 @@ impl Vecs {
) -> Result<Self> { ) -> Result<Self> {
let v2 = Version::TWO; let v2 = Version::TWO;
let hashrate = LazyPerBlock::from_height_source::<DifficultyToHashF64>( let hashrate = LazyPerBlock::from_height_source::<DifficultyToHashF64, _>(
"difficulty_hashrate", "difficulty_hashrate",
version, version,
indexer.vecs.blocks.difficulty.read_only_boxed_clone(), indexer.vecs.blocks.difficulty.read_only_clone(),
indexes, indexes,
); );
@@ -40,7 +40,7 @@ impl Vecs {
Ok(Self { Ok(Self {
value: Resolutions::forced_import( value: Resolutions::forced_import(
"difficulty", "difficulty",
indexer.vecs.blocks.difficulty.read_only_boxed_clone(), indexer.vecs.blocks.difficulty.read_only_clone(),
version, version,
indexes, indexes,
), ),
@@ -21,7 +21,7 @@ impl Vecs {
self.blocks_to_halving.height.compute_transform( self.blocks_to_halving.height.compute_transform(
starting_indexes.height, starting_indexes.height,
&indexes.height.identity, &indexes.height.halving,
|(h, ..)| (h, StoredU32::from(h.left_before_next_halving())), |(h, ..)| (h, StoredU32::from(h.left_before_next_halving())),
exit, exit,
)?; )?;
+5 -5
View File
@@ -24,11 +24,11 @@ impl Vecs {
let version = parent_version; let version = parent_version;
let lookback = LookbackVecs::forced_import(&db, version)?; let lookback = LookbackVecs::forced_import(&db, version)?;
let cached_starts = &lookback.cached_window_starts; let cached_starts = lookback.cached_window_starts();
let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?; let count = CountVecs::forced_import(&db, version, indexes, &cached_starts)?;
let interval = IntervalVecs::forced_import(&db, version, indexes, cached_starts)?; let interval = IntervalVecs::forced_import(&db, version, indexes, &cached_starts)?;
let size = SizeVecs::forced_import(&db, version, indexes, cached_starts)?; let size = SizeVecs::forced_import(&db, version, indexes, &cached_starts)?;
let weight = WeightVecs::forced_import(&db, version, indexes, cached_starts, &size)?; let weight = WeightVecs::forced_import(&db, version, indexes, &cached_starts, &size)?;
let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?; let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?;
let halving = HalvingVecs::forced_import(&db, version, indexes)?; let halving = HalvingVecs::forced_import(&db, version, indexes)?;
@@ -5,7 +5,7 @@ use vecdb::Database;
use super::Vecs; use super::Vecs;
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, PerBlockRollingAverage}, internal::{PerBlockRollingAverage, WindowStartVec, Windows},
}; };
impl Vecs { impl Vecs {
@@ -13,7 +13,7 @@ impl Vecs {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let interval = PerBlockRollingAverage::forced_import( let interval = PerBlockRollingAverage::forced_import(
db, db,
@@ -8,5 +8,5 @@ use crate::internal::PerBlockRollingAverage;
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct Vecs<M: StorageMode = Rw>( pub struct Vecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub PerBlockRollingAverage<Timestamp, M>, #[traversable(flatten)] pub PerBlockRollingAverage<Timestamp, Timestamp, M>,
); );
+28 -29
View File
@@ -8,17 +8,15 @@ use vecdb::{
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, WindowStarts, Windows}, internal::{WindowStartVec, WindowStarts, Windows},
}; };
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
pub cached_window_starts: CachedWindowStarts,
pub _1h: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _1h: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _24h: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 1d pub _24h: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 1d
pub _3d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _3d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _1w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 7d pub _1w: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 7d
pub _8d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _8d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _9d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _9d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _12d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _12d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
@@ -26,7 +24,7 @@ pub struct Vecs<M: StorageMode = Rw> {
pub _2w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 14d pub _2w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 14d
pub _21d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _21d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _26d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _26d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _1m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 30d pub _1m: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 30d
pub _34d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _34d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _55d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _55d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _2m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 60d pub _2m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 60d
@@ -43,7 +41,7 @@ pub struct Vecs<M: StorageMode = Rw> {
pub _9m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 270d pub _9m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 270d
pub _350d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _350d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _12m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 360d pub _12m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 360d
pub _1y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 365d pub _1y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 365d
pub _14m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 420d pub _14m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 420d
pub _2y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 730d pub _2y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 730d
pub _26m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 780d pub _26m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 780d
@@ -106,19 +104,11 @@ impl Vecs {
let _14y = ImportableVec::forced_import(db, "height_14y_ago", version)?; let _14y = ImportableVec::forced_import(db, "height_14y_ago", version)?;
let _26y = ImportableVec::forced_import(db, "height_26y_ago", version)?; let _26y = ImportableVec::forced_import(db, "height_26y_ago", version)?;
let cached_window_starts = CachedWindowStarts(Windows {
_24h: CachedVec::new(&_24h),
_1w: CachedVec::new(&_1w),
_1m: CachedVec::new(&_1m),
_1y: CachedVec::new(&_1y),
});
Ok(Self { Ok(Self {
cached_window_starts,
_1h, _1h,
_24h, _24h: CachedVec::wrap(_24h),
_3d, _3d,
_1w, _1w: CachedVec::wrap(_1w),
_8d, _8d,
_9d, _9d,
_12d, _12d,
@@ -126,7 +116,7 @@ impl Vecs {
_2w, _2w,
_21d, _21d,
_26d, _26d,
_1m, _1m: CachedVec::wrap(_1m),
_34d, _34d,
_55d, _55d,
_2m, _2m,
@@ -143,7 +133,7 @@ impl Vecs {
_9m, _9m,
_350d, _350d,
_12m, _12m,
_1y, _1y: CachedVec::wrap(_1y),
_14m, _14m,
_2y, _2y,
_26m, _26m,
@@ -161,8 +151,8 @@ impl Vecs {
}) })
} }
pub fn window_starts(&self) -> WindowStarts<'_> { pub fn cached_window_starts(&self) -> Windows<&WindowStartVec> {
WindowStarts { Windows {
_24h: &self._24h, _24h: &self._24h,
_1w: &self._1w, _1w: &self._1w,
_1m: &self._1m, _1m: &self._1m,
@@ -170,11 +160,20 @@ impl Vecs {
} }
} }
pub fn window_starts(&self) -> WindowStarts<'_> {
WindowStarts {
_24h: &self._24h.inner,
_1w: &self._1w.inner,
_1m: &self._1m.inner,
_1y: &self._1y.inner,
}
}
pub fn start_vec(&self, days: usize) -> &EagerVec<PcoVec<Height, Height>> { pub fn start_vec(&self, days: usize) -> &EagerVec<PcoVec<Height, Height>> {
match days { match days {
1 => &self._24h, 1 => &self._24h.inner,
3 => &self._3d, 3 => &self._3d,
7 => &self._1w, 7 => &self._1w.inner,
8 => &self._8d, 8 => &self._8d,
9 => &self._9d, 9 => &self._9d,
12 => &self._12d, 12 => &self._12d,
@@ -182,7 +181,7 @@ impl Vecs {
14 => &self._2w, 14 => &self._2w,
21 => &self._21d, 21 => &self._21d,
26 => &self._26d, 26 => &self._26d,
30 => &self._1m, 30 => &self._1m.inner,
34 => &self._34d, 34 => &self._34d,
55 => &self._55d, 55 => &self._55d,
60 => &self._2m, 60 => &self._2m,
@@ -199,7 +198,7 @@ impl Vecs {
270 => &self._9m, 270 => &self._9m,
350 => &self._350d, 350 => &self._350d,
360 => &self._12m, 360 => &self._12m,
365 => &self._1y, 365 => &self._1y.inner,
420 => &self._14m, 420 => &self._14m,
730 => &self._2y, 730 => &self._2y,
780 => &self._26m, 780 => &self._26m,
@@ -225,9 +224,9 @@ impl Vecs {
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.compute_rolling_start_hours(indexes, starting_indexes, exit, 1, |s| &mut s._1h)?; self.compute_rolling_start_hours(indexes, starting_indexes, exit, 1, |s| &mut s._1h)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 1, |s| &mut s._24h)?; self.compute_rolling_start(indexes, starting_indexes, exit, 1, |s| &mut s._24h.inner)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 3, |s| &mut s._3d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 3, |s| &mut s._3d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 7, |s| &mut s._1w)?; self.compute_rolling_start(indexes, starting_indexes, exit, 7, |s| &mut s._1w.inner)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 8, |s| &mut s._8d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 8, |s| &mut s._8d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 9, |s| &mut s._9d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 9, |s| &mut s._9d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 12, |s| &mut s._12d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 12, |s| &mut s._12d)?;
@@ -235,7 +234,7 @@ impl Vecs {
self.compute_rolling_start(indexes, starting_indexes, exit, 14, |s| &mut s._2w)?; self.compute_rolling_start(indexes, starting_indexes, exit, 14, |s| &mut s._2w)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 21, |s| &mut s._21d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 21, |s| &mut s._21d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 26, |s| &mut s._26d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 26, |s| &mut s._26d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 30, |s| &mut s._1m)?; self.compute_rolling_start(indexes, starting_indexes, exit, 30, |s| &mut s._1m.inner)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 34, |s| &mut s._34d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 34, |s| &mut s._34d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 55, |s| &mut s._55d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 55, |s| &mut s._55d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 60, |s| &mut s._2m)?; self.compute_rolling_start(indexes, starting_indexes, exit, 60, |s| &mut s._2m)?;
@@ -252,7 +251,7 @@ impl Vecs {
self.compute_rolling_start(indexes, starting_indexes, exit, 270, |s| &mut s._9m)?; self.compute_rolling_start(indexes, starting_indexes, exit, 270, |s| &mut s._9m)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 350, |s| &mut s._350d)?; self.compute_rolling_start(indexes, starting_indexes, exit, 350, |s| &mut s._350d)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 360, |s| &mut s._12m)?; self.compute_rolling_start(indexes, starting_indexes, exit, 360, |s| &mut s._12m)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 365, |s| &mut s._1y)?; self.compute_rolling_start(indexes, starting_indexes, exit, 365, |s| &mut s._1y.inner)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 420, |s| &mut s._14m)?; self.compute_rolling_start(indexes, starting_indexes, exit, 420, |s| &mut s._14m)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 730, |s| &mut s._2y)?; self.compute_rolling_start(indexes, starting_indexes, exit, 730, |s| &mut s._2y)?;
self.compute_rolling_start(indexes, starting_indexes, exit, 780, |s| &mut s._26m)?; self.compute_rolling_start(indexes, starting_indexes, exit, 780, |s| &mut s._26m)?;
@@ -5,7 +5,7 @@ use vecdb::Database;
use super::Vecs; use super::Vecs;
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, PerBlockFull, PerBlockRolling}, internal::{PerBlockFull, PerBlockRolling, WindowStartVec, Windows},
}; };
impl Vecs { impl Vecs {
@@ -13,7 +13,7 @@ impl Vecs {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
vbytes: PerBlockFull::forced_import( vbytes: PerBlockFull::forced_import(
@@ -6,7 +6,7 @@ use super::Vecs;
use crate::{ use crate::{
blocks::SizeVecs, blocks::SizeVecs,
indexes, indexes,
internal::{CachedWindowStarts, LazyPerBlockRolling, PercentVec, VBytesToWeight}, internal::{LazyPerBlockRolling, PercentVec, VBytesToWeight, WindowStartVec, Windows},
}; };
impl Vecs { impl Vecs {
@@ -14,7 +14,7 @@ impl Vecs {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
size: &SizeVecs, size: &SizeVecs,
) -> Result<Self> { ) -> Result<Self> {
let weight = LazyPerBlockRolling::from_per_block_full::<VBytesToWeight>( let weight = LazyPerBlockRolling::from_per_block_full::<VBytesToWeight>(
@@ -6,7 +6,7 @@ use super::Vecs;
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
CachedWindowStarts, LazyPerBlock, OneMinusF64, PerBlock, PerBlockCumulativeRolling, LazyPerBlock, OneMinusF64, PerBlock, PerBlockCumulativeRolling, WindowStartVec, Windows,
}, },
}; };
@@ -15,7 +15,7 @@ impl Vecs {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let liveliness = PerBlock::forced_import(db, "liveliness", version, indexes)?; let liveliness = PerBlock::forced_import(db, "liveliness", version, indexes)?;
+2 -2
View File
@@ -13,14 +13,14 @@ use super::{
ValueVecs, Vecs, ValueVecs, Vecs,
}; };
use crate::internal::CachedWindowStarts; use crate::internal::{WindowStartVec, Windows};
impl Vecs { impl Vecs {
pub(crate) fn forced_import( pub(crate) fn forced_import(
parent_path: &Path, parent_path: &Path,
parent_version: Version, parent_version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let db = open_db(parent_path, DB_NAME, 250_000)?; let db = open_db(parent_path, DB_NAME, 250_000)?;
let version = parent_version; let version = parent_version;
@@ -23,7 +23,7 @@ impl Vecs {
self.hodl_bank.compute_cumulative_transformed_binary( self.hodl_bank.compute_cumulative_transformed_binary(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_usd, &prices.spot.usd.height,
&self.vocdd_median_1y, &self.vocdd_median_1y,
|price, median| StoredF64::from(f64::from(price) - f64::from(median)), |price, median| StoredF64::from(f64::from(price) - f64::from(median)),
exit, exit,
@@ -31,7 +31,7 @@ impl Vecs {
self.value.height.compute_divide( self.value.height.compute_divide(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_usd, &prices.spot.usd.height,
&self.hodl_bank, &self.hodl_bank,
exit, exit,
)?; )?;
@@ -24,7 +24,7 @@ impl Vecs {
.compute(starting_indexes.height, exit, |vec| { .compute(starting_indexes.height, exit, |vec| {
vec.compute_multiply( vec.compute_multiply(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_usd, &prices.spot.usd.height,
&coinblocks_destroyed.block, &coinblocks_destroyed.block,
exit, exit,
)?; )?;
@@ -34,7 +34,7 @@ impl Vecs {
self.created.compute(starting_indexes.height, exit, |vec| { self.created.compute(starting_indexes.height, exit, |vec| {
vec.compute_multiply( vec.compute_multiply(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_usd, &prices.spot.usd.height,
&activity.coinblocks_created.block, &activity.coinblocks_created.block,
exit, exit,
)?; )?;
@@ -44,7 +44,7 @@ impl Vecs {
self.stored.compute(starting_indexes.height, exit, |vec| { self.stored.compute(starting_indexes.height, exit, |vec| {
vec.compute_multiply( vec.compute_multiply(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_usd, &prices.spot.usd.height,
&activity.coinblocks_stored.block, &activity.coinblocks_stored.block,
exit, exit,
)?; )?;
@@ -57,7 +57,7 @@ impl Vecs {
self.vocdd.compute(starting_indexes.height, exit, |vec| { self.vocdd.compute(starting_indexes.height, exit, |vec| {
vec.compute_transform3( vec.compute_transform3(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_usd, &prices.spot.usd.height,
&coindays_destroyed.block, &coindays_destroyed.block,
circulating_supply, circulating_supply,
|(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| { |(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| {
@@ -5,7 +5,7 @@ use vecdb::Database;
use super::Vecs; use super::Vecs;
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, PerBlockCumulativeRolling}, internal::{PerBlockCumulativeRolling, WindowStartVec, Windows},
}; };
impl Vecs { impl Vecs {
@@ -13,7 +13,7 @@ impl Vecs {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
destroyed: PerBlockCumulativeRolling::forced_import( destroyed: PerBlockCumulativeRolling::forced_import(
@@ -7,19 +7,20 @@
//! | `receiving` | Unique addresses that received this block | //! | `receiving` | Unique addresses that received this block |
//! | `sending` | Unique addresses that sent this block | //! | `sending` | Unique addresses that sent this block |
//! | `reactivated` | Addresses that were empty and now have funds | //! | `reactivated` | Addresses that were empty and now have funds |
//! | `both` | Addresses that both sent AND received same block | //! | `bidirectional` | Addresses that both sent AND received in same block |
//! | `active` | Distinct addresses involved (sent received) |
use brk_cohort::ByAddrType; use brk_cohort::ByAddrType;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, StoredU32, Version}; use brk_types::{Height, StoredU32, StoredU64, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use rayon::prelude::*; use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec}; use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, PerBlockRollingAverage}, internal::{PerBlockRollingAverage, WindowStartVec, Windows},
}; };
/// Per-block activity counts - reset each block. /// Per-block activity counts - reset each block.
@@ -28,7 +29,7 @@ pub struct BlockActivityCounts {
pub reactivated: u32, pub reactivated: u32,
pub sending: u32, pub sending: u32,
pub receiving: u32, pub receiving: u32,
pub both: u32, pub bidirectional: u32,
} }
impl BlockActivityCounts { impl BlockActivityCounts {
@@ -56,7 +57,7 @@ impl AddrTypeToActivityCounts {
total.reactivated += counts.reactivated; total.reactivated += counts.reactivated;
total.sending += counts.sending; total.sending += counts.sending;
total.receiving += counts.receiving; total.receiving += counts.receiving;
total.both += counts.both; total.bidirectional += counts.bidirectional;
} }
total total
} }
@@ -65,45 +66,61 @@ impl AddrTypeToActivityCounts {
/// Activity count vectors for a single category (e.g., one address type or "all"). /// Activity count vectors for a single category (e.g., one address type or "all").
#[derive(Traversable)] #[derive(Traversable)]
pub struct ActivityCountVecs<M: StorageMode = Rw> { pub struct ActivityCountVecs<M: StorageMode = Rw> {
pub reactivated: PerBlockRollingAverage<StoredU32, M>, pub reactivated: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub sending: PerBlockRollingAverage<StoredU32, M>, pub sending: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub receiving: PerBlockRollingAverage<StoredU32, M>, pub receiving: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub both: PerBlockRollingAverage<StoredU32, M>, pub bidirectional: PerBlockRollingAverage<StoredU32, StoredU64, M>,
/// Distinct addresses involved in this block (sent received),
/// computed at push time as `sending + receiving - bidirectional`
/// via inclusion-exclusion. For per-type instances this is
/// per-type. For the `all` aggregate it's the cross-type total.
pub active: PerBlockRollingAverage<StoredU32, StoredU64, M>,
} }
impl ActivityCountVecs { impl ActivityCountVecs {
/// `prefix` is prepended to each field's disk name. Use `""` for the
/// "all" aggregate and `"{type}_"` for per-address-type instances.
/// Field names are suffixed with `_addrs` so the final disk series
/// are e.g. `active_addrs`, `p2tr_bidirectional_addrs`.
pub(crate) fn forced_import( pub(crate) fn forced_import(
db: &Database, db: &Database,
name: &str, prefix: &str,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
reactivated: PerBlockRollingAverage::forced_import( reactivated: PerBlockRollingAverage::forced_import(
db, db,
&format!("{name}_reactivated"), &format!("{prefix}reactivated_addrs"),
version, version,
indexes, indexes,
cached_starts, cached_starts,
)?, )?,
sending: PerBlockRollingAverage::forced_import( sending: PerBlockRollingAverage::forced_import(
db, db,
&format!("{name}_sending"), &format!("{prefix}sending_addrs"),
version, version,
indexes, indexes,
cached_starts, cached_starts,
)?, )?,
receiving: PerBlockRollingAverage::forced_import( receiving: PerBlockRollingAverage::forced_import(
db, db,
&format!("{name}_receiving"), &format!("{prefix}receiving_addrs"),
version, version,
indexes, indexes,
cached_starts, cached_starts,
)?, )?,
both: PerBlockRollingAverage::forced_import( bidirectional: PerBlockRollingAverage::forced_import(
db, db,
&format!("{name}_both"), &format!("{prefix}bidirectional_addrs"),
version,
indexes,
cached_starts,
)?,
active: PerBlockRollingAverage::forced_import(
db,
&format!("{prefix}active_addrs"),
version, version,
indexes, indexes,
cached_starts, cached_starts,
@@ -117,7 +134,8 @@ impl ActivityCountVecs {
.len() .len()
.min(self.sending.block.len()) .min(self.sending.block.len())
.min(self.receiving.block.len()) .min(self.receiving.block.len())
.min(self.both.block.len()) .min(self.bidirectional.block.len())
.min(self.active.block.len())
} }
pub(crate) fn par_iter_height_mut( pub(crate) fn par_iter_height_mut(
@@ -125,9 +143,10 @@ impl ActivityCountVecs {
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> { ) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
[ [
&mut self.reactivated.block as &mut dyn AnyStoredVec, &mut self.reactivated.block as &mut dyn AnyStoredVec,
&mut self.sending.block as &mut dyn AnyStoredVec, &mut self.sending.block,
&mut self.receiving.block as &mut dyn AnyStoredVec, &mut self.receiving.block,
&mut self.both.block as &mut dyn AnyStoredVec, &mut self.bidirectional.block,
&mut self.active.block,
] ]
.into_par_iter() .into_par_iter()
} }
@@ -136,7 +155,8 @@ impl ActivityCountVecs {
self.reactivated.block.reset()?; self.reactivated.block.reset()?;
self.sending.block.reset()?; self.sending.block.reset()?;
self.receiving.block.reset()?; self.receiving.block.reset()?;
self.both.block.reset()?; self.bidirectional.block.reset()?;
self.active.block.reset()?;
Ok(()) Ok(())
} }
@@ -145,14 +165,19 @@ impl ActivityCountVecs {
self.reactivated.block.push(counts.reactivated.into()); self.reactivated.block.push(counts.reactivated.into());
self.sending.block.push(counts.sending.into()); self.sending.block.push(counts.sending.into());
self.receiving.block.push(counts.receiving.into()); self.receiving.block.push(counts.receiving.into());
self.both.block.push(counts.both.into()); self.bidirectional
.block
.push(counts.bidirectional.into());
let active = counts.sending + counts.receiving - counts.bidirectional;
self.active.block.push(active.into());
} }
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> { pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
self.reactivated.compute_rest(max_from, exit)?; self.reactivated.compute_rest(max_from, exit)?;
self.sending.compute_rest(max_from, exit)?; self.sending.compute_rest(max_from, exit)?;
self.receiving.compute_rest(max_from, exit)?; self.receiving.compute_rest(max_from, exit)?;
self.both.compute_rest(max_from, exit)?; self.bidirectional.compute_rest(max_from, exit)?;
self.active.compute_rest(max_from, exit)?;
Ok(()) Ok(())
} }
} }
@@ -171,16 +196,15 @@ impl From<ByAddrType<ActivityCountVecs>> for AddrTypeToActivityCountVecs {
impl AddrTypeToActivityCountVecs { impl AddrTypeToActivityCountVecs {
pub(crate) fn forced_import( pub(crate) fn forced_import(
db: &Database, db: &Database,
name: &str,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self::from(ByAddrType::<ActivityCountVecs>::new_with_name( Ok(Self::from(ByAddrType::<ActivityCountVecs>::new_with_name(
|type_name| { |type_name| {
ActivityCountVecs::forced_import( ActivityCountVecs::forced_import(
db, db,
&format!("{type_name}_{name}"), &format!("{type_name}_"),
version, version,
indexes, indexes,
cached_starts, cached_starts,
@@ -205,7 +229,8 @@ impl AddrTypeToActivityCountVecs {
vecs.push(&mut type_vecs.reactivated.block); vecs.push(&mut type_vecs.reactivated.block);
vecs.push(&mut type_vecs.sending.block); vecs.push(&mut type_vecs.sending.block);
vecs.push(&mut type_vecs.receiving.block); vecs.push(&mut type_vecs.receiving.block);
vecs.push(&mut type_vecs.both.block); vecs.push(&mut type_vecs.bidirectional.block);
vecs.push(&mut type_vecs.active.block);
} }
vecs.into_par_iter() vecs.into_par_iter()
} }
@@ -243,16 +268,14 @@ pub struct AddrActivityVecs<M: StorageMode = Rw> {
impl AddrActivityVecs { impl AddrActivityVecs {
pub(crate) fn forced_import( pub(crate) fn forced_import(
db: &Database, db: &Database,
name: &str,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
all: ActivityCountVecs::forced_import(db, name, version, indexes, cached_starts)?, all: ActivityCountVecs::forced_import(db, "", version, indexes, cached_starts)?,
by_addr_type: AddrTypeToActivityCountVecs::forced_import( by_addr_type: AddrTypeToActivityCountVecs::forced_import(
db, db,
name,
version, version,
indexes, indexes,
cached_starts, cached_starts,
@@ -1,28 +1,24 @@
use brk_cohort::ByAddrType;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, StoredI64, StoredU64, Version}; use brk_types::{BasisPointsSigned32, StoredI64, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, LazyRollingDeltasFromHeight}, internal::{LazyRollingDeltasFromHeight, WindowStartVec, Windows, WithAddrTypes},
}; };
use super::AddrCountsVecs; use super::AddrCountsVecs;
type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, BasisPointsSigned32>; type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, BasisPointsSigned32>;
#[derive(Clone, Traversable)] #[derive(Clone, Deref, DerefMut, Traversable)]
pub struct DeltaVecs { pub struct DeltaVecs(#[traversable(flatten)] pub WithAddrTypes<AddrDelta>);
pub all: AddrDelta,
#[traversable(flatten)]
pub by_addr_type: ByAddrType<AddrDelta>,
}
impl DeltaVecs { impl DeltaVecs {
pub(crate) fn new( pub(crate) fn new(
version: Version, version: Version,
addr_count: &AddrCountsVecs, addr_count: &AddrCountsVecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Self { ) -> Self {
let version = version + Version::TWO; let version = version + Version::TWO;
@@ -45,6 +41,6 @@ impl DeltaVecs {
) )
}); });
Self { all, by_addr_type } Self(WithAddrTypes { all, by_addr_type })
} }
} }
@@ -0,0 +1,74 @@
//! Exposed address count tracking — running counters of how many addresses
//! are currently in (or have ever been in) the exposed set, per address type
//! plus an aggregated `all`. See the parent [`super`] module for the
//! definition of "exposed" and how it varies by address type.
mod state;
mod vecs;
pub use state::AddrTypeToExposedAddrCount;
pub use vecs::ExposedAddrCountAllVecs;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
use crate::indexes;
/// Exposed address counts: funded (currently at-risk) and total (ever at-risk).
#[derive(Traversable)]
pub struct ExposedAddrCountsVecs<M: StorageMode = Rw> {
pub funded: ExposedAddrCountAllVecs<M>,
pub total: ExposedAddrCountAllVecs<M>,
}
impl ExposedAddrCountsVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
funded: ExposedAddrCountAllVecs::forced_import(
db,
"exposed_addr_count",
version,
indexes,
)?,
total: ExposedAddrCountAllVecs::forced_import(
db,
"total_exposed_addr_count",
version,
indexes,
)?,
})
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.funded
.min_stateful_len()
.min(self.total.min_stateful_len())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.funded
.par_iter_height_mut()
.chain(self.total.par_iter_height_mut())
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.funded.reset_height()?;
self.total.reset_height()?;
Ok(())
}
pub(crate) fn compute_rest(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.funded.compute_rest(starting_indexes, exit)?;
self.total.compute_rest(starting_indexes, exit)?;
Ok(())
}
}
@@ -0,0 +1,42 @@
use brk_cohort::ByAddrType;
use brk_types::{Height, StoredU64};
use derive_more::{Deref, DerefMut};
use vecdb::ReadableVec;
use crate::internal::PerBlock;
use super::vecs::ExposedAddrCountAllVecs;
/// Runtime counter for exposed address counts per address type.
#[derive(Debug, Default, Deref, DerefMut)]
pub struct AddrTypeToExposedAddrCount(ByAddrType<u64>);
impl AddrTypeToExposedAddrCount {
#[inline]
pub(crate) fn sum(&self) -> u64 {
self.0.values().sum()
}
}
impl From<(&ExposedAddrCountAllVecs, Height)> for AddrTypeToExposedAddrCount {
#[inline]
fn from((vecs, starting_height): (&ExposedAddrCountAllVecs, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
let read = |v: &PerBlock<StoredU64>| -> u64 {
v.height.collect_one(prev_height).unwrap().into()
};
Self(ByAddrType {
p2pk65: read(&vecs.by_addr_type.p2pk65),
p2pk33: read(&vecs.by_addr_type.p2pk33),
p2pkh: read(&vecs.by_addr_type.p2pkh),
p2sh: read(&vecs.by_addr_type.p2sh),
p2wpkh: read(&vecs.by_addr_type.p2wpkh),
p2wsh: read(&vecs.by_addr_type.p2wsh),
p2tr: read(&vecs.by_addr_type.p2tr),
p2a: read(&vecs.by_addr_type.p2a),
})
} else {
Default::default()
}
}
}
@@ -0,0 +1,29 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{PerBlock, WithAddrTypes},
};
/// Exposed address count (`all` + per-type) for a single variant (funded or total).
#[derive(Deref, DerefMut, Traversable)]
pub struct ExposedAddrCountAllVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub WithAddrTypes<PerBlock<StoredU64, M>>,
);
impl ExposedAddrCountAllVecs {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(WithAddrTypes::<PerBlock<StoredU64>>::forced_import(
db, name, version, indexes,
)?))
}
}
@@ -0,0 +1,134 @@
//! Exposed address tracking (quantum / pubkey-exposure sense).
//!
//! An address is "exposed" once its public key is in the blockchain. Once
//! exposed, any funds at that address are at cryptographic risk (e.g. from
//! a quantum attacker capable of recovering the private key from the pubkey).
//!
//! When the pubkey gets exposed depends on the address type:
//!
//! - **P2PK33, P2PK65, P2TR**: the pubkey (or P2TR's tweaked output key) is
//! directly in the locking script of the funding output. These addresses are
//! exposed the moment they receive any funds.
//! - **P2PKH, P2SH, P2WPKH, P2WSH**: the locking script contains a hash of
//! the pubkey/script. The pubkey is only revealed when spending. Note that
//! even the spending tx itself exposes the pubkey while the address still
//! holds funds — during the mempool window between broadcast and confirmation,
//! the pubkey is visible while the UTXO being spent is still unspent on-chain.
//! So every spent address of these types has had at least one moment with
//! funds at quantum risk.
//! - **P2A**: anyone-can-spend, no pubkey at all. Excluded from both counters.
//!
//! Formally, with `is_funding_exposed` = `output_type.pubkey_exposed_at_funding()`:
//! - `funded` (count): `(utxo_count > 0) AND (is_funding_exposed OR spent_txo_count >= 1)`
//! - `total` (count): `(is_funding_exposed AND ever received) OR spent_txo_count >= 1`
//! - `supply` (sats): sum of balances of addresses currently in the funded set
//!
//! For P2PK/P2TR types this means `total ≡ total_addr_count` and
//! `funded ≡ funded_addr_count` (every address of those types is exposed by
//! virtue of existing). For P2PKH/P2SH/P2WPKH/P2WSH it's the strict subset of
//! addresses that have been spent from. The aggregate `all` exposed counter
//! sums these, giving "Bitcoin addresses currently with funds at quantum risk".
//!
//! All metrics are tracked as running counters and require no extra fields
//! on the address data — they're maintained via delta detection in
//! `process_received` and `process_sent`.
mod count;
mod supply;
pub use count::{AddrTypeToExposedAddrCount, ExposedAddrCountsVecs};
pub use supply::{AddrTypeToExposedSupply, ExposedAddrSupplyVecs, ExposedSupplyShareVecs};
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Sats, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{indexes, internal::RatioSatsBp16, prices};
/// Top-level container for all exposed address tracking: counts (funded +
/// total), the funded supply, and share of supply.
#[derive(Traversable)]
pub struct ExposedAddrVecs<M: StorageMode = Rw> {
pub count: ExposedAddrCountsVecs<M>,
pub supply: ExposedAddrSupplyVecs<M>,
#[traversable(wrap = "supply", rename = "share")]
pub supply_share: ExposedSupplyShareVecs<M>,
}
impl ExposedAddrVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
count: ExposedAddrCountsVecs::forced_import(db, version, indexes)?,
supply: ExposedAddrSupplyVecs::forced_import(db, version, indexes)?,
supply_share: ExposedSupplyShareVecs::forced_import(db, version, indexes)?,
})
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.count
.min_stateful_len()
.min(self.supply.min_stateful_len())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.count
.par_iter_height_mut()
.chain(self.supply.par_iter_height_mut())
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.count.reset_height()?;
self.supply.reset_height()?;
self.supply_share.reset_height()?;
Ok(())
}
pub(crate) fn compute_rest(
&mut self,
starting_indexes: &Indexes,
prices: &prices::Vecs,
all_supply_sats: &impl ReadableVec<Height, Sats>,
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
exit: &Exit,
) -> Result<()> {
self.count.compute_rest(starting_indexes, exit)?;
self.supply
.compute_rest(starting_indexes.height, prices, exit)?;
let max_from = starting_indexes.height;
self.supply_share
.all
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&self.supply.all.sats.height,
all_supply_sats,
exit,
)?;
for ((_, share), ((_, exposed), (_, denom))) in self
.supply_share
.by_addr_type
.iter_mut()
.zip(self.supply.by_addr_type.iter().zip(type_supply_sats.iter()))
{
share.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&exposed.sats.height,
*denom,
exit,
)?;
}
Ok(())
}
}
@@ -0,0 +1,12 @@
//! Exposed address supply (sats) tracking — running sum of balances held by
//! addresses currently in the funded exposed set, per address type plus an
//! aggregated `all`. See the parent [`super`] module for the definition of
//! "exposed" and how it varies by address type.
mod share;
mod state;
mod vecs;
pub use share::ExposedSupplyShareVecs;
pub use state::AddrTypeToExposedSupply;
pub use vecs::ExposedAddrSupplyVecs;
@@ -0,0 +1,36 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{PercentPerBlock, WithAddrTypes},
};
/// Share of exposed supply relative to total supply.
///
/// - `all`: exposed_supply / circulating_supply
/// - Per-type: type's exposed_supply / type's total supply
#[derive(Deref, DerefMut, Traversable)]
pub struct ExposedSupplyShareVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub WithAddrTypes<PercentPerBlock<BasisPoints16, M>>,
);
impl ExposedSupplyShareVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(
WithAddrTypes::<PercentPerBlock<BasisPoints16>>::forced_import(
db,
"exposed_supply_share",
version,
indexes,
)?,
))
}
}
@@ -0,0 +1,42 @@
use brk_cohort::ByAddrType;
use brk_types::{Height, Sats};
use derive_more::{Deref, DerefMut};
use vecdb::ReadableVec;
use crate::internal::AmountPerBlock;
use super::vecs::ExposedAddrSupplyVecs;
/// Runtime running counter for the total balance (sats) held by funded
/// exposed addresses, per address type.
#[derive(Debug, Default, Deref, DerefMut)]
pub struct AddrTypeToExposedSupply(ByAddrType<Sats>);
impl AddrTypeToExposedSupply {
#[inline]
pub(crate) fn sum(&self) -> Sats {
self.0.values().copied().sum()
}
}
impl From<(&ExposedAddrSupplyVecs, Height)> for AddrTypeToExposedSupply {
#[inline]
fn from((vecs, starting_height): (&ExposedAddrSupplyVecs, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
let read =
|v: &AmountPerBlock| -> Sats { v.sats.height.collect_one(prev_height).unwrap() };
Self(ByAddrType {
p2pk65: read(&vecs.by_addr_type.p2pk65),
p2pk33: read(&vecs.by_addr_type.p2pk33),
p2pkh: read(&vecs.by_addr_type.p2pkh),
p2sh: read(&vecs.by_addr_type.p2sh),
p2wpkh: read(&vecs.by_addr_type.p2wpkh),
p2wsh: read(&vecs.by_addr_type.p2wsh),
p2tr: read(&vecs.by_addr_type.p2tr),
p2a: read(&vecs.by_addr_type.p2a),
})
} else {
Default::default()
}
}
}
@@ -0,0 +1,34 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Version;
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{AmountPerBlock, WithAddrTypes},
};
/// Exposed address supply (sats/btc/cents/usd) — `all` + per-address-type.
/// Tracks the total balance held by addresses currently in the funded
/// exposed set. Sats are pushed stateful per block; cents/usd are derived
/// post-hoc from sats × spot price.
#[derive(Deref, DerefMut, Traversable)]
pub struct ExposedAddrSupplyVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub WithAddrTypes<AmountPerBlock<M>>,
);
impl ExposedAddrSupplyVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(WithAddrTypes::<AmountPerBlock>::forced_import(
db,
"exposed_supply",
version,
indexes,
)?))
}
}
@@ -2,8 +2,10 @@ mod activity;
mod addr_count; mod addr_count;
mod data; mod data;
mod delta; mod delta;
mod exposed;
mod indexes; mod indexes;
mod new_addr_count; mod new_addr_count;
mod reused;
mod total_addr_count; mod total_addr_count;
mod type_map; mod type_map;
@@ -11,7 +13,9 @@ pub use activity::{AddrActivityVecs, AddrTypeToActivityCounts};
pub use addr_count::{AddrCountsVecs, AddrTypeToAddrCount}; pub use addr_count::{AddrCountsVecs, AddrTypeToAddrCount};
pub use data::AddrsDataVecs; pub use data::AddrsDataVecs;
pub use delta::DeltaVecs; pub use delta::DeltaVecs;
pub use exposed::{AddrTypeToExposedAddrCount, AddrTypeToExposedSupply, ExposedAddrVecs,};
pub use indexes::AnyAddrIndexesVecs; pub use indexes::AnyAddrIndexesVecs;
pub use new_addr_count::NewAddrCountVecs; pub use new_addr_count::NewAddrCountVecs;
pub use reused::{AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, ReusedAddrVecs};
pub use total_addr_count::TotalAddrCountVecs; pub use total_addr_count::TotalAddrCountVecs;
pub use type_map::{AddrTypeToTypeIndexMap, AddrTypeToVec, HeightToAddrTypeToVec}; pub use type_map::{AddrTypeToTypeIndexMap, AddrTypeToVec, HeightToAddrTypeToVec};
@@ -1,50 +1,35 @@
use brk_cohort::ByAddrType;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, StoredU64, Version}; use brk_types::{Height, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, Rw, StorageMode}; use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{CachedWindowStarts, PerBlockCumulativeRolling}, internal::{PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes},
}; };
use super::TotalAddrCountVecs; use super::TotalAddrCountVecs;
/// New address count per block (global + per-type) /// New address count per block (global + per-type).
#[derive(Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct NewAddrCountVecs<M: StorageMode = Rw> { pub struct NewAddrCountVecs<M: StorageMode = Rw>(
pub all: PerBlockCumulativeRolling<StoredU64, StoredU64, M>,
#[traversable(flatten)] #[traversable(flatten)]
pub by_addr_type: ByAddrType<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>, pub WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
} );
impl NewAddrCountVecs { impl NewAddrCountVecs {
pub(crate) fn forced_import( pub(crate) fn forced_import(
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let all = PerBlockCumulativeRolling::forced_import( Ok(Self(WithAddrTypes::<
db, PerBlockCumulativeRolling<StoredU64, StoredU64>,
"new_addr_count", >::forced_import(
version, db, "new_addr_count", version, indexes, cached_starts
indexes, )?))
cached_starts,
)?;
let by_addr_type = ByAddrType::new_with_name(|name| {
PerBlockCumulativeRolling::forced_import(
db,
&format!("{name}_new_addr_count"),
version,
indexes,
cached_starts,
)
})?;
Ok(Self { all, by_addr_type })
} }
pub(crate) fn compute( pub(crate) fn compute(
@@ -53,11 +38,12 @@ impl NewAddrCountVecs {
total_addr_count: &TotalAddrCountVecs, total_addr_count: &TotalAddrCountVecs,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.all.compute(max_from, exit, |height_vec| { self.0.all.compute(max_from, exit, |height_vec| {
Ok(height_vec.compute_change(max_from, &total_addr_count.all.height, 1, exit)?) Ok(height_vec.compute_change(max_from, &total_addr_count.all.height, 1, exit)?)
})?; })?;
for ((_, new), (_, total)) in self for ((_, new), (_, total)) in self
.0
.by_addr_type .by_addr_type
.iter_mut() .iter_mut()
.zip(total_addr_count.by_addr_type.iter()) .zip(total_addr_count.by_addr_type.iter())
@@ -0,0 +1,78 @@
//! Reused address count tracking — running counters of how many addresses
//! are currently in (or have ever been in) the reused set, per address type
//! plus an aggregated `all`. See the parent [`super`] module for the
//! definition of "reused".
//!
//! Two counters are exposed:
//! - `funded`: addresses currently funded AND with `funded_txo_count > 1`
//! - `total`: addresses that have ever satisfied `funded_txo_count > 1` (monotonic)
mod state;
mod vecs;
pub use state::AddrTypeToReusedAddrCount;
pub use vecs::ReusedAddrCountAllVecs;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
use crate::indexes;
/// Reused address counts: funded (currently with balance) and total (ever reused).
#[derive(Traversable)]
pub struct ReusedAddrCountsVecs<M: StorageMode = Rw> {
pub funded: ReusedAddrCountAllVecs<M>,
pub total: ReusedAddrCountAllVecs<M>,
}
impl ReusedAddrCountsVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
funded: ReusedAddrCountAllVecs::forced_import(
db,
"reused_addr_count",
version,
indexes,
)?,
total: ReusedAddrCountAllVecs::forced_import(
db,
"total_reused_addr_count",
version,
indexes,
)?,
})
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.funded
.min_stateful_len()
.min(self.total.min_stateful_len())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.funded
.par_iter_height_mut()
.chain(self.total.par_iter_height_mut())
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.funded.reset_height()?;
self.total.reset_height()?;
Ok(())
}
pub(crate) fn compute_rest(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.funded.compute_rest(starting_indexes, exit)?;
self.total.compute_rest(starting_indexes, exit)?;
Ok(())
}
}
@@ -0,0 +1,42 @@
use brk_cohort::ByAddrType;
use brk_types::{Height, StoredU64};
use derive_more::{Deref, DerefMut};
use vecdb::ReadableVec;
use crate::internal::PerBlock;
use super::vecs::ReusedAddrCountAllVecs;
/// Runtime counter for reused address counts per address type.
#[derive(Debug, Default, Deref, DerefMut)]
pub struct AddrTypeToReusedAddrCount(ByAddrType<u64>);
impl AddrTypeToReusedAddrCount {
#[inline]
pub(crate) fn sum(&self) -> u64 {
self.0.values().sum()
}
}
impl From<(&ReusedAddrCountAllVecs, Height)> for AddrTypeToReusedAddrCount {
#[inline]
fn from((vecs, starting_height): (&ReusedAddrCountAllVecs, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
let read = |v: &PerBlock<StoredU64>| -> u64 {
v.height.collect_one(prev_height).unwrap().into()
};
Self(ByAddrType {
p2pk65: read(&vecs.by_addr_type.p2pk65),
p2pk33: read(&vecs.by_addr_type.p2pk33),
p2pkh: read(&vecs.by_addr_type.p2pkh),
p2sh: read(&vecs.by_addr_type.p2sh),
p2wpkh: read(&vecs.by_addr_type.p2wpkh),
p2wsh: read(&vecs.by_addr_type.p2wsh),
p2tr: read(&vecs.by_addr_type.p2tr),
p2a: read(&vecs.by_addr_type.p2a),
})
} else {
Default::default()
}
}
}
@@ -0,0 +1,29 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{PerBlock, WithAddrTypes},
};
/// Reused address count (`all` + per-type) for a single variant (funded or total).
#[derive(Deref, DerefMut, Traversable)]
pub struct ReusedAddrCountAllVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub WithAddrTypes<PerBlock<StoredU64, M>>,
);
impl ReusedAddrCountAllVecs {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(WithAddrTypes::<PerBlock<StoredU64>>::forced_import(
db, name, version, indexes,
)?))
}
}
@@ -0,0 +1,11 @@
//! Per-block reused-address event tracking. Holds both the output-side
//! ("an output landed on a previously-used address") and input-side
//! ("an input spent from an address in the reused set") event counters.
//! See [`vecs::ReusedAddrEventsVecs`] for the full description of each
//! metric.
mod state;
mod vecs;
pub use state::AddrTypeToReusedAddrEventCount;
pub use vecs::ReusedAddrEventsVecs;
@@ -0,0 +1,28 @@
use brk_cohort::ByAddrType;
use derive_more::{Deref, DerefMut};
/// Per-block running counter of reused-address events, per address type.
/// Shared runtime container for both output-side events
/// (`output_to_reused_addr_count`, outputs landing on addresses that
/// had already received ≥ 1 prior output) and input-side events
/// (`input_from_reused_addr_count`, inputs spending from addresses
/// with lifetime `funded_txo_count > 1`). Reset at the start of each
/// block (no disk recovery needed since per-block flow is
/// reconstructed deterministically from `process_received` /
/// `process_sent`).
#[derive(Debug, Default, Deref, DerefMut)]
pub struct AddrTypeToReusedAddrEventCount(ByAddrType<u64>);
impl AddrTypeToReusedAddrEventCount {
#[inline]
pub(crate) fn sum(&self) -> u64 {
self.0.values().sum()
}
#[inline]
pub(crate) fn reset(&mut self) {
for v in self.0.values_mut() {
*v = 0;
}
}
}
@@ -0,0 +1,261 @@
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Indexes, OutputType, StoredF32, StoredU32, StoredU64, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{
indexes, inputs,
internal::{
PerBlockCumulativeRolling, PerBlockRollingAverage, PercentCumulativeRolling,
WindowStartVec, Windows, WithAddrTypes,
},
outputs,
};
use super::state::AddrTypeToReusedAddrEventCount;
/// Per-block reused-address event metrics. Holds three families of
/// signals: output-level (use), input-level (spend), and address-level
/// (active in block).
///
/// `output_to_reused_addr_count`: every output landing on an address that had
/// already received at least one prior output anywhere in its lifetime,
/// i.e. an output-level reuse event. Outputs are not deduplicated per
/// address within a block: an address receiving N outputs in one block
/// that had `before` lifetime outputs contributes
/// `max(0, N - max(0, 1 - before))` events. Only the very first output
/// an address ever sees is excluded. Every subsequent output counts,
/// matching the standard "% of outputs to previously-used addresses"
/// reuse ratio reported by external sources. `output_to_reused_addr_share`
/// uses `outputs::ByTypeVecs::output_count` (all 12 output types) as
/// denominator. `spendable_output_to_reused_addr_share` uses the
/// op_return-excluded 11-type aggregate (`spendable_output_count`).
///
/// `input_from_reused_addr_count`: every input spending from an address
/// whose lifetime `funded_txo_count > 1` at the time of the spend (i.e.
/// the address is in the same reused set tracked by
/// `reused_addr_count`). Every input is checked independently. If a
/// single address has multiple inputs in one block each one counts.
/// This is a *stable-predicate* signal about the sending address, not
/// an output-level repeat event: the first spend from a reused address
/// counts just as much as the tenth. Denominator
/// (`input_from_reused_addr_share`): `inputs::ByTypeVecs::input_count` (11
/// spendable types, where `p2ms`, `unknown`, `empty` count as true
/// negatives).
///
/// `active_reused_addr_count` / `active_reused_addr_share`: block-level
/// *address* signals (single aggregate, not per-type).
/// `active_reused_addr_count` is the count of distinct addresses
/// involved in this block (sent received) that satisfy `is_reused()`
/// after the block's events, populated inline in `process_received`
/// (each receiver, post-receive) and in `process_sent` (each
/// first-encounter sender, deduped against `received_addrs` so
/// addresses that did both aren't double-counted).
/// `active_reused_addr_share` is the per-block ratio
/// `reused / active * 100` as a percentage in `[0, 100]` (or `0.0` for
/// empty blocks). The denominator (distinct active addrs per block)
/// lives on `ActivityCountVecs::active` (`addrs.activity.all.active`),
/// derived from `sending + receiving - bidirectional`. Both fields
/// use `PerBlockRollingAverage` so their lazy 24h/1w/1m/1y series are
/// rolling *averages* of the per-block values. Sums and cumulatives of
/// distinct-address counts would be misleading because the same
/// address can appear in multiple blocks.
#[derive(Traversable)]
pub struct ReusedAddrEventsVecs<M: StorageMode = Rw> {
pub output_to_reused_addr_count:
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub output_to_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
pub spendable_output_to_reused_addr_share: PercentCumulativeRolling<BasisPoints16, M>,
pub input_from_reused_addr_count:
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub input_from_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
pub active_reused_addr_count: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub active_reused_addr_share: PerBlockRollingAverage<StoredF32, StoredF32, M>,
}
impl ReusedAddrEventsVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let import_count = |name: &str| {
WithAddrTypes::<PerBlockCumulativeRolling<StoredU64, StoredU64>>::forced_import(
db,
name,
version,
indexes,
cached_starts,
)
};
let import_percent = |name: &str| -> Result<WithAddrTypes<
PercentCumulativeRolling<BasisPoints16>,
>> {
Ok(WithAddrTypes {
all: PercentCumulativeRolling::forced_import(db, name, version, indexes)?,
by_addr_type: ByAddrType::new_with_name(|type_name| {
PercentCumulativeRolling::forced_import(
db,
&format!("{type_name}_{name}"),
version,
indexes,
)
})?,
})
};
let output_to_reused_addr_count = import_count("output_to_reused_addr_count")?;
let output_to_reused_addr_share = import_percent("output_to_reused_addr_share")?;
let spendable_output_to_reused_addr_share = PercentCumulativeRolling::forced_import(
db,
"spendable_output_to_reused_addr_share",
version,
indexes,
)?;
let input_from_reused_addr_count = import_count("input_from_reused_addr_count")?;
let input_from_reused_addr_share = import_percent("input_from_reused_addr_share")?;
let active_reused_addr_count = PerBlockRollingAverage::forced_import(
db,
"active_reused_addr_count",
version,
indexes,
cached_starts,
)?;
let active_reused_addr_share = PerBlockRollingAverage::forced_import(
db,
"active_reused_addr_share",
version,
indexes,
cached_starts,
)?;
Ok(Self {
output_to_reused_addr_count,
output_to_reused_addr_share,
spendable_output_to_reused_addr_share,
input_from_reused_addr_count,
input_from_reused_addr_share,
active_reused_addr_count,
active_reused_addr_share,
})
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.output_to_reused_addr_count
.min_stateful_len()
.min(self.input_from_reused_addr_count.min_stateful_len())
.min(self.active_reused_addr_count.block.len())
.min(self.active_reused_addr_share.block.len())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.output_to_reused_addr_count
.par_iter_height_mut()
.chain(self.input_from_reused_addr_count.par_iter_height_mut())
.chain([
&mut self.active_reused_addr_count.block as &mut dyn AnyStoredVec,
&mut self.active_reused_addr_share.block as &mut dyn AnyStoredVec,
])
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.output_to_reused_addr_count.reset_height()?;
self.input_from_reused_addr_count.reset_height()?;
self.active_reused_addr_count.block.reset()?;
self.active_reused_addr_share.block.reset()?;
Ok(())
}
#[inline(always)]
pub(crate) fn push_height(
&mut self,
uses: &AddrTypeToReusedAddrEventCount,
spends: &AddrTypeToReusedAddrEventCount,
active_addr_count: u32,
active_reused_addr_count: u32,
) {
self.output_to_reused_addr_count
.push_height(uses.sum(), uses.values().copied());
self.input_from_reused_addr_count
.push_height(spends.sum(), spends.values().copied());
self.active_reused_addr_count
.block
.push(StoredU32::from(active_reused_addr_count));
// Stored as a percentage in [0, 100] to match the rest of the
// codebase (Unit.percentage on the website expects 0..100). The
// `active_addr_count` denominator lives on `ActivityCountVecs`
// (`addrs.activity.all.active`), passed in here so we can
// compute the per-block ratio inline.
let share = if active_addr_count > 0 {
100.0 * (active_reused_addr_count as f32 / active_addr_count as f32)
} else {
0.0
};
self.active_reused_addr_share
.block
.push(StoredF32::from(share));
}
pub(crate) fn compute_rest(
&mut self,
starting_indexes: &Indexes,
outputs_by_type: &outputs::ByTypeVecs,
inputs_by_type: &inputs::ByTypeVecs,
exit: &Exit,
) -> Result<()> {
self.output_to_reused_addr_count
.compute_rest(starting_indexes.height, exit)?;
self.input_from_reused_addr_count
.compute_rest(starting_indexes.height, exit)?;
self.active_reused_addr_count
.compute_rest(starting_indexes.height, exit)?;
self.active_reused_addr_share
.compute_rest(starting_indexes.height, exit)?;
self.output_to_reused_addr_share.all.compute_count_ratio(
&self.output_to_reused_addr_count.all,
&outputs_by_type.output_count.all,
starting_indexes.height,
exit,
)?;
self.spendable_output_to_reused_addr_share.compute_count_ratio(
&self.output_to_reused_addr_count.all,
&outputs_by_type.spendable_output_count,
starting_indexes.height,
exit,
)?;
self.input_from_reused_addr_share.all.compute_count_ratio(
&self.input_from_reused_addr_count.all,
&inputs_by_type.input_count.all,
starting_indexes.height,
exit,
)?;
for otype in OutputType::ADDR_TYPES {
self.output_to_reused_addr_share
.by_addr_type
.get_mut_unwrap(otype)
.compute_count_ratio(
self.output_to_reused_addr_count.by_addr_type.get_unwrap(otype),
outputs_by_type.output_count.by_type.get(otype),
starting_indexes.height,
exit,
)?;
self.input_from_reused_addr_share
.by_addr_type
.get_mut_unwrap(otype)
.compute_count_ratio(
self.input_from_reused_addr_count.by_addr_type.get_unwrap(otype),
inputs_by_type.input_count.by_type.get(otype),
starting_indexes.height,
exit,
)?;
}
Ok(())
}
}
@@ -0,0 +1,94 @@
//! Reused address tracking.
//!
//! An address is "reused" if its lifetime `funded_txo_count > 1`, i.e.
//! it has received more than one output across its lifetime. This is
//! the simplest output-multiplicity proxy for address linkability.
//!
//! Two facets are tracked here:
//! - [`count`]: how many distinct addresses are currently reused
//! (funded) and how many have *ever* been reused (total). Per address
//! type plus an aggregated `all`.
//! - [`events`]: per-block address-reuse event counts on both sides.
//! Output-side (`output_to_reused_addr_count`, outputs landing on
//! addresses that had already received ≥ 1 prior output) and
//! input-side (`input_from_reused_addr_count`, inputs spending from
//! addresses with lifetime `funded_txo_count > 1`). Each count is
//! paired with a percent over the matching block-level output/input
//! total.
mod count;
mod events;
pub use count::{AddrTypeToReusedAddrCount, ReusedAddrCountsVecs};
pub use events::{AddrTypeToReusedAddrEventCount, ReusedAddrEventsVecs};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
use crate::{
indexes, inputs,
internal::{WindowStartVec, Windows},
outputs,
};
/// Top-level container for all reused address tracking: counts (funded +
/// total) plus per-block reuse events (output-side + input-side).
#[derive(Traversable)]
pub struct ReusedAddrVecs<M: StorageMode = Rw> {
pub count: ReusedAddrCountsVecs<M>,
pub events: ReusedAddrEventsVecs<M>,
}
impl ReusedAddrVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
Ok(Self {
count: ReusedAddrCountsVecs::forced_import(db, version, indexes)?,
events: ReusedAddrEventsVecs::forced_import(db, version, indexes, cached_starts)?,
})
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.count
.min_stateful_len()
.min(self.events.min_stateful_len())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.count
.par_iter_height_mut()
.chain(self.events.par_iter_height_mut())
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.count.reset_height()?;
self.events.reset_height()?;
Ok(())
}
pub(crate) fn compute_rest(
&mut self,
starting_indexes: &Indexes,
outputs_by_type: &outputs::ByTypeVecs,
inputs_by_type: &inputs::ByTypeVecs,
exit: &Exit,
) -> Result<()> {
self.count.compute_rest(starting_indexes, exit)?;
self.events.compute_rest(
starting_indexes,
outputs_by_type,
inputs_by_type,
exit,
)?;
Ok(())
}
}
@@ -1,20 +1,21 @@
use brk_cohort::ByAddrType;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, StoredU64, Version}; use brk_types::{Height, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, Rw, StorageMode}; use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{indexes, internal::PerBlock}; use crate::{
indexes,
internal::{PerBlock, WithAddrTypes},
};
use super::AddrCountsVecs; use super::AddrCountsVecs;
/// Total address count (global + per-type) with all derived indexes /// Total address count (global + per-type) with all derived indexes.
#[derive(Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct TotalAddrCountVecs<M: StorageMode = Rw> { pub struct TotalAddrCountVecs<M: StorageMode = Rw>(
pub all: PerBlock<StoredU64, M>, #[traversable(flatten)] pub WithAddrTypes<PerBlock<StoredU64, M>>,
#[traversable(flatten)] );
pub by_addr_type: ByAddrType<PerBlock<StoredU64, M>>,
}
impl TotalAddrCountVecs { impl TotalAddrCountVecs {
pub(crate) fn forced_import( pub(crate) fn forced_import(
@@ -22,13 +23,12 @@ impl TotalAddrCountVecs {
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Result<Self> { ) -> Result<Self> {
let all = PerBlock::forced_import(db, "total_addr_count", version, indexes)?; Ok(Self(WithAddrTypes::<PerBlock<StoredU64>>::forced_import(
db,
let by_addr_type: ByAddrType<PerBlock<StoredU64>> = ByAddrType::new_with_name(|name| { "total_addr_count",
PerBlock::forced_import(db, &format!("{name}_total_addr_count"), version, indexes) version,
})?; indexes,
)?))
Ok(Self { all, by_addr_type })
} }
/// Eagerly compute total = addr_count + empty_addr_count. /// Eagerly compute total = addr_count + empty_addr_count.
@@ -39,14 +39,14 @@ impl TotalAddrCountVecs {
empty_addr_count: &AddrCountsVecs, empty_addr_count: &AddrCountsVecs,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.all.height.compute_add( self.0.all.height.compute_add(
max_from, max_from,
&addr_count.all.height, &addr_count.all.height,
&empty_addr_count.all.height, &empty_addr_count.all.height,
exit, exit,
)?; )?;
for ((_, total), ((_, addr), (_, empty))) in self.by_addr_type.iter_mut().zip( for ((_, total), ((_, addr), (_, empty))) in self.0.by_addr_type.iter_mut().zip(
addr_count addr_count
.by_addr_type .by_addr_type
.iter() .iter()
@@ -67,14 +67,14 @@ pub(crate) fn process_funded_addrs(
// Pure pushes - no holes remain // Pure pushes - no holes remain
addrs_data.funded.reserve_pushed(pushes_iter.len()); addrs_data.funded.reserve_pushed(pushes_iter.len());
let mut next_index = addrs_data.funded.len(); for (next_index, (addr_type, type_index, data)) in
for (addr_type, type_index, data) in pushes_iter { (addrs_data.funded.len()..).zip(pushes_iter)
{
addrs_data.funded.push(data); addrs_data.funded.push(data);
result.get_mut(addr_type).unwrap().insert( result.get_mut(addr_type).unwrap().insert(
type_index, type_index,
AnyAddrIndex::from(FundedAddrIndex::from(next_index)), AnyAddrIndex::from(FundedAddrIndex::from(next_index)),
); );
next_index += 1;
} }
Ok(result) Ok(result)
@@ -138,14 +138,14 @@ pub(crate) fn process_empty_addrs(
// Pure pushes - no holes remain // Pure pushes - no holes remain
addrs_data.empty.reserve_pushed(pushes_iter.len()); addrs_data.empty.reserve_pushed(pushes_iter.len());
let mut next_index = addrs_data.empty.len(); for (next_index, (addr_type, type_index, data)) in
for (addr_type, type_index, data) in pushes_iter { (addrs_data.empty.len()..).zip(pushes_iter)
{
addrs_data.empty.push(data); addrs_data.empty.push(data);
result.get_mut(addr_type).unwrap().insert( result.get_mut(addr_type).unwrap().insert(
type_index, type_index,
AnyAddrIndex::from(EmptyAddrIndex::from(next_index)), AnyAddrIndex::from(EmptyAddrIndex::from(next_index)),
); );
next_index += 1;
} }
Ok(result) Ok(result)
@@ -3,7 +3,10 @@ use brk_types::{Cents, Sats, TypeIndex};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::distribution::{ use crate::distribution::{
addr::{AddrTypeToActivityCounts, AddrTypeToVec}, addr::{
AddrTypeToActivityCounts, AddrTypeToExposedAddrCount, AddrTypeToExposedSupply,
AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, AddrTypeToVec,
},
cohorts::AddrCohorts, cohorts::AddrCohorts,
}; };
@@ -25,6 +28,13 @@ pub(crate) fn process_received(
addr_count: &mut ByAddrType<u64>, addr_count: &mut ByAddrType<u64>,
empty_addr_count: &mut ByAddrType<u64>, empty_addr_count: &mut ByAddrType<u64>,
activity_counts: &mut AddrTypeToActivityCounts, activity_counts: &mut AddrTypeToActivityCounts,
reused_addr_count: &mut AddrTypeToReusedAddrCount,
total_reused_addr_count: &mut AddrTypeToReusedAddrCount,
output_to_reused_addr_count: &mut AddrTypeToReusedAddrEventCount,
active_reused_addr_count: &mut AddrTypeToReusedAddrEventCount,
exposed_addr_count: &mut AddrTypeToExposedAddrCount,
total_exposed_addr_count: &mut AddrTypeToExposedAddrCount,
exposed_supply: &mut AddrTypeToExposedSupply,
) { ) {
let max_type_len = received_data let max_type_len = received_data
.iter() .iter()
@@ -43,6 +53,13 @@ pub(crate) fn process_received(
let type_addr_count = addr_count.get_mut(output_type).unwrap(); let type_addr_count = addr_count.get_mut(output_type).unwrap();
let type_empty_count = empty_addr_count.get_mut(output_type).unwrap(); let type_empty_count = empty_addr_count.get_mut(output_type).unwrap();
let type_activity = activity_counts.get_mut_unwrap(output_type); let type_activity = activity_counts.get_mut_unwrap(output_type);
let type_reused_count = reused_addr_count.get_mut(output_type).unwrap();
let type_total_reused_count = total_reused_addr_count.get_mut(output_type).unwrap();
let type_output_to_reused_count = output_to_reused_addr_count.get_mut(output_type).unwrap();
let type_active_reused_count = active_reused_addr_count.get_mut(output_type).unwrap();
let type_exposed_count = exposed_addr_count.get_mut(output_type).unwrap();
let type_total_exposed_count = total_exposed_addr_count.get_mut(output_type).unwrap();
let type_exposed_supply = exposed_supply.get_mut(output_type).unwrap();
// Aggregate receives by address - each address processed exactly once // Aggregate receives by address - each address processed exactly once
for (type_index, value) in vec { for (type_index, value) in vec {
@@ -57,6 +74,13 @@ pub(crate) fn process_received(
// Track receiving activity - each address in receive aggregation // Track receiving activity - each address in receive aggregation
type_activity.receiving += 1; type_activity.receiving += 1;
// Capture state BEFORE the receive mutates funded_txo_count
let was_funded = addr_data.is_funded();
let was_reused = addr_data.is_reused();
let funded_txo_count_before = addr_data.funded_txo_count;
let was_pubkey_exposed = addr_data.is_pubkey_exposed(output_type);
let exposed_contribution_before = addr_data.exposed_supply_contribution(output_type);
match status { match status {
TrackingStatus::New => { TrackingStatus::New => {
*type_addr_count += 1; *type_addr_count += 1;
@@ -134,6 +158,62 @@ pub(crate) fn process_received(
.receive_outputs(addr_data, recv.total_value, price, recv.output_count); .receive_outputs(addr_data, recv.total_value, price, recv.output_count);
} }
} }
// Update reused counts based on the post-receive state
let is_now_reused = addr_data.is_reused();
if is_now_reused && !was_reused {
// Newly crossed the reuse threshold this block
*type_reused_count += 1;
*type_total_reused_count += 1;
} else if is_now_reused && !was_funded {
// Already-reused address reactivating into the funded set
*type_reused_count += 1;
}
// Block-level "active reused address" count: each address
// is processed exactly once here (via aggregation), so we
// count it once iff it is reused after the block's receives.
// The sender-side counterpart in process_sent dedupes
// against `received_addrs` so addresses that did both
// aren't double-counted.
if is_now_reused {
*type_active_reused_count += 1;
}
// Per-block reused-use count: every individual output to this
// address counts iff, at the moment the output arrives, the
// address had already received at least one prior output
// (i.e. it is an output-level "address reuse event"). With
// aggregation, that means we skip the very first output the
// address ever sees and count every subsequent one, so
// `skipped` is `max(0, 1 - before)`.
let skipped = 1u32.saturating_sub(funded_txo_count_before);
let counted = recv.output_count.saturating_sub(skipped);
*type_output_to_reused_count += u64::from(counted);
// Update exposed counts. The address's pubkey-exposure state
// is unchanged by a receive (spent_txo_count unchanged), so we
// can use the captured `was_pubkey_exposed` for both pre and post.
// After the receive the address is always funded, so it's in the
// funded exposed set iff its pubkey is exposed.
//
// Funded exposed enters when the address wasn't funded before but
// is now AND its pubkey is exposed.
// Total exposed (pk_exposed_at_funding types only) increments on
// first-ever receive (status == TrackingStatus::New); for other
// types it's incremented in process_sent on the first spend.
if !was_funded && was_pubkey_exposed {
*type_exposed_count += 1;
}
if output_type.pubkey_exposed_at_funding() && matches!(status, TrackingStatus::New) {
*type_total_exposed_count += 1;
}
// Update exposed supply via post-receive contribution delta.
let exposed_contribution_after = addr_data.exposed_supply_contribution(output_type);
// Receives can only add to balance and membership, so the delta
// is always non-negative.
*type_exposed_supply += exposed_contribution_after - exposed_contribution_before;
} }
} }
} }
@@ -5,7 +5,10 @@ use rustc_hash::FxHashSet;
use vecdb::VecIndex; use vecdb::VecIndex;
use crate::distribution::{ use crate::distribution::{
addr::{AddrTypeToActivityCounts, HeightToAddrTypeToVec}, addr::{
AddrTypeToActivityCounts, AddrTypeToExposedAddrCount, AddrTypeToExposedSupply,
AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, HeightToAddrTypeToVec,
},
cohorts::AddrCohorts, cohorts::AddrCohorts,
compute::PriceRangeMax, compute::PriceRangeMax,
}; };
@@ -35,6 +38,12 @@ pub(crate) fn process_sent(
addr_count: &mut ByAddrType<u64>, addr_count: &mut ByAddrType<u64>,
empty_addr_count: &mut ByAddrType<u64>, empty_addr_count: &mut ByAddrType<u64>,
activity_counts: &mut AddrTypeToActivityCounts, activity_counts: &mut AddrTypeToActivityCounts,
reused_addr_count: &mut AddrTypeToReusedAddrCount,
input_from_reused_addr_count: &mut AddrTypeToReusedAddrEventCount,
active_reused_addr_count: &mut AddrTypeToReusedAddrEventCount,
exposed_addr_count: &mut AddrTypeToExposedAddrCount,
total_exposed_addr_count: &mut AddrTypeToExposedAddrCount,
exposed_supply: &mut AddrTypeToExposedSupply,
received_addrs: &ByAddrType<FxHashSet<TypeIndex>>, received_addrs: &ByAddrType<FxHashSet<TypeIndex>>,
height_to_price: &[Cents], height_to_price: &[Cents],
height_to_timestamp: &[Timestamp], height_to_timestamp: &[Timestamp],
@@ -57,12 +66,28 @@ pub(crate) fn process_sent(
let type_addr_count = addr_count.get_mut(output_type).unwrap(); let type_addr_count = addr_count.get_mut(output_type).unwrap();
let type_empty_count = empty_addr_count.get_mut(output_type).unwrap(); let type_empty_count = empty_addr_count.get_mut(output_type).unwrap();
let type_activity = activity_counts.get_mut_unwrap(output_type); let type_activity = activity_counts.get_mut_unwrap(output_type);
let type_reused_count = reused_addr_count.get_mut(output_type).unwrap();
let type_input_from_reused_count =
input_from_reused_addr_count.get_mut(output_type).unwrap();
let type_active_reused_count = active_reused_addr_count.get_mut(output_type).unwrap();
let type_exposed_count = exposed_addr_count.get_mut(output_type).unwrap();
let type_total_exposed_count = total_exposed_addr_count.get_mut(output_type).unwrap();
let type_exposed_supply = exposed_supply.get_mut(output_type).unwrap();
let type_received = received_addrs.get(output_type); let type_received = received_addrs.get(output_type);
let type_seen = seen_senders.get_mut_unwrap(output_type); let type_seen = seen_senders.get_mut_unwrap(output_type);
for (type_index, value) in vec { for (type_index, value) in vec {
let addr_data = lookup.get_for_send(output_type, type_index); let addr_data = lookup.get_for_send(output_type, type_index);
// "Input from a reused address" event: the sending
// address is in the reused set (lifetime
// funded_txo_count > 1). Checked once per input. The
// spend itself doesn't touch funded_txo_count so the
// predicate is stable before/after `cohort_state.send`.
if addr_data.is_reused() {
*type_input_from_reused_count += 1;
}
let prev_balance = addr_data.balance(); let prev_balance = addr_data.balance();
let new_balance = prev_balance.checked_sub(value).unwrap(); let new_balance = prev_balance.checked_sub(value).unwrap();
@@ -70,14 +95,29 @@ pub(crate) fn process_sent(
if type_seen.insert(type_index) { if type_seen.insert(type_index) {
type_activity.sending += 1; type_activity.sending += 1;
// Track "both" - addresses that sent AND received this block let also_received = type_received.is_some_and(|s| s.contains(&type_index));
if type_received.is_some_and(|s| s.contains(&type_index)) { // Track "bidirectional": addresses that sent AND
type_activity.both += 1; // received this block.
if also_received {
type_activity.bidirectional += 1;
}
// Block-level "active reused address" count: count
// every distinct sender that's reused, but skip
// those that also received this block (already
// counted in process_received).
if !also_received && addr_data.is_reused() {
*type_active_reused_count += 1;
} }
} }
let will_be_empty = addr_data.has_1_utxos(); let will_be_empty = addr_data.has_1_utxos();
// Capture exposed state BEFORE the spend mutates spent_txo_count.
let was_pubkey_exposed = addr_data.is_pubkey_exposed(output_type);
let exposed_contribution_before =
addr_data.exposed_supply_contribution(output_type);
// Compute buckets once // Compute buckets once
let prev_bucket = AmountBucket::from(prev_balance); let prev_bucket = AmountBucket::from(prev_balance);
let new_bucket = AmountBucket::from(new_balance); let new_bucket = AmountBucket::from(new_balance);
@@ -91,6 +131,28 @@ pub(crate) fn process_sent(
.unwrap(); .unwrap();
cohort_state.send(addr_data, value, current_price, prev_price, peak_price, age)?; cohort_state.send(addr_data, value, current_price, prev_price, peak_price, age)?;
// addr_data.spent_txo_count is now incremented by 1.
// Update exposed supply via post-spend contribution delta.
let exposed_contribution_after = addr_data.exposed_supply_contribution(output_type);
if exposed_contribution_after >= exposed_contribution_before {
*type_exposed_supply +=
exposed_contribution_after - exposed_contribution_before;
} else {
*type_exposed_supply -=
exposed_contribution_before - exposed_contribution_after;
}
// Update exposed counts on first-ever pubkey exposure.
// For non-pk-exposed types this fires on the first spend; for
// pk-exposed types it never fires here (was_pubkey_exposed was
// already true at first receive in process_received).
if !was_pubkey_exposed {
*type_total_exposed_count += 1;
if !will_be_empty {
*type_exposed_count += 1;
}
}
// If crossing a bucket boundary, remove the (now-updated) address from old bucket // If crossing a bucket boundary, remove the (now-updated) address from old bucket
if will_be_empty || crossing_boundary { if will_be_empty || crossing_boundary {
@@ -101,6 +163,17 @@ pub(crate) fn process_sent(
if will_be_empty { if will_be_empty {
*type_addr_count -= 1; *type_addr_count -= 1;
*type_empty_count += 1; *type_empty_count += 1;
// Reused addr leaving the funded reused set
if addr_data.is_reused() {
*type_reused_count -= 1;
}
// Exposed addr leaving the funded exposed set: was in set
// iff its pubkey was exposed pre-spend (since it was funded
// to be in process_sent in the first place), and now leaves
// because it's empty.
if was_pubkey_exposed {
*type_exposed_count -= 1;
}
lookup.move_to_empty(output_type, type_index); lookup.move_to_empty(output_type, type_index);
} else if crossing_boundary { } else if crossing_boundary {
cohorts cohorts
@@ -3,12 +3,17 @@ use std::path::Path;
use brk_cohort::{AddrGroups, AmountRange, Filter, Filtered, OverAmount, UnderAmount}; use brk_cohort::{AddrGroups, AmountRange, Filter, Filtered, OverAmount, UnderAmount};
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, Indexes, StoredU64, Version}; use brk_types::{Height, Indexes, Sats, StoredU64, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use rayon::prelude::*; use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode}; use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{distribution::DynCohortVecs, indexes, internal::CachedWindowStarts, prices}; use crate::{
distribution::DynCohortVecs,
indexes,
internal::{WindowStartVec, Windows},
prices,
};
use super::{super::traits::CohortVecs, vecs::AddrCohortVecs}; use super::{super::traits::CohortVecs, vecs::AddrCohortVecs};
@@ -25,7 +30,7 @@ impl AddrCohorts {
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
states_path: &Path, states_path: &Path,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let v = version + VERSION; let v = version + VERSION;
@@ -104,12 +109,19 @@ impl AddrCohorts {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>, all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.0 self.0.par_iter_mut().try_for_each(|v| {
.par_iter_mut() v.compute_rest_part2(
.try_for_each(|v| v.compute_rest_part2(prices, starting_indexes, all_utxo_count, exit)) prices,
starting_indexes,
all_supply_sats,
all_utxo_count,
exit,
)
})
} }
/// Returns a parallel iterator over all vecs for parallel writing. /// Returns a parallel iterator over all vecs for parallel writing.
@@ -3,14 +3,14 @@ use std::path::Path;
use brk_cohort::{CohortContext, Filter, Filtered}; use brk_cohort::{CohortContext, Filter, Filtered};
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Cents, Height, Indexes, StoredI64, StoredU64, Version}; use brk_types::{BasisPointsSigned32, Cents, Height, Indexes, Sats, StoredI64, StoredU64, Version};
use rayon::prelude::*; use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec}; use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{ use crate::{
distribution::state::{AddrCohortState, MinimalRealizedState}, distribution::state::{AddrCohortState, MinimalRealizedState},
indexes, indexes,
internal::{CachedWindowStarts, PerBlockWithDeltas}, internal::{PerBlockWithDeltas, WindowStartVec, Windows},
prices, prices,
}; };
@@ -38,7 +38,7 @@ impl AddrCohortVecs {
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
states_path: Option<&Path>, states_path: Option<&Path>,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let full_name = CohortContext::Addr.full_name(&filter, name); let full_name = CohortContext::Addr.full_name(&filter, name);
@@ -230,10 +230,16 @@ impl CohortVecs for AddrCohortVecs {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>, all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.metrics self.metrics.compute_rest_part2(
.compute_rest_part2(prices, starting_indexes, all_utxo_count, exit) prices,
starting_indexes,
all_supply_sats,
all_utxo_count,
exit,
)
} }
} }
@@ -1,5 +1,5 @@
use brk_error::Result; use brk_error::Result;
use brk_types::{Cents, Height, Indexes, StoredU64, Version}; use brk_types::{Cents, Height, Indexes, Sats, StoredU64, Version};
use vecdb::{Exit, ReadableVec}; use vecdb::{Exit, ReadableVec};
use crate::prices; use crate::prices;
@@ -62,6 +62,7 @@ pub trait CohortVecs: DynCohortVecs {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>, all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()>; ) -> Result<()>;
@@ -25,7 +25,7 @@ use crate::{
state::UTXOCohortState, state::UTXOCohortState,
}, },
indexes, indexes,
internal::{AmountPerBlockCumulativeRolling, CachedWindowStarts}, internal::{AmountPerBlockCumulativeRolling, WindowStartVec, Windows},
prices, prices,
}; };
@@ -75,7 +75,7 @@ impl UTXOCohorts<Rw> {
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
states_path: &Path, states_path: &Path,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let v = version + VERSION; let v = version + VERSION;
@@ -542,17 +542,14 @@ impl UTXOCohorts<Rw> {
} }
/// Second phase of post-processing: compute relative metrics. /// Second phase of post-processing: compute relative metrics.
pub(crate) fn compute_rest_part2<HM>( pub(crate) fn compute_rest_part2(
&mut self, &mut self,
blocks: &blocks::Vecs, blocks: &blocks::Vecs,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
height_to_market_cap: &HM, height_to_market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit, exit: &Exit,
) -> Result<()> ) -> Result<()> {
where
HM: ReadableVec<Height, Dollars> + Sync,
{
// Get under_1h value sources for adjusted computation (cloned to avoid borrow conflicts). // Get under_1h value sources for adjusted computation (cloned to avoid borrow conflicts).
let under_1h_value_created = self let under_1h_value_created = self
.age_range .age_range
@@ -663,7 +660,7 @@ impl UTXOCohorts<Rw> {
Box::new(|| { Box::new(|| {
over_amount.par_iter_mut().try_for_each(|v| { over_amount.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, au, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
@@ -681,19 +678,19 @@ impl UTXOCohorts<Rw> {
Box::new(|| { Box::new(|| {
amount_range.par_iter_mut().try_for_each(|v| { amount_range.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, au, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
under_amount.par_iter_mut().try_for_each(|v| { under_amount.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, au, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
type_.par_iter_mut().try_for_each(|v| { type_.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, au, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
]; ];
@@ -830,26 +827,26 @@ impl UTXOCohorts<Rw> {
let mut sth_acc = RealizedFullAccum::default(); let mut sth_acc = RealizedFullAccum::default();
let mut lth_acc = RealizedFullAccum::default(); let mut lth_acc = RealizedFullAccum::default();
let mut all_icap = (0u128, 0u128); let mut all_ccap = (0u128, 0u128);
let mut sth_icap = (0u128, 0u128); let mut sth_ccap = (0u128, 0u128);
let mut lth_icap = (0u128, 0u128); let mut lth_ccap = (0u128, 0u128);
for ar in age_range.iter_mut() { for ar in age_range.iter_mut() {
if let Some(state) = ar.state.as_mut() { if let Some(state) = ar.state.as_mut() {
all_acc.add(&state.realized); all_acc.add(&state.realized);
let u = state.compute_unrealized_state(height_price); let u = state.compute_unrealized_state(height_price);
all_icap.0 += u.investor_cap_in_profit_raw; all_ccap.0 += u.capitalized_cap_in_profit_raw;
all_icap.1 += u.investor_cap_in_loss_raw; all_ccap.1 += u.capitalized_cap_in_loss_raw;
if sth_filter.includes(&ar.metrics.filter) { if sth_filter.includes(&ar.metrics.filter) {
sth_acc.add(&state.realized); sth_acc.add(&state.realized);
sth_icap.0 += u.investor_cap_in_profit_raw; sth_ccap.0 += u.capitalized_cap_in_profit_raw;
sth_icap.1 += u.investor_cap_in_loss_raw; sth_ccap.1 += u.capitalized_cap_in_loss_raw;
} else { } else {
lth_acc.add(&state.realized); lth_acc.add(&state.realized);
lth_icap.0 += u.investor_cap_in_profit_raw; lth_ccap.0 += u.capitalized_cap_in_profit_raw;
lth_icap.1 += u.investor_cap_in_loss_raw; lth_ccap.1 += u.capitalized_cap_in_loss_raw;
} }
} }
} }
@@ -860,28 +857,28 @@ impl UTXOCohorts<Rw> {
all.metrics all.metrics
.unrealized .unrealized
.investor_cap_in_profit_raw .capitalized_cap_in_profit_raw
.push(CentsSquaredSats::new(all_icap.0)); .push(CentsSquaredSats::new(all_ccap.0));
all.metrics all.metrics
.unrealized .unrealized
.investor_cap_in_loss_raw .capitalized_cap_in_loss_raw
.push(CentsSquaredSats::new(all_icap.1)); .push(CentsSquaredSats::new(all_ccap.1));
sth.metrics sth.metrics
.unrealized .unrealized
.investor_cap_in_profit_raw .capitalized_cap_in_profit_raw
.push(CentsSquaredSats::new(sth_icap.0)); .push(CentsSquaredSats::new(sth_ccap.0));
sth.metrics sth.metrics
.unrealized .unrealized
.investor_cap_in_loss_raw .capitalized_cap_in_loss_raw
.push(CentsSquaredSats::new(sth_icap.1)); .push(CentsSquaredSats::new(sth_ccap.1));
lth.metrics lth.metrics
.unrealized .unrealized
.investor_cap_in_profit_raw .capitalized_cap_in_profit_raw
.push(CentsSquaredSats::new(lth_icap.0)); .push(CentsSquaredSats::new(lth_ccap.0));
lth.metrics lth.metrics
.unrealized .unrealized
.investor_cap_in_loss_raw .capitalized_cap_in_loss_raw
.push(CentsSquaredSats::new(lth_icap.1)); .push(CentsSquaredSats::new(lth_ccap.1));
} }
} }
@@ -1,6 +1,7 @@
use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path}; use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES}; use brk_cohort::{AGE_RANGE_NAMES, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES};
use rayon::prelude::*;
use brk_error::Result; use brk_error::Result;
use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Sats}; use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Sats};
@@ -57,6 +58,29 @@ impl UTXOCohorts {
fn write_disk_distributions(&mut self, date: Date, states_path: &Path) -> Result<()> { fn write_disk_distributions(&mut self, date: Date, states_path: &Path) -> Result<()> {
let sth_filter = self.sth.metrics.filter.clone(); let sth_filter = self.sth.metrics.filter.clone();
self.age_range
.iter()
.zip(AGE_RANGE_NAMES.iter())
.collect::<Vec<_>>()
.into_par_iter()
.try_for_each(|(sub, name)| -> Result<()> {
let Some(state) = sub.state.as_ref() else {
return Ok(());
};
let mut merged: Vec<(CentsCompact, Sats)> = Vec::new();
for (&price, &sats) in state.cost_basis_map().iter() {
let rounded = price.round_to_dollar(COST_BASIS_PRICE_DIGITS);
if let Some(last) = merged.last_mut()
&& last.0 == rounded
{
last.1 += sats;
} else {
merged.push((rounded, sats));
}
}
write_distribution(states_path, name.id, date, merged)
})?;
let maps: Vec<_> = self let maps: Vec<_> = self
.age_range .age_range
.iter() .iter()
@@ -84,9 +108,13 @@ impl UTXOCohorts {
merge_k_way(&maps, &mut targets); merge_k_way(&maps, &mut targets);
write_distribution(states_path, "all", date, targets.all.merged)?; [
write_distribution(states_path, TERM_NAMES.short.id, date, targets.sth.merged)?; ("all", targets.all.merged),
write_distribution(states_path, TERM_NAMES.long.id, date, targets.lth.merged)?; (TERM_NAMES.short.id, targets.sth.merged),
(TERM_NAMES.long.id, targets.lth.merged),
]
.into_par_iter()
.try_for_each(|(name, merged)| write_distribution(states_path, name, date, merged))?;
Ok(()) Ok(())
} }
@@ -11,7 +11,10 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec, unli
use crate::{ use crate::{
distribution::{ distribution::{
addr::{AddrTypeToActivityCounts, AddrTypeToAddrCount}, addr::{
AddrTypeToActivityCounts, AddrTypeToAddrCount, AddrTypeToExposedAddrCount,
AddrTypeToExposedSupply, AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount,
},
block::{ block::{
AddrCache, InputsResult, process_inputs, process_outputs, process_received, AddrCache, InputsResult, process_inputs, process_outputs, process_received,
process_sent, process_sent,
@@ -192,22 +195,49 @@ pub(crate) fn process_blocks(
// Track running totals - recover from previous height if resuming // Track running totals - recover from previous height if resuming
debug!("recovering addr_counts from height {}", starting_height); debug!("recovering addr_counts from height {}", starting_height);
let (mut addr_counts, mut empty_addr_counts) = if starting_height > Height::ZERO { let (
let addr_counts = mut addr_counts,
AddrTypeToAddrCount::from((&vecs.addrs.funded.by_addr_type, starting_height)); mut empty_addr_counts,
let empty_addr_counts = mut reused_addr_counts,
AddrTypeToAddrCount::from((&vecs.addrs.empty.by_addr_type, starting_height)); mut total_reused_addr_counts,
(addr_counts, empty_addr_counts) mut exposed_addr_counts,
mut total_exposed_addr_counts,
mut exposed_supply,
) = if starting_height > Height::ZERO {
(
AddrTypeToAddrCount::from((&vecs.addrs.funded.by_addr_type, starting_height)),
AddrTypeToAddrCount::from((&vecs.addrs.empty.by_addr_type, starting_height)),
AddrTypeToReusedAddrCount::from((&vecs.addrs.reused.count.funded, starting_height)),
AddrTypeToReusedAddrCount::from((&vecs.addrs.reused.count.total, starting_height)),
AddrTypeToExposedAddrCount::from((&vecs.addrs.exposed.count.funded, starting_height)),
AddrTypeToExposedAddrCount::from((&vecs.addrs.exposed.count.total, starting_height)),
AddrTypeToExposedSupply::from((&vecs.addrs.exposed.supply, starting_height)),
)
} else { } else {
( (
AddrTypeToAddrCount::default(), AddrTypeToAddrCount::default(),
AddrTypeToAddrCount::default(), AddrTypeToAddrCount::default(),
AddrTypeToReusedAddrCount::default(),
AddrTypeToReusedAddrCount::default(),
AddrTypeToExposedAddrCount::default(),
AddrTypeToExposedAddrCount::default(),
AddrTypeToExposedSupply::default(),
) )
}; };
debug!("addr_counts recovered"); debug!("addr_counts recovered");
// Track activity counts - reset each block // Track activity counts - reset each block
let mut activity_counts = AddrTypeToActivityCounts::default(); let mut activity_counts = AddrTypeToActivityCounts::default();
// Reused-addr event counts (receive + spend side). Per-block
// flow, reset each block.
let mut output_to_reused_addr_counts = AddrTypeToReusedAddrEventCount::default();
let mut input_from_reused_addr_counts = AddrTypeToReusedAddrEventCount::default();
// Distinct addresses active this block whose lifetime
// funded_txo_count > 1 after this block's events. Incremented in
// process_received for every receiver that ends up reused, and in
// process_sent for every sender that's reused AND didn't also
// receive this block (deduped via `received_addrs`).
let mut active_reused_addr_counts = AddrTypeToReusedAddrEventCount::default();
debug!("creating AddrCache"); debug!("creating AddrCache");
let mut cache = AddrCache::new(); let mut cache = AddrCache::new();
@@ -226,6 +256,9 @@ pub(crate) fn process_blocks(
.chain(vecs.addrs.funded.par_iter_height_mut()) .chain(vecs.addrs.funded.par_iter_height_mut())
.chain(vecs.addrs.empty.par_iter_height_mut()) .chain(vecs.addrs.empty.par_iter_height_mut())
.chain(vecs.addrs.activity.par_iter_height_mut()) .chain(vecs.addrs.activity.par_iter_height_mut())
.chain(vecs.addrs.reused.par_iter_height_mut())
.chain(vecs.addrs.exposed.par_iter_height_mut())
.chain(vecs.addrs.avg_amount.par_iter_height_mut())
.chain(rayon::iter::once( .chain(rayon::iter::once(
&mut vecs.coinblocks_destroyed.block as &mut dyn AnyStoredVec, &mut vecs.coinblocks_destroyed.block as &mut dyn AnyStoredVec,
)) ))
@@ -278,6 +311,9 @@ pub(crate) fn process_blocks(
// Reset per-block activity counts // Reset per-block activity counts
activity_counts.reset(); activity_counts.reset();
output_to_reused_addr_counts.reset();
input_from_reused_addr_counts.reset();
active_reused_addr_counts.reset();
// Process outputs, inputs, and tick-tock in parallel via rayon::join. // Process outputs, inputs, and tick-tock in parallel via rayon::join.
// Collection (build tx_index mappings + bulk mmap reads) is merged into the // Collection (build tx_index mappings + bulk mmap reads) is merged into the
@@ -447,6 +483,13 @@ pub(crate) fn process_blocks(
&mut addr_counts, &mut addr_counts,
&mut empty_addr_counts, &mut empty_addr_counts,
&mut activity_counts, &mut activity_counts,
&mut reused_addr_counts,
&mut total_reused_addr_counts,
&mut output_to_reused_addr_counts,
&mut active_reused_addr_counts,
&mut exposed_addr_counts,
&mut total_exposed_addr_counts,
&mut exposed_supply,
); );
// Process sent inputs (addresses sending funds) // Process sent inputs (addresses sending funds)
@@ -459,6 +502,12 @@ pub(crate) fn process_blocks(
&mut addr_counts, &mut addr_counts,
&mut empty_addr_counts, &mut empty_addr_counts,
&mut activity_counts, &mut activity_counts,
&mut reused_addr_counts,
&mut input_from_reused_addr_counts,
&mut active_reused_addr_counts,
&mut exposed_addr_counts,
&mut total_exposed_addr_counts,
&mut exposed_supply,
&received_addrs, &received_addrs,
height_to_price_vec, height_to_price_vec,
height_to_timestamp_vec, height_to_timestamp_vec,
@@ -481,6 +530,36 @@ pub(crate) fn process_blocks(
.empty .empty
.push_height(empty_addr_counts.sum(), &empty_addr_counts); .push_height(empty_addr_counts.sum(), &empty_addr_counts);
vecs.addrs.activity.push_height(&activity_counts); vecs.addrs.activity.push_height(&activity_counts);
vecs.addrs.reused.count.funded.push_height(
reused_addr_counts.sum(),
reused_addr_counts.values().copied(),
);
vecs.addrs.reused.count.total.push_height(
total_reused_addr_counts.sum(),
total_reused_addr_counts.values().copied(),
);
let activity_totals = activity_counts.totals();
let active_addr_count =
activity_totals.sending + activity_totals.receiving - activity_totals.bidirectional;
let active_reused = u32::try_from(active_reused_addr_counts.sum()).unwrap();
vecs.addrs.reused.events.push_height(
&output_to_reused_addr_counts,
&input_from_reused_addr_counts,
active_addr_count,
active_reused,
);
vecs.addrs.exposed.count.funded.push_height(
exposed_addr_counts.sum(),
exposed_addr_counts.values().copied(),
);
vecs.addrs.exposed.count.total.push_height(
total_exposed_addr_counts.sum(),
total_exposed_addr_counts.values().copied(),
);
vecs.addrs
.exposed
.supply
.push_height(exposed_supply.sum(), exposed_supply.values().copied());
let is_last_of_day = is_last_of_day[offset]; let is_last_of_day = is_last_of_day[offset];
let date_opt = is_last_of_day.then(|| Date::from(timestamp)); let date_opt = is_last_of_day.then(|| Date::from(timestamp));
@@ -553,7 +632,7 @@ fn push_cohort_states(
height: Height, height: Height,
height_price: Cents, height_price: Cents,
) { ) {
// Phase 1: push + unrealized (no reset yet states still needed for aggregation) // Phase 1: push + unrealized (no reset yet; states still needed for aggregation)
rayon::join( rayon::join(
|| { || {
utxo_cohorts.par_iter_separate_mut().for_each(|v| { utxo_cohorts.par_iter_separate_mut().for_each(|v| {
@@ -79,6 +79,8 @@ pub(crate) fn write(
.chain(vecs.addrs.funded.par_iter_height_mut()) .chain(vecs.addrs.funded.par_iter_height_mut())
.chain(vecs.addrs.empty.par_iter_height_mut()) .chain(vecs.addrs.empty.par_iter_height_mut())
.chain(vecs.addrs.activity.par_iter_height_mut()) .chain(vecs.addrs.activity.par_iter_height_mut())
.chain(vecs.addrs.reused.par_iter_height_mut())
.chain(vecs.addrs.exposed.par_iter_height_mut())
.chain( .chain(
[ [
&mut vecs.supply_state as &mut dyn AnyStoredVec, &mut vecs.supply_state as &mut dyn AnyStoredVec,
@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Indexes, StoredF32, StoredF64, Version}; use brk_types::{Indexes, StoredF32, StoredF64, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, ReadableCloneableVec, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::internal::{Identity, LazyPerBlock, PerBlock, Windows}; use crate::internal::{Identity, LazyPerBlock, PerBlock, Windows};
@@ -33,15 +33,10 @@ impl ActivityFull {
let v1 = Version::ONE; let v1 = Version::ONE;
let inner = ActivityCore::forced_import(cfg)?; let inner = ActivityCore::forced_import(cfg)?;
let coinyears_destroyed = LazyPerBlock::from_height_source::<Identity<StoredF64>>( let coinyears_destroyed = LazyPerBlock::from_height_source::<Identity<StoredF64>, _>(
&cfg.name("coinyears_destroyed"), &cfg.name("coinyears_destroyed"),
cfg.version + v1, cfg.version + v1,
inner inner.coindays_destroyed.sum._1y.height.clone(),
.coindays_destroyed
.sum
._1y
.height
.read_only_boxed_clone(),
cfg.indexes, cfg.indexes,
); );
@@ -2,8 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, Indexes, Version}; use brk_types::{Cents, Dollars, Height, Indexes, Version};
use vecdb::AnyStoredVec; use vecdb::{AnyStoredVec, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode};
use vecdb::{Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
blocks, blocks,
@@ -119,7 +118,7 @@ impl AllCohortMetrics {
self.unrealized.compute( self.unrealized.compute(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized.price.cents.height, &self.realized.price.cents.height,
exit, exit,
)?; )?;
@@ -139,23 +138,28 @@ impl AllCohortMetrics {
self.cost_basis.compute_prices( self.cost_basis.compute_prices(
starting_indexes, starting_indexes,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.unrealized.invested_capital.in_profit.cents.height, &self.unrealized.invested_capital.in_profit.cents.height,
&self.unrealized.invested_capital.in_loss.cents.height, &self.unrealized.invested_capital.in_loss.cents.height,
&self.supply.in_profit.sats.height, &self.supply.in_profit.sats.height,
&self.supply.in_loss.sats.height, &self.supply.in_loss.sats.height,
&self.unrealized.investor_cap_in_profit_raw, &self.unrealized.capitalized_cap_in_profit_raw,
&self.unrealized.investor_cap_in_loss_raw, &self.unrealized.capitalized_cap_in_loss_raw,
exit, exit,
)?; )?;
self.unrealized self.unrealized
.compute_sentiment(starting_indexes, &prices.cached_spot_cents, exit)?; .compute_sentiment(starting_indexes, &prices.spot.cents.height, exit)?;
let own_supply_sats = self.supply.total.sats.height.read_only_clone();
self.supply
.compute_dominance(starting_indexes.height, &own_supply_sats, exit)?;
self.relative.compute( self.relative.compute(
starting_indexes.height, starting_indexes.height,
&self.supply, &self.supply,
&self.unrealized, &self.unrealized,
&self.realized,
height_to_market_cap, height_to_market_cap,
exit, exit,
)?; )?;
@@ -6,14 +6,13 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
distribution::metrics::{ distribution::metrics::{
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, RelativeToAll, ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
SupplyCore, UnrealizedCore, UnrealizedCore,
}, },
prices, prices,
}; };
/// Basic cohort metrics: no extensions, with relative (rel_to_all). /// Basic cohort metrics: no extensions, used by age_range cohorts.
/// Used by: age_range cohorts.
#[derive(Traversable)] #[derive(Traversable)]
pub struct BasicCohortMetrics<M: StorageMode = Rw> { pub struct BasicCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)] #[traversable(skip)]
@@ -23,8 +22,6 @@ pub struct BasicCohortMetrics<M: StorageMode = Rw> {
pub activity: Box<ActivityCore<M>>, pub activity: Box<ActivityCore<M>>,
pub realized: Box<RealizedCore<M>>, pub realized: Box<RealizedCore<M>>,
pub unrealized: Box<UnrealizedCore<M>>, pub unrealized: Box<UnrealizedCore<M>>,
#[traversable(flatten)]
pub relative: Box<RelativeToAll<M>>,
} }
impl CohortMetricsBase for BasicCohortMetrics { impl CohortMetricsBase for BasicCohortMetrics {
@@ -51,8 +48,6 @@ impl BasicCohortMetrics {
let unrealized = UnrealizedCore::forced_import(cfg)?; let unrealized = UnrealizedCore::forced_import(cfg)?;
let realized = RealizedCore::forced_import(cfg)?; let realized = RealizedCore::forced_import(cfg)?;
let relative = RelativeToAll::forced_import(cfg)?;
Ok(Self { Ok(Self {
filter: cfg.filter.clone(), filter: cfg.filter.clone(),
supply: Box::new(supply), supply: Box::new(supply),
@@ -60,7 +55,6 @@ impl BasicCohortMetrics {
activity: Box::new(ActivityCore::forced_import(cfg)?), activity: Box::new(ActivityCore::forced_import(cfg)?),
realized: Box::new(realized), realized: Box::new(realized),
unrealized: Box::new(unrealized), unrealized: Box::new(unrealized),
relative: Box::new(relative),
}) })
} }
@@ -82,13 +76,13 @@ impl BasicCohortMetrics {
self.unrealized.compute( self.unrealized.compute(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized.price.cents.height, &self.realized.price.cents.height,
exit, exit,
)?; )?;
self.relative self.supply
.compute(starting_indexes.height, &self.supply, all_supply_sats, exit)?; .compute_dominance(starting_indexes.height, all_supply_sats, exit)?;
self.outputs self.outputs
.compute_part2(starting_indexes.height, all_utxo_count, exit)?; .compute_part2(starting_indexes.height, all_utxo_count, exit)?;
@@ -6,8 +6,8 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
distribution::metrics::{ distribution::metrics::{
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, RelativeToAll, ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
SupplyCore, UnrealizedCore, UnrealizedCore,
}, },
prices, prices,
}; };
@@ -21,8 +21,6 @@ pub struct CoreCohortMetrics<M: StorageMode = Rw> {
pub activity: Box<ActivityCore<M>>, pub activity: Box<ActivityCore<M>>,
pub realized: Box<RealizedCore<M>>, pub realized: Box<RealizedCore<M>>,
pub unrealized: Box<UnrealizedCore<M>>, pub unrealized: Box<UnrealizedCore<M>>,
#[traversable(flatten)]
pub relative: Box<RelativeToAll<M>>,
} }
impl CoreCohortMetrics { impl CoreCohortMetrics {
@@ -34,7 +32,6 @@ impl CoreCohortMetrics {
activity: Box::new(ActivityCore::forced_import(cfg)?), activity: Box::new(ActivityCore::forced_import(cfg)?),
realized: Box::new(RealizedCore::forced_import(cfg)?), realized: Box::new(RealizedCore::forced_import(cfg)?),
unrealized: Box::new(UnrealizedCore::forced_import(cfg)?), unrealized: Box::new(UnrealizedCore::forced_import(cfg)?),
relative: Box::new(RelativeToAll::forced_import(cfg)?),
}) })
} }
@@ -140,13 +137,13 @@ impl CoreCohortMetrics {
self.unrealized.compute( self.unrealized.compute(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized.price.cents.height, &self.realized.price.cents.height,
exit, exit,
)?; )?;
self.relative self.supply
.compute(starting_indexes.height, &self.supply, all_supply_sats, exit)?; .compute_dominance(starting_indexes.height, all_supply_sats, exit)?;
self.outputs self.outputs
.compute_part2(starting_indexes.height, all_utxo_count, exit)?; .compute_part2(starting_indexes.height, all_utxo_count, exit)?;
@@ -108,32 +108,35 @@ impl ExtendedCohortMetrics {
self.unrealized.compute( self.unrealized.compute(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized.price.cents.height, &self.realized.price.cents.height,
exit, exit,
)?; )?;
self.cost_basis.compute_prices( self.cost_basis.compute_prices(
starting_indexes, starting_indexes,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.unrealized.invested_capital.in_profit.cents.height, &self.unrealized.invested_capital.in_profit.cents.height,
&self.unrealized.invested_capital.in_loss.cents.height, &self.unrealized.invested_capital.in_loss.cents.height,
&self.supply.in_profit.sats.height, &self.supply.in_profit.sats.height,
&self.supply.in_loss.sats.height, &self.supply.in_loss.sats.height,
&self.unrealized.investor_cap_in_profit_raw, &self.unrealized.capitalized_cap_in_profit_raw,
&self.unrealized.investor_cap_in_loss_raw, &self.unrealized.capitalized_cap_in_loss_raw,
exit, exit,
)?; )?;
self.unrealized self.unrealized
.compute_sentiment(starting_indexes, &prices.cached_spot_cents, exit)?; .compute_sentiment(starting_indexes, &prices.spot.cents.height, exit)?;
self.supply
.compute_dominance(starting_indexes.height, all_supply_sats, exit)?;
self.relative.compute( self.relative.compute(
starting_indexes.height, starting_indexes.height,
&self.supply, &self.supply,
&self.unrealized, &self.unrealized,
&self.realized,
height_to_market_cap, height_to_market_cap,
all_supply_sats,
&self.supply.total.usd.height, &self.supply.total.usd.height,
exit, exit,
)?; )?;
@@ -1,7 +1,7 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, Indexes, StoredU64}; use brk_types::{Height, Indexes, Sats, StoredU64};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
@@ -112,6 +112,7 @@ impl MinimalCohortMetrics {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>, all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
@@ -124,11 +125,14 @@ impl MinimalCohortMetrics {
self.unrealized.compute( self.unrealized.compute(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized.price.cents.height, &self.realized.price.cents.height,
exit, exit,
)?; )?;
self.supply
.compute_dominance(starting_indexes.height, all_supply_sats, exit)?;
self.outputs self.outputs
.compute_part2(starting_indexes.height, all_utxo_count, exit)?; .compute_part2(starting_indexes.height, all_utxo_count, exit)?;
@@ -1,7 +1,7 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, Indexes, StoredU64}; use brk_types::{Height, Indexes, Sats, StoredU64};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
@@ -74,6 +74,7 @@ impl TypeCohortMetrics {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>, all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
@@ -86,11 +87,14 @@ impl TypeCohortMetrics {
self.unrealized.compute( self.unrealized.compute(
starting_indexes.height, starting_indexes.height,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized.price.cents.height, &self.realized.price.cents.height,
exit, exit,
)?; )?;
self.supply
.compute_dominance(starting_indexes.height, all_supply_sats, exit)?;
self.outputs self.outputs
.compute_part2(starting_indexes.height, all_utxo_count, exit)?; .compute_part2(starting_indexes.height, all_utxo_count, exit)?;
@@ -7,11 +7,11 @@ use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockCumulativeRolling, AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockCumulativeRolling, CentsType,
CachedWindowStarts, CentsType, FiatPerBlock, FiatPerBlockCumulativeWithSums, NumericValue, FiatPerBlock, FiatPerBlockCumulativeWithSums, NumericValue, PerBlock,
PerBlock, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, Price, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, Price,
PriceWithRatioExtendedPerBlock, PriceWithRatioPerBlock, RatioPerBlock, PriceWithRatioExtendedPerBlock, PriceWithRatioPerBlock, RatioPerBlock,
RollingWindow24hPerBlock, RollingWindows, RollingWindowsFrom1w, RollingWindow24hPerBlock, RollingWindows, RollingWindowsFrom1w, WindowStartVec, Windows,
}, },
}; };
@@ -128,7 +128,7 @@ pub struct ImportConfig<'a> {
pub full_name: &'a str, pub full_name: &'a str,
pub version: Version, pub version: Version,
pub indexes: &'a indexes::Vecs, pub indexes: &'a indexes::Vecs,
pub cached_starts: &'a CachedWindowStarts, pub cached_starts: &'a Windows<&'a WindowStartVec>,
} }
impl<'a> ImportConfig<'a> { impl<'a> ImportConfig<'a> {
@@ -153,8 +153,8 @@ impl CostBasis {
invested_cap_in_loss: &impl ReadableVec<Height, Cents>, invested_cap_in_loss: &impl ReadableVec<Height, Cents>,
supply_in_profit_sats: &impl ReadableVec<Height, Sats>, supply_in_profit_sats: &impl ReadableVec<Height, Sats>,
supply_in_loss_sats: &impl ReadableVec<Height, Sats>, supply_in_loss_sats: &impl ReadableVec<Height, Sats>,
investor_cap_in_profit_raw: &impl ReadableVec<Height, CentsSquaredSats>, capitalized_cap_in_profit_raw: &impl ReadableVec<Height, CentsSquaredSats>,
investor_cap_in_loss_raw: &impl ReadableVec<Height, CentsSquaredSats>, capitalized_cap_in_loss_raw: &impl ReadableVec<Height, CentsSquaredSats>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.in_profit.per_coin.cents.height.compute_transform3( self.in_profit.per_coin.cents.height.compute_transform3(
@@ -193,29 +193,29 @@ impl CostBasis {
)?; )?;
self.in_profit.per_dollar.cents.height.compute_transform3( self.in_profit.per_dollar.cents.height.compute_transform3(
starting_indexes.height, starting_indexes.height,
investor_cap_in_profit_raw, capitalized_cap_in_profit_raw,
invested_cap_in_profit, invested_cap_in_profit,
spot, spot,
|(h, investor_cap, invested_cents, spot, ..)| { |(h, capitalized_cap, invested_cents, spot, ..)| {
let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128; let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128;
if invested_raw == 0 { if invested_raw == 0 {
return (h, spot); return (h, spot);
} }
(h, Cents::new((investor_cap.inner() / invested_raw) as u64)) (h, Cents::new((capitalized_cap.inner() / invested_raw) as u64))
}, },
exit, exit,
)?; )?;
self.in_loss.per_dollar.cents.height.compute_transform3( self.in_loss.per_dollar.cents.height.compute_transform3(
starting_indexes.height, starting_indexes.height,
investor_cap_in_loss_raw, capitalized_cap_in_loss_raw,
invested_cap_in_loss, invested_cap_in_loss,
spot, spot,
|(h, investor_cap, invested_cents, spot, ..)| { |(h, capitalized_cap, invested_cents, spot, ..)| {
let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128; let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128;
if invested_raw == 0 { if invested_raw == 0 {
return (h, spot); return (h, spot);
} }
(h, Cents::new((investor_cap.inner() / invested_raw) as u64)) (h, Cents::new((capitalized_cap.inner() / invested_raw) as u64))
}, },
exit, exit,
)?; )?;
@@ -132,8 +132,8 @@ pub use profitability::ProfitabilityMetrics;
pub use realized::{ pub use realized::{
AdjustedSopr, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike, RealizedMinimal, AdjustedSopr, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike, RealizedMinimal,
}; };
pub use relative::{RelativeForAll, RelativeToAll, RelativeWithExtended}; pub use relative::{RelativeForAll, RelativeWithExtended};
pub use supply::{SupplyBase, SupplyCore}; pub use supply::{AvgAmountMetrics, SupplyBase, SupplyCore};
pub use unrealized::{ pub use unrealized::{
UnrealizedBasic, UnrealizedCore, UnrealizedFull, UnrealizedLike, UnrealizedMinimal, UnrealizedBasic, UnrealizedCore, UnrealizedFull, UnrealizedLike, UnrealizedMinimal,
}; };
@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
AmountPerBlock, AmountPerBlockWithDeltas, CachedWindowStarts, PerBlock, RatioPerBlock, AmountPerBlock, AmountPerBlockWithDeltas, PerBlock, RatioPerBlock, WindowStartVec, Windows,
}, },
prices, prices,
}; };
@@ -43,7 +43,7 @@ impl ProfitabilityBucket {
name: &str, name: &str,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
supply: WithSth { supply: WithSth {
@@ -126,7 +126,7 @@ impl ProfitabilityBucket {
self.unrealized_pnl.all.height.compute_transform3( self.unrealized_pnl.all.height.compute_transform3(
max_from, max_from,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized_cap.all.height, &self.realized_cap.all.height,
&self.supply.all.sats.height, &self.supply.all.sats.height,
|(i, spot, cap, supply, ..)| { |(i, spot, cap, supply, ..)| {
@@ -139,7 +139,7 @@ impl ProfitabilityBucket {
)?; )?;
self.unrealized_pnl.sth.height.compute_transform3( self.unrealized_pnl.sth.height.compute_transform3(
max_from, max_from,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized_cap.sth.height, &self.realized_cap.sth.height,
&self.supply.sth.sats.height, &self.supply.sth.sats.height,
|(i, spot, cap, supply, ..)| { |(i, spot, cap, supply, ..)| {
@@ -153,7 +153,7 @@ impl ProfitabilityBucket {
self.nupl.bps.height.compute_transform3( self.nupl.bps.height.compute_transform3(
max_from, max_from,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.realized_cap.all.height, &self.realized_cap.all.height,
&self.supply.all.sats.height, &self.supply.all.sats.height,
|(i, spot, cap_dollars, supply_sats, ..)| { |(i, spot, cap_dollars, supply_sats, ..)| {
@@ -267,7 +267,7 @@ impl ProfitabilityMetrics {
db: &Database, db: &Database,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let range = ProfitabilityRange::try_new(|name| { let range = ProfitabilityRange::try_new(|name| {
ProfitabilityBucket::forced_import(db, name, version, indexes, cached_starts) ProfitabilityBucket::forced_import(db, name, version, indexes, cached_starts)
@@ -62,10 +62,10 @@ impl RealizedCore {
); );
let neg_loss_sum = minimal.loss.sum.0.map_with_suffix(|suffix, slot| { let neg_loss_sum = minimal.loss.sum.0.map_with_suffix(|suffix, slot| {
LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars>( LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars, _>(
&cfg.name(&format!("realized_loss_neg_sum_{suffix}")), &cfg.name(&format!("realized_loss_neg_sum_{suffix}")),
cfg.version + Version::ONE, cfg.version + Version::ONE,
slot.cents.height.read_only_boxed_clone(), slot.cents.height.clone(),
cfg.indexes, cfg.indexes,
) )
}); });
@@ -45,7 +45,7 @@ pub struct RealizedPeakRegret<M: StorageMode = Rw> {
} }
#[derive(Traversable)] #[derive(Traversable)]
pub struct RealizedInvestor<M: StorageMode = Rw> { pub struct RealizedCapitalized<M: StorageMode = Rw> {
pub price: PriceWithRatioExtendedPerBlock<M>, pub price: PriceWithRatioExtendedPerBlock<M>,
#[traversable(hidden)] #[traversable(hidden)]
pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>, pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
@@ -63,7 +63,7 @@ pub struct RealizedFull<M: StorageMode = Rw> {
pub net_pnl: RealizedNetPnl<M>, pub net_pnl: RealizedNetPnl<M>,
pub sopr: RealizedSopr<M>, pub sopr: RealizedSopr<M>,
pub peak_regret: RealizedPeakRegret<M>, pub peak_regret: RealizedPeakRegret<M>,
pub investor: RealizedInvestor<M>, pub capitalized: RealizedCapitalized<M>,
pub profit_to_loss_ratio: RollingWindows<StoredF64, M>, pub profit_to_loss_ratio: RollingWindows<StoredF64, M>,
@@ -108,10 +108,10 @@ impl RealizedFull {
value: cfg.import("realized_peak_regret", Version::new(3))?, value: cfg.import("realized_peak_regret", Version::new(3))?,
}; };
// Investor // Capitalized
let investor = RealizedInvestor { let capitalized = RealizedCapitalized {
price: cfg.import("investor_price", v0)?, price: cfg.import("capitalized_price", v0)?,
cap_raw: cfg.import("investor_cap_raw", v0)?, cap_raw: cfg.import("capitalized_cap_raw", v0)?,
}; };
// Price ratio stats // Price ratio stats
@@ -125,7 +125,7 @@ impl RealizedFull {
net_pnl, net_pnl,
sopr, sopr,
peak_regret, peak_regret,
investor, capitalized,
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?, profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
cap_raw: cfg.import("cap_raw", v0)?, cap_raw: cfg.import("cap_raw", v0)?,
cap_to_own_mcap: cfg.import("realized_cap_to_own_mcap", v1)?, cap_to_own_mcap: cfg.import("realized_cap_to_own_mcap", v1)?,
@@ -151,13 +151,13 @@ impl RealizedFull {
} }
pub(crate) fn min_stateful_len(&self) -> usize { pub(crate) fn min_stateful_len(&self) -> usize {
self.investor self.capitalized
.price .price
.cents .cents
.height .height
.len() .len()
.min(self.cap_raw.len()) .min(self.cap_raw.len())
.min(self.investor.cap_raw.len()) .min(self.capitalized.cap_raw.len())
.min(self.peak_regret.value.block.cents.len()) .min(self.peak_regret.value.block.cents.len())
} }
@@ -167,15 +167,15 @@ impl RealizedFull {
state: &CohortState<RealizedState, CostBasisData<WithCapital>>, state: &CohortState<RealizedState, CostBasisData<WithCapital>>,
) { ) {
self.core.push_state(state); self.core.push_state(state);
self.investor self.capitalized
.price .price
.cents .cents
.height .height
.push(state.realized.investor_price()); .push(state.realized.capitalized_price());
self.cap_raw.push(state.realized.cap_raw()); self.cap_raw.push(state.realized.cap_raw());
self.investor self.capitalized
.cap_raw .cap_raw
.push(state.realized.investor_cap_raw()); .push(state.realized.capitalized_cap_raw());
self.peak_regret self.peak_regret
.value .value
.block .block
@@ -185,9 +185,9 @@ impl RealizedFull {
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.core.collect_vecs_mut(); let mut vecs = self.core.collect_vecs_mut();
vecs.push(&mut self.investor.price.cents.height); vecs.push(&mut self.capitalized.price.cents.height);
vecs.push(&mut self.cap_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.cap_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.investor.cap_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.capitalized.cap_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.peak_regret.value.block.cents); vecs.push(&mut self.peak_regret.value.block.cents);
vecs vecs
} }
@@ -207,17 +207,17 @@ impl RealizedFull {
#[inline(always)] #[inline(always)]
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) { pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) {
self.cap_raw.push(accum.cap_raw); self.cap_raw.push(accum.cap_raw);
self.investor.cap_raw.push(accum.investor_cap_raw); self.capitalized.cap_raw.push(accum.capitalized_cap_raw);
let investor_price = { let capitalized_price = {
let cap = accum.cap_raw.as_u128(); let cap = accum.cap_raw.as_u128();
if cap == 0 { if cap == 0 {
Cents::ZERO Cents::ZERO
} else { } else {
Cents::new((accum.investor_cap_raw / cap) as u64) Cents::new((accum.capitalized_cap_raw / cap) as u64)
} }
}; };
self.investor.price.cents.height.push(investor_price); self.capitalized.price.cents.height.push(capitalized_price);
self.peak_regret.value.block.cents.push(accum.peak_regret()); self.peak_regret.value.block.cents.push(accum.peak_regret());
} }
@@ -298,8 +298,8 @@ impl RealizedFull {
exit, exit,
)?; )?;
// Investor price ratio, percentiles and bands // Capitalized price ratio, percentiles and bands
self.investor self.capitalized
.price .price
.compute_rest(prices, starting_indexes, exit)?; .compute_rest(prices, starting_indexes, exit)?;
@@ -374,14 +374,14 @@ impl RealizedFull {
#[derive(Default)] #[derive(Default)]
pub struct RealizedFullAccum { pub struct RealizedFullAccum {
pub(crate) cap_raw: CentsSats, pub(crate) cap_raw: CentsSats,
pub(crate) investor_cap_raw: CentsSquaredSats, pub(crate) capitalized_cap_raw: CentsSquaredSats,
peak_regret: CentsSats, peak_regret: CentsSats,
} }
impl RealizedFullAccum { impl RealizedFullAccum {
pub(crate) fn add(&mut self, state: &RealizedState) { pub(crate) fn add(&mut self, state: &RealizedState) {
self.cap_raw += state.cap_raw(); self.cap_raw += state.cap_raw();
self.investor_cap_raw += state.investor_cap_raw(); self.capitalized_cap_raw += state.capitalized_cap_raw();
self.peak_regret += CentsSats::new(state.peak_regret_raw()); self.peak_regret += CentsSats::new(state.peak_regret_raw());
} }
@@ -118,11 +118,11 @@ impl RealizedMinimal {
|(i, cap_cents, supply, ..)| { |(i, cap_cents, supply, ..)| {
let cap = cap_cents.as_u128(); let cap = cap_cents.as_u128();
let supply_sats = Sats::from(supply).as_u128(); let supply_sats = Sats::from(supply).as_u128();
if supply_sats == 0 { let cents = (cap * Sats::ONE_BTC_U128)
(i, Cents::ZERO) .checked_div(supply_sats)
} else { .map(Cents::from)
(i, Cents::from(cap * Sats::ONE_BTC_U128 / supply_sats)) .unwrap_or(Cents::ZERO);
} (i, cents)
}, },
exit, exit,
)?) )?)
@@ -4,9 +4,9 @@ use brk_types::{Dollars, Height};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, SupplyCore, UnrealizedFull}; use crate::distribution::metrics::{ImportConfig, RealizedFull, SupplyCore, UnrealizedFull};
use super::{RelativeExtendedOwnPnl, RelativeFull}; use super::{RelativeExtendedOwnPnl, RelativeFull, RelativeInvestedCapital};
/// Relative metrics for the "all" cohort (base + own_pnl, NO rel_to_all). /// Relative metrics for the "all" cohort (base + own_pnl, NO rel_to_all).
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
@@ -17,6 +17,8 @@ pub struct RelativeForAll<M: StorageMode = Rw> {
pub base: RelativeFull<M>, pub base: RelativeFull<M>,
#[traversable(flatten)] #[traversable(flatten)]
pub extended_own_pnl: RelativeExtendedOwnPnl<M>, pub extended_own_pnl: RelativeExtendedOwnPnl<M>,
#[traversable(flatten)]
pub invested_capital: RelativeInvestedCapital<M>,
} }
impl RelativeForAll { impl RelativeForAll {
@@ -24,6 +26,7 @@ impl RelativeForAll {
Ok(Self { Ok(Self {
base: RelativeFull::forced_import(cfg)?, base: RelativeFull::forced_import(cfg)?,
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?, extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?,
invested_capital: RelativeInvestedCapital::forced_import(cfg)?,
}) })
} }
@@ -32,6 +35,7 @@ impl RelativeForAll {
max_from: Height, max_from: Height,
supply: &SupplyCore, supply: &SupplyCore,
unrealized: &UnrealizedFull, unrealized: &UnrealizedFull,
realized: &RealizedFull,
market_cap: &impl ReadableVec<Height, Dollars>, market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
@@ -43,6 +47,8 @@ impl RelativeForAll {
&unrealized.gross_pnl.usd.height, &unrealized.gross_pnl.usd.height,
exit, exit,
)?; )?;
self.invested_capital
.compute(max_from, unrealized, realized, exit)?;
Ok(()) Ok(())
} }
} }
@@ -11,10 +11,10 @@ use crate::{
/// Full relative metrics (sth/lth/all tier). /// Full relative metrics (sth/lth/all tier).
#[derive(Traversable)] #[derive(Traversable)]
pub struct RelativeFull<M: StorageMode = Rw> { pub struct RelativeFull<M: StorageMode = Rw> {
#[traversable(wrap = "supply/in_profit", rename = "to_own")] #[traversable(wrap = "supply/in_profit", rename = "share")]
pub supply_in_profit_to_own: PercentPerBlock<BasisPoints16, M>, pub supply_in_profit_share: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "to_own")] #[traversable(wrap = "supply/in_loss", rename = "share")]
pub supply_in_loss_to_own: PercentPerBlock<BasisPoints16, M>, pub supply_in_loss_share: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/profit", rename = "to_mcap")] #[traversable(wrap = "unrealized/profit", rename = "to_mcap")]
pub unrealized_profit_to_mcap: PercentPerBlock<BasisPoints16, M>, pub unrealized_profit_to_mcap: PercentPerBlock<BasisPoints16, M>,
@@ -28,8 +28,8 @@ impl RelativeFull {
let v2 = Version::new(2); let v2 = Version::new(2);
Ok(Self { Ok(Self {
supply_in_profit_to_own: cfg.import("supply_in_profit_to_own", v1)?, supply_in_profit_share: cfg.import("supply_in_profit_share", v1)?,
supply_in_loss_to_own: cfg.import("supply_in_loss_to_own", v1)?, supply_in_loss_share: cfg.import("supply_in_loss_share", v1)?,
unrealized_profit_to_mcap: cfg.import("unrealized_profit_to_mcap", v2)?, unrealized_profit_to_mcap: cfg.import("unrealized_profit_to_mcap", v2)?,
unrealized_loss_to_mcap: cfg.import("unrealized_loss_to_mcap", v2)?, unrealized_loss_to_mcap: cfg.import("unrealized_loss_to_mcap", v2)?,
}) })
@@ -43,14 +43,14 @@ impl RelativeFull {
market_cap: &impl ReadableVec<Height, Dollars>, market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.supply_in_profit_to_own self.supply_in_profit_share
.compute_binary::<Sats, Sats, RatioSatsBp16>( .compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, max_from,
&supply.in_profit.sats.height, &supply.in_profit.sats.height,
&supply.total.sats.height, &supply.total.sats.height,
exit, exit,
)?; )?;
self.supply_in_loss_to_own self.supply_in_loss_share
.compute_binary::<Sats, Sats, RatioSatsBp16>( .compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, max_from,
&supply.in_loss.sats.height, &supply.in_loss.sats.height,
@@ -0,0 +1,53 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Cents, Height, Version};
use vecdb::{Exit, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioCentsBp16};
use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
/// Shares of invested capital in profit / in loss relative to own realized cap.
/// Present for cohorts with `UnrealizedFull` (all, sth, lth).
#[derive(Traversable)]
pub struct RelativeInvestedCapital<M: StorageMode = Rw> {
#[traversable(wrap = "invested_capital/in_profit", rename = "share")]
pub in_profit_share: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "invested_capital/in_loss", rename = "share")]
pub in_loss_share: PercentPerBlock<BasisPoints16, M>,
}
impl RelativeInvestedCapital {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
Ok(Self {
in_profit_share: cfg.import("invested_capital_in_profit_share", v0)?,
in_loss_share: cfg.import("invested_capital_in_loss_share", v0)?,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
realized: &RealizedFull,
exit: &Exit,
) -> Result<()> {
let realized_cap = &realized.core.minimal.cap.cents.height;
self.in_profit_share
.compute_binary::<Cents, Cents, RatioCentsBp16>(
max_from,
&unrealized.invested_capital.in_profit.cents.height,
realized_cap,
exit,
)?;
self.in_loss_share
.compute_binary::<Cents, Cents, RatioCentsBp16>(
max_from,
&unrealized.invested_capital.in_loss.cents.height,
realized_cap,
exit,
)?;
Ok(())
}
}
@@ -2,12 +2,12 @@ mod extended_own_market_cap;
mod extended_own_pnl; mod extended_own_pnl;
mod for_all; mod for_all;
mod full; mod full;
mod to_all; mod invested_capital;
mod with_extended; mod with_extended;
pub use extended_own_market_cap::RelativeExtendedOwnMarketCap; pub use extended_own_market_cap::RelativeExtendedOwnMarketCap;
pub use extended_own_pnl::RelativeExtendedOwnPnl; pub use extended_own_pnl::RelativeExtendedOwnPnl;
pub use for_all::RelativeForAll; pub use for_all::RelativeForAll;
pub use full::RelativeFull; pub use full::RelativeFull;
pub use to_all::RelativeToAll; pub use invested_capital::RelativeInvestedCapital;
pub use with_extended::RelativeWithExtended; pub use with_extended::RelativeWithExtended;
@@ -1,62 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, Sats, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioSatsBp16};
use crate::distribution::metrics::{ImportConfig, SupplyCore};
/// Relative-to-all metrics (not present for the "all" cohort itself).
#[derive(Traversable)]
pub struct RelativeToAll<M: StorageMode = Rw> {
#[traversable(wrap = "supply", rename = "to_circulating")]
pub supply_to_circulating: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_profit", rename = "to_circulating")]
pub supply_in_profit_to_circulating: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "to_circulating")]
pub supply_in_loss_to_circulating: PercentPerBlock<BasisPoints16, M>,
}
impl RelativeToAll {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
supply_to_circulating: cfg.import("supply_to_circulating", Version::ONE)?,
supply_in_profit_to_circulating: cfg
.import("supply_in_profit_to_circulating", Version::ONE)?,
supply_in_loss_to_circulating: cfg
.import("supply_in_loss_to_circulating", Version::ONE)?,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
supply: &SupplyCore,
all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {
self.supply_to_circulating
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.total.sats.height,
all_supply_sats,
exit,
)?;
self.supply_in_profit_to_circulating
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.in_profit.sats.height,
all_supply_sats,
exit,
)?;
self.supply_in_loss_to_circulating
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.in_loss.sats.height,
all_supply_sats,
exit,
)?;
Ok(())
}
}
@@ -1,14 +1,16 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats}; use brk_types::{Dollars, Height};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, SupplyCore, UnrealizedFull}; use crate::distribution::metrics::{ImportConfig, RealizedFull, SupplyCore, UnrealizedFull};
use super::{RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeFull, RelativeToAll}; use super::{
RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeFull, RelativeInvestedCapital,
};
/// Full extended relative metrics (base + rel_to_all + own_market_cap + own_pnl). /// Full extended relative metrics (base + own_market_cap + own_pnl + invested_capital).
/// Used by: sth, lth cohorts. /// Used by: sth, lth cohorts.
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct RelativeWithExtended<M: StorageMode = Rw> { pub struct RelativeWithExtended<M: StorageMode = Rw> {
@@ -17,20 +19,20 @@ pub struct RelativeWithExtended<M: StorageMode = Rw> {
#[traversable(flatten)] #[traversable(flatten)]
pub base: RelativeFull<M>, pub base: RelativeFull<M>,
#[traversable(flatten)] #[traversable(flatten)]
pub rel_to_all: RelativeToAll<M>,
#[traversable(flatten)]
pub extended_own_market_cap: RelativeExtendedOwnMarketCap<M>, pub extended_own_market_cap: RelativeExtendedOwnMarketCap<M>,
#[traversable(flatten)] #[traversable(flatten)]
pub extended_own_pnl: RelativeExtendedOwnPnl<M>, pub extended_own_pnl: RelativeExtendedOwnPnl<M>,
#[traversable(flatten)]
pub invested_capital: RelativeInvestedCapital<M>,
} }
impl RelativeWithExtended { impl RelativeWithExtended {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self { Ok(Self {
base: RelativeFull::forced_import(cfg)?, base: RelativeFull::forced_import(cfg)?,
rel_to_all: RelativeToAll::forced_import(cfg)?,
extended_own_market_cap: RelativeExtendedOwnMarketCap::forced_import(cfg)?, extended_own_market_cap: RelativeExtendedOwnMarketCap::forced_import(cfg)?,
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?, extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?,
invested_capital: RelativeInvestedCapital::forced_import(cfg)?,
}) })
} }
@@ -40,15 +42,13 @@ impl RelativeWithExtended {
max_from: Height, max_from: Height,
supply: &SupplyCore, supply: &SupplyCore,
unrealized: &UnrealizedFull, unrealized: &UnrealizedFull,
realized: &RealizedFull,
market_cap: &impl ReadableVec<Height, Dollars>, market_cap: &impl ReadableVec<Height, Dollars>,
all_supply_sats: &impl ReadableVec<Height, Sats>,
own_market_cap: &impl ReadableVec<Height, Dollars>, own_market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.base self.base
.compute(max_from, supply, &unrealized.inner.basic, market_cap, exit)?; .compute(max_from, supply, &unrealized.inner.basic, market_cap, exit)?;
self.rel_to_all
.compute(max_from, supply, all_supply_sats, exit)?;
self.extended_own_market_cap self.extended_own_market_cap
.compute(max_from, &unrealized.inner, own_market_cap, exit)?; .compute(max_from, &unrealized.inner, own_market_cap, exit)?;
self.extended_own_pnl.compute( self.extended_own_pnl.compute(
@@ -57,6 +57,8 @@ impl RelativeWithExtended {
&unrealized.gross_pnl.usd.height, &unrealized.gross_pnl.usd.height,
exit, exit,
)?; )?;
self.invested_capital
.compute(max_from, unrealized, realized, exit)?;
Ok(()) Ok(())
} }
} }
@@ -0,0 +1,77 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64, Version};
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{indexes, internal::AmountPerBlock, prices};
/// Average amount held per UTXO and per funded address.
///
/// `utxo = supply / utxo_count`, `addr = supply / funded_addr_count`.
#[derive(Traversable)]
pub struct AvgAmountMetrics<M: StorageMode = Rw> {
pub utxo: AmountPerBlock<M>,
pub addr: AmountPerBlock<M>,
}
impl AvgAmountMetrics {
pub(crate) fn forced_import(
db: &Database,
prefix: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let name = |suffix: &str| {
if prefix.is_empty() {
suffix.to_string()
} else {
format!("{prefix}_{suffix}")
}
};
Ok(Self {
utxo: AmountPerBlock::forced_import(db, &name("avg_utxo_amount"), version, indexes)?,
addr: AmountPerBlock::forced_import(db, &name("avg_addr_amount"), version, indexes)?,
})
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.utxo.sats.height as &mut dyn AnyStoredVec,
&mut self.utxo.cents.height,
&mut self.addr.sats.height,
&mut self.addr.cents.height,
]
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.utxo.sats.height.reset()?;
self.utxo.cents.height.reset()?;
self.addr.sats.height.reset()?;
self.addr.cents.height.reset()?;
Ok(())
}
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
supply_sats: &impl ReadableVec<Height, Sats>,
utxo_count: &impl ReadableVec<Height, StoredU64>,
funded_addr_count: &impl ReadableVec<Height, StoredU64>,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.utxo
.sats
.height
.compute_divide(max_from, supply_sats, utxo_count, exit)?;
self.utxo.compute(prices, max_from, exit)?;
self.addr
.sats
.height
.compute_divide(max_from, supply_sats, funded_addr_count, exit)?;
self.addr.compute(prices, max_from, exit)?;
Ok(())
}
}
@@ -1,22 +1,26 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Height, Indexes, Sats, SatsSigned, Version}; use brk_types::{BasisPoints16, BasisPointsSigned32, Height, Indexes, Sats, SatsSigned, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec}; use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{ use crate::{
distribution::state::{CohortState, CostBasisOps, RealizedOps}, distribution::state::{CohortState, CostBasisOps, RealizedOps},
prices, prices,
}; };
use crate::internal::{AmountPerBlock, LazyRollingDeltasFromHeight}; use crate::internal::{
AmountPerBlock, LazyRollingDeltasFromHeight, PercentPerBlock, RatioSatsBp16,
};
use crate::distribution::metrics::ImportConfig; use crate::distribution::metrics::ImportConfig;
/// Base supply metrics: total supply only (2 stored vecs). /// Base supply metrics: total supply + dominance (share of circulating).
#[derive(Traversable)] #[derive(Traversable)]
pub struct SupplyBase<M: StorageMode = Rw> { pub struct SupplyBase<M: StorageMode = Rw> {
pub total: AmountPerBlock<M>, pub total: AmountPerBlock<M>,
pub delta: LazyRollingDeltasFromHeight<Sats, SatsSigned, BasisPointsSigned32>, pub delta: LazyRollingDeltasFromHeight<Sats, SatsSigned, BasisPointsSigned32>,
#[traversable(rename = "dominance")]
pub dominance: PercentPerBlock<BasisPoints16, M>,
} }
impl SupplyBase { impl SupplyBase {
@@ -31,9 +35,12 @@ impl SupplyBase {
cfg.indexes, cfg.indexes,
); );
let dominance = cfg.import("supply_dominance", Version::ZERO)?;
Ok(Self { Ok(Self {
total: supply, total: supply,
delta, delta,
dominance,
}) })
} }
@@ -49,7 +56,8 @@ impl SupplyBase {
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![ vec![
&mut self.total.sats.height as &mut dyn AnyStoredVec, &mut self.total.sats.height as &mut dyn AnyStoredVec,
&mut self.total.cents.height as &mut dyn AnyStoredVec, &mut self.total.cents.height,
&mut self.dominance.bps.height,
] ]
} }
@@ -62,6 +70,21 @@ impl SupplyBase {
self.total.compute(prices, max_from, exit) self.total.compute(prices, max_from, exit)
} }
pub(crate) fn compute_dominance(
&mut self,
max_from: Height,
all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {
self.dominance
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&self.total.sats.height,
all_supply_sats,
exit,
)
}
pub(crate) fn compute_from_stateful( pub(crate) fn compute_from_stateful(
&mut self, &mut self,
starting_indexes: &Indexes, starting_indexes: &Indexes,
@@ -1,5 +1,7 @@
mod avg_amount;
mod base; mod base;
mod core; mod core;
pub use avg_amount::AvgAmountMetrics;
pub use self::core::SupplyCore; pub use self::core::SupplyCore;
pub use base::SupplyBase; pub use base::SupplyBase;
@@ -33,8 +33,8 @@ pub struct UnrealizedFull<M: StorageMode = Rw> {
pub gross_pnl: FiatPerBlock<Cents, M>, pub gross_pnl: FiatPerBlock<Cents, M>,
pub invested_capital: UnrealizedInvestedCapital<M>, pub invested_capital: UnrealizedInvestedCapital<M>,
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>, pub capitalized_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>, pub capitalized_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub sentiment: UnrealizedSentiment<M>, pub sentiment: UnrealizedSentiment<M>,
} }
@@ -53,8 +53,8 @@ impl UnrealizedFull {
in_loss: cfg.import("invested_capital_in_loss", v1)?, in_loss: cfg.import("invested_capital_in_loss", v1)?,
}; };
let investor_cap_in_profit_raw = cfg.import("investor_cap_in_profit_raw", v0)?; let capitalized_cap_in_profit_raw = cfg.import("capitalized_cap_in_profit_raw", v0)?;
let investor_cap_in_loss_raw = cfg.import("investor_cap_in_loss_raw", v0)?; let capitalized_cap_in_loss_raw = cfg.import("capitalized_cap_in_loss_raw", v0)?;
let sentiment = UnrealizedSentiment { let sentiment = UnrealizedSentiment {
pain_index: cfg.import("pain_index", v1)?, pain_index: cfg.import("pain_index", v1)?,
@@ -66,33 +66,33 @@ impl UnrealizedFull {
inner, inner,
gross_pnl, gross_pnl,
invested_capital, invested_capital,
investor_cap_in_profit_raw, capitalized_cap_in_profit_raw,
investor_cap_in_loss_raw, capitalized_cap_in_loss_raw,
sentiment, sentiment,
}) })
} }
pub(crate) fn min_stateful_len(&self) -> usize { pub(crate) fn min_stateful_len(&self) -> usize {
// Only check per-block pushed vecs (investor_cap_raw). // Only check per-block pushed vecs (capitalized_cap_raw).
// Core-level vecs (profit/loss) are aggregated from age_range, not stateful. // Core-level vecs (profit/loss) are aggregated from age_range, not stateful.
self.investor_cap_in_profit_raw self.capitalized_cap_in_profit_raw
.len() .len()
.min(self.investor_cap_in_loss_raw.len()) .min(self.capitalized_cap_in_loss_raw.len())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn push_state_all(&mut self, state: &UnrealizedState) { pub(crate) fn push_state_all(&mut self, state: &UnrealizedState) {
self.inner.push_state(state); self.inner.push_state(state);
self.investor_cap_in_profit_raw self.capitalized_cap_in_profit_raw
.push(CentsSquaredSats::new(state.investor_cap_in_profit_raw)); .push(CentsSquaredSats::new(state.capitalized_cap_in_profit_raw));
self.investor_cap_in_loss_raw self.capitalized_cap_in_loss_raw
.push(CentsSquaredSats::new(state.investor_cap_in_loss_raw)); .push(CentsSquaredSats::new(state.capitalized_cap_in_loss_raw));
} }
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.inner.collect_vecs_mut(); let mut vecs = self.inner.collect_vecs_mut();
vecs.push(&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.capitalized_cap_in_profit_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.capitalized_cap_in_loss_raw as &mut dyn AnyStoredVec);
vecs vecs
} }
@@ -122,7 +122,7 @@ impl UnrealizedFull {
.compute_transform3( .compute_transform3(
starting_indexes.height, starting_indexes.height,
supply_in_profit_sats, supply_in_profit_sats,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.inner.basic.profit.cents.height, &self.inner.basic.profit.cents.height,
|(h, supply_sats, spot, profit, ..): (_, Sats, Cents, Cents, _)| { |(h, supply_sats, spot, profit, ..): (_, Sats, Cents, Cents, _)| {
let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128; let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128;
@@ -142,7 +142,7 @@ impl UnrealizedFull {
.compute_transform3( .compute_transform3(
starting_indexes.height, starting_indexes.height,
supply_in_loss_sats, supply_in_loss_sats,
&prices.cached_spot_cents, &prices.spot.cents.height,
&self.inner.basic.loss.cents.height, &self.inner.basic.loss.cents.height,
|(h, supply_sats, spot, loss, ..): (_, Sats, Cents, Cents, _)| { |(h, supply_sats, spot, loss, ..): (_, Sats, Cents, Cents, _)| {
let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128; let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128;
@@ -154,7 +154,7 @@ impl UnrealizedFull {
Ok(()) Ok(())
} }
/// Compute sentiment using investor_price (original formula). /// Compute sentiment using capitalized_price (original formula).
/// Called after cost_basis.in_profit/loss are computed at the cohort level. /// Called after cost_basis.in_profit/loss are computed at the cohort level.
pub(crate) fn compute_sentiment( pub(crate) fn compute_sentiment(
&mut self, &mut self,
@@ -162,45 +162,45 @@ impl UnrealizedFull {
spot: &impl ReadableVec<Height, Cents>, spot: &impl ReadableVec<Height, Cents>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
// greed = spot - investor_price_winners // greed = spot - capitalized_price_winners
// investor_price = investor_cap / invested_cap // capitalized_price = capitalized_cap / invested_cap
// invested_cap is in Cents (already / ONE_BTC), multiply back for CentsSats scale // invested_cap is in Cents (already / ONE_BTC), multiply back for CentsSats scale
self.sentiment.greed_index.cents.height.compute_transform3( self.sentiment.greed_index.cents.height.compute_transform3(
starting_indexes.height, starting_indexes.height,
&self.investor_cap_in_profit_raw, &self.capitalized_cap_in_profit_raw,
&self.invested_capital.in_profit.cents.height, &self.invested_capital.in_profit.cents.height,
spot, spot,
|(h, investor_cap, invested_cap_cents, spot, ..)| { |(h, capitalized_cap, invested_cap_cents, spot, ..)| {
let invested_cap_raw = invested_cap_cents.as_u128() * Sats::ONE_BTC_U128; let invested_cap_raw = invested_cap_cents.as_u128() * Sats::ONE_BTC_U128;
if invested_cap_raw == 0 { if invested_cap_raw == 0 {
return (h, Cents::ZERO); return (h, Cents::ZERO);
} }
let investor_price = investor_cap.inner() / invested_cap_raw; let capitalized_price = capitalized_cap.inner() / invested_cap_raw;
let spot_u128 = spot.as_u128(); let spot_u128 = spot.as_u128();
( (
h, h,
Cents::new(spot_u128.saturating_sub(investor_price) as u64), Cents::new(spot_u128.saturating_sub(capitalized_price) as u64),
) )
}, },
exit, exit,
)?; )?;
// pain = investor_price_losers - spot // pain = capitalized_price_losers - spot
self.sentiment.pain_index.cents.height.compute_transform3( self.sentiment.pain_index.cents.height.compute_transform3(
starting_indexes.height, starting_indexes.height,
&self.investor_cap_in_loss_raw, &self.capitalized_cap_in_loss_raw,
&self.invested_capital.in_loss.cents.height, &self.invested_capital.in_loss.cents.height,
spot, spot,
|(h, investor_cap, invested_cap_cents, spot, ..)| { |(h, capitalized_cap, invested_cap_cents, spot, ..)| {
let invested_cap_raw = invested_cap_cents.as_u128() * Sats::ONE_BTC_U128; let invested_cap_raw = invested_cap_cents.as_u128() * Sats::ONE_BTC_U128;
if invested_cap_raw == 0 { if invested_cap_raw == 0 {
return (h, Cents::ZERO); return (h, Cents::ZERO);
} }
let investor_price = investor_cap.inner() / invested_cap_raw; let capitalized_price = capitalized_cap.inner() / invested_cap_raw;
let spot_u128 = spot.as_u128(); let spot_u128 = spot.as_u128();
( (
h, h,
Cents::new(investor_price.saturating_sub(spot_u128) as u64), Cents::new(capitalized_price.saturating_sub(spot_u128) as u64),
) )
}, },
exit, exit,
@@ -18,7 +18,7 @@ pub struct SendPrecomputed {
pub current_ps: CentsSats, pub current_ps: CentsSats,
pub prev_ps: CentsSats, pub prev_ps: CentsSats,
pub ath_ps: CentsSats, pub ath_ps: CentsSats,
pub prev_investor_cap: CentsSquaredSats, pub prev_capitalized_cap: CentsSquaredSats,
} }
impl SendPrecomputed { impl SendPrecomputed {
@@ -42,7 +42,7 @@ impl SendPrecomputed {
} else { } else {
CentsSats::from_price_sats(ath, sats) CentsSats::from_price_sats(ath, sats)
}; };
let prev_investor_cap = prev_ps.to_investor_cap(prev_price); let prev_capitalized_cap = prev_ps.to_capitalized_cap(prev_price);
Some(Self { Some(Self {
sats, sats,
prev_price, prev_price,
@@ -50,7 +50,7 @@ impl SendPrecomputed {
current_ps, current_ps,
prev_ps, prev_ps,
ath_ps, ath_ps,
prev_investor_cap, prev_capitalized_cap,
}) })
} }
} }
@@ -90,7 +90,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
pub(crate) fn restore_realized_cap(&mut self) { pub(crate) fn restore_realized_cap(&mut self) {
self.realized.set_cap_raw(self.cost_basis.cap_raw()); self.realized.set_cap_raw(self.cost_basis.cap_raw());
self.realized self.realized
.set_investor_cap_raw(self.cost_basis.investor_cap_raw()); .set_capitalized_cap_raw(self.cost_basis.capitalized_cap_raw());
} }
pub(crate) fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> { pub(crate) fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
@@ -117,12 +117,12 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
if s.supply_state.value > Sats::ZERO { if s.supply_state.value > Sats::ZERO {
self.realized self.realized
.increment_snapshot(s.price_sats, s.investor_cap); .increment_snapshot(s.price_sats, s.capitalized_cap_raw);
self.cost_basis.increment( self.cost_basis.increment(
s.realized_price, s.realized_price,
s.supply_state.value, s.supply_state.value,
s.price_sats, s.price_sats,
s.investor_cap, s.capitalized_cap_raw,
); );
} }
} }
@@ -132,12 +132,12 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
if s.supply_state.value > Sats::ZERO { if s.supply_state.value > Sats::ZERO {
self.realized self.realized
.decrement_snapshot(s.price_sats, s.investor_cap); .decrement_snapshot(s.price_sats, s.capitalized_cap_raw);
self.cost_basis.decrement( self.cost_basis.decrement(
s.realized_price, s.realized_price,
s.supply_state.value, s.supply_state.value,
s.price_sats, s.price_sats,
s.investor_cap, s.capitalized_cap_raw,
); );
} }
} }
@@ -162,7 +162,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
snapshot.realized_price, snapshot.realized_price,
supply.value, supply.value,
snapshot.price_sats, snapshot.price_sats,
snapshot.investor_cap, snapshot.capitalized_cap_raw,
); );
} }
} }
@@ -184,7 +184,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
current.realized_price, current.realized_price,
current.supply_state.value, current.supply_state.value,
current.price_sats, current.price_sats,
current.investor_cap, current.capitalized_cap_raw,
); );
} }
@@ -193,7 +193,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
prev.realized_price, prev.realized_price,
prev.supply_state.value, prev.supply_state.value,
prev.price_sats, prev.price_sats,
prev.investor_cap, prev.capitalized_cap_raw,
); );
} }
} }
@@ -212,11 +212,11 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
pre.current_ps, pre.current_ps,
pre.prev_ps, pre.prev_ps,
pre.ath_ps, pre.ath_ps,
pre.prev_investor_cap, pre.prev_capitalized_cap,
); );
self.cost_basis self.cost_basis
.decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_investor_cap); .decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_capitalized_cap);
} }
pub(crate) fn send_utxo( pub(crate) fn send_utxo(
@@ -265,17 +265,17 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
let current_ps = CentsSats::from_price_sats(current_price, sats); let current_ps = CentsSats::from_price_sats(current_price, sats);
let prev_ps = CentsSats::from_price_sats(prev_price, sats); let prev_ps = CentsSats::from_price_sats(prev_price, sats);
let ath_ps = CentsSats::from_price_sats(ath, sats); let ath_ps = CentsSats::from_price_sats(ath, sats);
let prev_investor_cap = prev_ps.to_investor_cap(prev_price); let prev_capitalized_cap = prev_ps.to_capitalized_cap(prev_price);
self.realized self.realized
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); .send(sats, current_ps, prev_ps, ath_ps, prev_capitalized_cap);
if current.supply_state.value.is_not_zero() { if current.supply_state.value.is_not_zero() {
self.cost_basis.increment( self.cost_basis.increment(
current.realized_price, current.realized_price,
current.supply_state.value, current.supply_state.value,
current.price_sats, current.price_sats,
current.investor_cap, current.capitalized_cap_raw,
); );
} }
@@ -284,7 +284,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
prev.realized_price, prev.realized_price,
prev.supply_state.value, prev.supply_state.value,
prev.price_sats, prev.price_sats,
prev.investor_cap, prev.capitalized_cap_raw,
); );
} }
} }
@@ -12,7 +12,7 @@ use rustc_hash::FxHashMap;
use vecdb::{Bytes, unlikely}; use vecdb::{Bytes, unlikely};
use super::{Accumulate, CachedUnrealizedState, UnrealizedState}; use super::{Accumulate, CachedUnrealizedState, UnrealizedState};
use crate::distribution::state::pending::{PendingCapDelta, PendingDelta, PendingInvestorCapDelta}; use crate::distribution::state::pending::{PendingCapDelta, PendingDelta, PendingCapitalizedCapRawDelta};
/// Type alias for the price-to-sats map used in cost basis data. /// Type alias for the price-to-sats map used in cost basis data.
pub(super) type CostBasisMap = BTreeMap<CentsCompact, Sats>; pub(super) type CostBasisMap = BTreeMap<CentsCompact, Sats>;
@@ -27,20 +27,20 @@ pub trait CostBasisOps: Send + Sync + 'static {
fn with_price_rounding(self, digits: i32) -> Self; fn with_price_rounding(self, digits: i32) -> Self;
fn import_at_or_before(&mut self, height: Height) -> Result<Height>; fn import_at_or_before(&mut self, height: Height) -> Result<Height>;
fn cap_raw(&self) -> CentsSats; fn cap_raw(&self) -> CentsSats;
fn investor_cap_raw(&self) -> CentsSquaredSats; fn capitalized_cap_raw(&self) -> CentsSquaredSats;
fn increment( fn increment(
&mut self, &mut self,
price: Cents, price: Cents,
sats: Sats, sats: Sats,
price_sats: CentsSats, price_sats: CentsSats,
investor_cap: CentsSquaredSats, capitalized_cap: CentsSquaredSats,
); );
fn decrement( fn decrement(
&mut self, &mut self,
price: Cents, price: Cents,
sats: Sats, sats: Sats,
price_sats: CentsSats, price_sats: CentsSats,
investor_cap: CentsSquaredSats, capitalized_cap: CentsSquaredSats,
); );
fn apply_pending(&mut self); fn apply_pending(&mut self);
fn init(&mut self); fn init(&mut self);
@@ -182,7 +182,7 @@ impl CostBasisOps for CostBasisRaw {
self.state.as_ref().unwrap().cap_raw self.state.as_ref().unwrap().cap_raw
} }
fn investor_cap_raw(&self) -> CentsSquaredSats { fn capitalized_cap_raw(&self) -> CentsSquaredSats {
CentsSquaredSats::ZERO CentsSquaredSats::ZERO
} }
@@ -192,7 +192,7 @@ impl CostBasisOps for CostBasisRaw {
_price: Cents, _price: Cents,
_sats: Sats, _sats: Sats,
price_sats: CentsSats, price_sats: CentsSats,
_investor_cap: CentsSquaredSats, _capitalized_cap: CentsSquaredSats,
) { ) {
self.pending_cap.inc += price_sats; self.pending_cap.inc += price_sats;
} }
@@ -203,7 +203,7 @@ impl CostBasisOps for CostBasisRaw {
_price: Cents, _price: Cents,
_sats: Sats, _sats: Sats,
price_sats: CentsSats, price_sats: CentsSats,
_investor_cap: CentsSquaredSats, _capitalized_cap: CentsSquaredSats,
) { ) {
self.pending_cap.dec += price_sats; self.pending_cap.dec += price_sats;
} }
@@ -240,7 +240,7 @@ impl CostBasisOps for CostBasisRaw {
/// Composes `CostBasisRaw` for scalar tracking, adds map, pending, and cache. /// Composes `CostBasisRaw` for scalar tracking, adds map, pending, and cache.
/// ///
/// Generic over the accumulator `S`: /// Generic over the accumulator `S`:
/// - `WithCapital`: tracks all fields including invested capital + investor cap (128 bytes) /// - `WithCapital`: tracks all fields including invested capital + capitalized cap (128 bytes)
/// - `WithoutCapital`: tracks only supply + unrealized profit/loss (64 bytes, 1 cache line) /// - `WithoutCapital`: tracks only supply + unrealized profit/loss (64 bytes, 1 cache line)
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CostBasisData<S: Accumulate> { pub struct CostBasisData<S: Accumulate> {
@@ -249,8 +249,8 @@ pub struct CostBasisData<S: Accumulate> {
pending: FxHashMap<CentsCompact, PendingDelta>, pending: FxHashMap<CentsCompact, PendingDelta>,
cache: Option<CachedUnrealizedState<S>>, cache: Option<CachedUnrealizedState<S>>,
rounding_digits: Option<i32>, rounding_digits: Option<i32>,
investor_cap_raw: CentsSquaredSats, capitalized_cap_raw: CentsSquaredSats,
pending_investor_cap: PendingInvestorCapDelta, pending_capitalized_cap: PendingCapitalizedCapRawDelta,
} }
impl<S: Accumulate> CostBasisData<S> { impl<S: Accumulate> CostBasisData<S> {
@@ -351,8 +351,8 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
pending: FxHashMap::default(), pending: FxHashMap::default(),
cache: None, cache: None,
rounding_digits: None, rounding_digits: None,
investor_cap_raw: CentsSquaredSats::ZERO, capitalized_cap_raw: CentsSquaredSats::ZERO,
pending_investor_cap: PendingInvestorCapDelta::default(), pending_capitalized_cap: PendingCapitalizedCapRawDelta::default(),
} }
} }
@@ -375,10 +375,10 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
"CostBasisData state too short: {} bytes", "CostBasisData state too short: {} bytes",
rest.len() rest.len()
); );
self.investor_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?; self.capitalized_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?;
self.pending.clear(); self.pending.clear();
self.raw.pending_cap = PendingCapDelta::default(); self.raw.pending_cap = PendingCapDelta::default();
self.pending_investor_cap = PendingInvestorCapDelta::default(); self.pending_capitalized_cap = PendingCapitalizedCapRawDelta::default();
self.cache = None; self.cache = None;
Ok(height) Ok(height)
} }
@@ -387,8 +387,8 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
self.raw.cap_raw() self.raw.cap_raw()
} }
fn investor_cap_raw(&self) -> CentsSquaredSats { fn capitalized_cap_raw(&self) -> CentsSquaredSats {
self.investor_cap_raw self.capitalized_cap_raw
} }
#[inline] #[inline]
@@ -397,13 +397,13 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
price: Cents, price: Cents,
sats: Sats, sats: Sats,
price_sats: CentsSats, price_sats: CentsSats,
investor_cap: CentsSquaredSats, capitalized_cap: CentsSquaredSats,
) { ) {
let price = self.round_price(price); let price = self.round_price(price);
self.pending.entry(price.into()).or_default().inc += sats; self.pending.entry(price.into()).or_default().inc += sats;
self.raw.pending_cap.inc += price_sats; self.raw.pending_cap.inc += price_sats;
if investor_cap != CentsSquaredSats::ZERO { if capitalized_cap != CentsSquaredSats::ZERO {
self.pending_investor_cap.inc += investor_cap; self.pending_capitalized_cap.inc += capitalized_cap;
} }
if let Some(cache) = self.cache.as_mut() { if let Some(cache) = self.cache.as_mut() {
cache.on_receive(price, sats); cache.on_receive(price, sats);
@@ -416,13 +416,13 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
price: Cents, price: Cents,
sats: Sats, sats: Sats,
price_sats: CentsSats, price_sats: CentsSats,
investor_cap: CentsSquaredSats, capitalized_cap: CentsSquaredSats,
) { ) {
let price = self.round_price(price); let price = self.round_price(price);
self.pending.entry(price.into()).or_default().dec += sats; self.pending.entry(price.into()).or_default().dec += sats;
self.raw.pending_cap.dec += price_sats; self.raw.pending_cap.dec += price_sats;
if investor_cap != CentsSquaredSats::ZERO { if capitalized_cap != CentsSquaredSats::ZERO {
self.pending_investor_cap.dec += investor_cap; self.pending_capitalized_cap.dec += capitalized_cap;
} }
if let Some(cache) = self.cache.as_mut() { if let Some(cache) = self.cache.as_mut() {
cache.on_send(price, sats); cache.on_send(price, sats);
@@ -432,19 +432,19 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
fn apply_pending(&mut self) { fn apply_pending(&mut self) {
self.apply_map_pending(); self.apply_map_pending();
self.raw.apply_pending_cap(); self.raw.apply_pending_cap();
self.investor_cap_raw += self.pending_investor_cap.inc; self.capitalized_cap_raw += self.pending_capitalized_cap.inc;
debug_assert!( debug_assert!(
self.investor_cap_raw >= self.pending_investor_cap.dec, self.capitalized_cap_raw >= self.pending_capitalized_cap.dec,
"CostBasis investor_cap_raw underflow!\n\ "CostBasis capitalized_cap_raw underflow!\n\
Path: {:?}\n\ Path: {:?}\n\
Current (after increments): {:?}\n\ Current (after increments): {:?}\n\
Trying to decrement by: {:?}", Trying to decrement by: {:?}",
self.raw.pathbuf, self.raw.pathbuf,
self.investor_cap_raw, self.capitalized_cap_raw,
self.pending_investor_cap.dec self.pending_capitalized_cap.dec
); );
self.investor_cap_raw -= self.pending_investor_cap.dec; self.capitalized_cap_raw -= self.pending_capitalized_cap.dec;
self.pending_investor_cap = PendingInvestorCapDelta::default(); self.pending_capitalized_cap = PendingCapitalizedCapRawDelta::default();
} }
fn init(&mut self) { fn init(&mut self) {
@@ -452,8 +452,8 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
self.map.replace(CostBasisDistribution::default()); self.map.replace(CostBasisDistribution::default());
self.pending.clear(); self.pending.clear();
self.cache = None; self.cache = None;
self.investor_cap_raw = CentsSquaredSats::ZERO; self.capitalized_cap_raw = CentsSquaredSats::ZERO;
self.pending_investor_cap = PendingInvestorCapDelta::default(); self.pending_capitalized_cap = PendingCapitalizedCapRawDelta::default();
} }
fn clean(&mut self) -> Result<()> { fn clean(&mut self) -> Result<()> {
@@ -469,7 +469,7 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
let raw_state = self.raw.state.as_ref().unwrap(); let raw_state = self.raw.state.as_ref().unwrap();
let mut buffer = self.map.as_ref().unwrap().serialize()?; let mut buffer = self.map.as_ref().unwrap().serialize()?;
buffer.extend(raw_state.cap_raw.to_bytes()); buffer.extend(raw_state.cap_raw.to_bytes());
buffer.extend(self.investor_cap_raw.to_bytes()); buffer.extend(self.capitalized_cap_raw.to_bytes());
fs::write(self.raw.path_state(height), buffer)?; fs::write(self.raw.path_state(height), buffer)?;
Ok(()) Ok(())
@@ -18,11 +18,11 @@ pub trait RealizedOps: Default + Clone + Send + Sync + 'static {
Sats::ZERO Sats::ZERO
} }
fn set_cap_raw(&mut self, cap_raw: CentsSats); fn set_cap_raw(&mut self, cap_raw: CentsSats);
fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats); fn set_capitalized_cap_raw(&mut self, capitalized_cap_raw: CentsSquaredSats);
fn reset_single_iteration_values(&mut self); fn reset_single_iteration_values(&mut self);
fn increment(&mut self, price: Cents, sats: Sats); fn increment(&mut self, price: Cents, sats: Sats);
fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats); fn increment_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats);
fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats); fn decrement_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats);
fn receive(&mut self, price: Cents, sats: Sats) { fn receive(&mut self, price: Cents, sats: Sats) {
self.increment(price, sats); self.increment(price, sats);
} }
@@ -32,7 +32,7 @@ pub trait RealizedOps: Default + Clone + Send + Sync + 'static {
current_ps: CentsSats, current_ps: CentsSats,
prev_ps: CentsSats, prev_ps: CentsSats,
ath_ps: CentsSats, ath_ps: CentsSats,
prev_investor_cap: CentsSquaredSats, prev_capitalized_cap: CentsSquaredSats,
); );
} }
@@ -85,7 +85,7 @@ impl RealizedOps for MinimalRealizedState {
} }
#[inline] #[inline]
fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {} fn set_capitalized_cap_raw(&mut self, _capitalized_cap_raw: CentsSquaredSats) {}
#[inline] #[inline]
fn reset_single_iteration_values(&mut self) { fn reset_single_iteration_values(&mut self) {
@@ -104,12 +104,12 @@ impl RealizedOps for MinimalRealizedState {
} }
#[inline] #[inline]
fn increment_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { fn increment_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) {
self.cap_raw += price_sats.as_u128(); self.cap_raw += price_sats.as_u128();
} }
#[inline] #[inline]
fn decrement_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { fn decrement_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) {
self.cap_raw -= price_sats.as_u128(); self.cap_raw -= price_sats.as_u128();
} }
@@ -120,7 +120,7 @@ impl RealizedOps for MinimalRealizedState {
current_ps: CentsSats, current_ps: CentsSats,
prev_ps: CentsSats, prev_ps: CentsSats,
_ath_ps: CentsSats, _ath_ps: CentsSats,
_prev_investor_cap: CentsSquaredSats, _prev_capitalized_cap: CentsSquaredSats,
) { ) {
match current_ps.cmp(&prev_ps) { match current_ps.cmp(&prev_ps) {
Ordering::Greater => { Ordering::Greater => {
@@ -184,7 +184,7 @@ impl RealizedOps for CoreRealizedState {
} }
#[inline] #[inline]
fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {} fn set_capitalized_cap_raw(&mut self, _capitalized_cap_raw: CentsSquaredSats) {}
#[inline] #[inline]
fn reset_single_iteration_values(&mut self) { fn reset_single_iteration_values(&mut self) {
@@ -199,13 +199,13 @@ impl RealizedOps for CoreRealizedState {
} }
#[inline] #[inline]
fn increment_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { fn increment_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) {
self.minimal.increment_snapshot(price_sats, _investor_cap); self.minimal.increment_snapshot(price_sats, _capitalized_cap);
} }
#[inline] #[inline]
fn decrement_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { fn decrement_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) {
self.minimal.decrement_snapshot(price_sats, _investor_cap); self.minimal.decrement_snapshot(price_sats, _capitalized_cap);
} }
#[inline] #[inline]
@@ -215,10 +215,10 @@ impl RealizedOps for CoreRealizedState {
current_ps: CentsSats, current_ps: CentsSats,
prev_ps: CentsSats, prev_ps: CentsSats,
ath_ps: CentsSats, ath_ps: CentsSats,
prev_investor_cap: CentsSquaredSats, prev_capitalized_cap: CentsSquaredSats,
) { ) {
self.minimal self.minimal
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); .send(sats, current_ps, prev_ps, ath_ps, prev_capitalized_cap);
match current_ps.cmp(&prev_ps) { match current_ps.cmp(&prev_ps) {
Ordering::Greater | Ordering::Equal => { Ordering::Greater | Ordering::Equal => {
self.sent_in_profit += sats; self.sent_in_profit += sats;
@@ -242,8 +242,8 @@ impl CoreRealizedState {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct RealizedState { pub struct RealizedState {
core: CoreRealizedState, core: CoreRealizedState,
/// Raw investor cap: Σ(price² × sats) /// Raw capitalized cap: Σ(price² × sats)
investor_cap_raw: CentsSquaredSats, capitalized_cap_raw: CentsSquaredSats,
/// Raw realized peak regret: Σ((peak - sell_price) × sats) /// Raw realized peak regret: Σ((peak - sell_price) × sats)
peak_regret_raw: u128, peak_regret_raw: u128,
} }
@@ -287,8 +287,8 @@ impl RealizedOps for RealizedState {
} }
#[inline] #[inline]
fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats) { fn set_capitalized_cap_raw(&mut self, capitalized_cap_raw: CentsSquaredSats) {
self.investor_cap_raw = investor_cap_raw; self.capitalized_cap_raw = capitalized_cap_raw;
} }
#[inline] #[inline]
@@ -301,20 +301,20 @@ impl RealizedOps for RealizedState {
fn increment(&mut self, price: Cents, sats: Sats) { fn increment(&mut self, price: Cents, sats: Sats) {
self.core.increment(price, sats); self.core.increment(price, sats);
if sats.is_not_zero() { if sats.is_not_zero() {
self.investor_cap_raw += CentsSats::from_price_sats(price, sats).to_investor_cap(price); self.capitalized_cap_raw += CentsSats::from_price_sats(price, sats).to_capitalized_cap(price);
} }
} }
#[inline] #[inline]
fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) { fn increment_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats) {
self.core.increment_snapshot(price_sats, investor_cap); self.core.increment_snapshot(price_sats, capitalized_cap);
self.investor_cap_raw += investor_cap; self.capitalized_cap_raw += capitalized_cap;
} }
#[inline] #[inline]
fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) { fn decrement_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats) {
self.core.decrement_snapshot(price_sats, investor_cap); self.core.decrement_snapshot(price_sats, capitalized_cap);
self.investor_cap_raw -= investor_cap; self.capitalized_cap_raw -= capitalized_cap;
} }
#[inline] #[inline]
@@ -324,26 +324,26 @@ impl RealizedOps for RealizedState {
current_ps: CentsSats, current_ps: CentsSats,
prev_ps: CentsSats, prev_ps: CentsSats,
ath_ps: CentsSats, ath_ps: CentsSats,
prev_investor_cap: CentsSquaredSats, prev_capitalized_cap: CentsSquaredSats,
) { ) {
self.core self.core
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); .send(sats, current_ps, prev_ps, ath_ps, prev_capitalized_cap);
self.peak_regret_raw += (ath_ps - current_ps).as_u128(); self.peak_regret_raw += (ath_ps - current_ps).as_u128();
self.investor_cap_raw -= prev_investor_cap; self.capitalized_cap_raw -= prev_capitalized_cap;
} }
} }
impl RealizedState { impl RealizedState {
/// Get investor price as CentsUnsigned. /// Get capitalized price as CentsUnsigned.
/// investor_price = Σ(price² × sats) / Σ(price × sats) /// capitalized_price = Σ(price² × sats) / Σ(price × sats)
#[inline] #[inline]
pub(crate) fn investor_price(&self) -> Cents { pub(crate) fn capitalized_price(&self) -> Cents {
let cap_raw = self.core.cap_raw_u128(); let cap_raw = self.core.cap_raw_u128();
if cap_raw == 0 { if cap_raw == 0 {
return Cents::ZERO; return Cents::ZERO;
} }
Cents::new((self.investor_cap_raw / cap_raw) as u64) Cents::new((self.capitalized_cap_raw / cap_raw) as u64)
} }
/// Get raw realized cap for aggregation. /// Get raw realized cap for aggregation.
@@ -352,10 +352,10 @@ impl RealizedState {
CentsSats::new(self.core.cap_raw_u128()) CentsSats::new(self.core.cap_raw_u128())
} }
/// Get raw investor cap for aggregation. /// Get raw capitalized cap for aggregation.
#[inline] #[inline]
pub(crate) fn investor_cap_raw(&self) -> CentsSquaredSats { pub(crate) fn capitalized_cap_raw(&self) -> CentsSquaredSats {
self.investor_cap_raw self.capitalized_cap_raw
} }
/// Get realized peak regret as CentsUnsigned. /// Get realized peak regret as CentsUnsigned.
@@ -10,8 +10,8 @@ pub struct UnrealizedState {
pub supply_in_loss: Sats, pub supply_in_loss: Sats,
pub unrealized_profit: Cents, pub unrealized_profit: Cents,
pub unrealized_loss: Cents, pub unrealized_loss: Cents,
pub investor_cap_in_profit_raw: u128, pub capitalized_cap_in_profit_raw: u128,
pub investor_cap_in_loss_raw: u128, pub capitalized_cap_in_loss_raw: u128,
} }
impl UnrealizedState { impl UnrealizedState {
@@ -20,8 +20,8 @@ impl UnrealizedState {
supply_in_loss: Sats::ZERO, supply_in_loss: Sats::ZERO,
unrealized_profit: Cents::ZERO, unrealized_profit: Cents::ZERO,
unrealized_loss: Cents::ZERO, unrealized_loss: Cents::ZERO,
investor_cap_in_profit_raw: 0, capitalized_cap_in_profit_raw: 0,
investor_cap_in_loss_raw: 0, capitalized_cap_in_loss_raw: 0,
}; };
} }
@@ -34,12 +34,12 @@ pub struct WithoutCapital {
pub(crate) unrealized_loss: u128, pub(crate) unrealized_loss: u128,
} }
/// Full cache state: core + investor cap (for sentiment computation). /// Full cache state: core + capitalized cap (for sentiment computation).
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct WithCapital { pub struct WithCapital {
core: WithoutCapital, core: WithoutCapital,
investor_cap_in_profit: u128, capitalized_cap_in_profit: u128,
investor_cap_in_loss: u128, capitalized_cap_in_loss: u128,
} }
#[inline(always)] #[inline(always)]
@@ -116,8 +116,8 @@ impl Accumulate for WithoutCapital {
impl Accumulate for WithCapital { impl Accumulate for WithCapital {
fn to_output(&self) -> UnrealizedState { fn to_output(&self) -> UnrealizedState {
UnrealizedState { UnrealizedState {
investor_cap_in_profit_raw: self.investor_cap_in_profit, capitalized_cap_in_profit_raw: self.capitalized_cap_in_profit,
investor_cap_in_loss_raw: self.investor_cap_in_loss, capitalized_cap_in_loss_raw: self.capitalized_cap_in_loss,
..Accumulate::to_output(&self.core) ..Accumulate::to_output(&self.core)
} }
} }
@@ -133,25 +133,25 @@ impl Accumulate for WithCapital {
fn accumulate_profit(&mut self, price_u128: u128, sats: Sats) { fn accumulate_profit(&mut self, price_u128: u128, sats: Sats) {
self.core.supply_in_profit += sats; self.core.supply_in_profit += sats;
let invested = price_u128 * sats.as_u128(); let invested = price_u128 * sats.as_u128();
self.investor_cap_in_profit += price_u128 * invested; self.capitalized_cap_in_profit += price_u128 * invested;
} }
#[inline(always)] #[inline(always)]
fn accumulate_loss(&mut self, price_u128: u128, sats: Sats) { fn accumulate_loss(&mut self, price_u128: u128, sats: Sats) {
self.core.supply_in_loss += sats; self.core.supply_in_loss += sats;
let invested = price_u128 * sats.as_u128(); let invested = price_u128 * sats.as_u128();
self.investor_cap_in_loss += price_u128 * invested; self.capitalized_cap_in_loss += price_u128 * invested;
} }
#[inline(always)] #[inline(always)]
fn deaccumulate_profit(&mut self, price_u128: u128, sats: Sats) { fn deaccumulate_profit(&mut self, price_u128: u128, sats: Sats) {
self.core.supply_in_profit -= sats; self.core.supply_in_profit -= sats;
let invested = price_u128 * sats.as_u128(); let invested = price_u128 * sats.as_u128();
self.investor_cap_in_profit -= price_u128 * invested; self.capitalized_cap_in_profit -= price_u128 * invested;
} }
#[inline(always)] #[inline(always)]
fn deaccumulate_loss(&mut self, price_u128: u128, sats: Sats) { fn deaccumulate_loss(&mut self, price_u128: u128, sats: Sats) {
self.core.supply_in_loss -= sats; self.core.supply_in_loss -= sats;
let invested = price_u128 * sats.as_u128(); let invested = price_u128 * sats.as_u128();
self.investor_cap_in_loss -= price_u128 * invested; self.capitalized_cap_in_loss -= price_u128 * invested;
} }
} }
@@ -13,7 +13,7 @@ impl PendingCapDelta {
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub(crate) struct PendingInvestorCapDelta { pub(crate) struct PendingCapitalizedCapRawDelta {
pub inc: CentsSquaredSats, pub inc: CentsSquaredSats,
pub dec: CentsSquaredSats, pub dec: CentsSquaredSats,
} }
+93 -7
View File
@@ -1,5 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use brk_cohort::{ByAddrType, Filter};
use brk_error::Result; use brk_error::Result;
use brk_indexer::Indexer; use brk_indexer::Indexer;
use brk_traversable::Traversable; use brk_traversable::Traversable;
@@ -24,7 +25,7 @@ use crate::{
}, },
indexes, inputs, indexes, inputs,
internal::{ internal::{
CachedWindowStarts, PerBlockCumulativeRolling, PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes,
db_utils::{finalize_db, open_db}, db_utils::{finalize_db, open_db},
}, },
outputs, prices, transactions, outputs, prices, transactions,
@@ -32,10 +33,14 @@ use crate::{
use super::{ use super::{
AddrCohorts, AddrsDataVecs, AnyAddrIndexesVecs, RangeMap, UTXOCohorts, AddrCohorts, AddrsDataVecs, AnyAddrIndexesVecs, RangeMap, UTXOCohorts,
addr::{AddrActivityVecs, AddrCountsVecs, DeltaVecs, NewAddrCountVecs, TotalAddrCountVecs}, addr::{
AddrActivityVecs, AddrCountsVecs, DeltaVecs, ExposedAddrVecs, NewAddrCountVecs,
ReusedAddrVecs, TotalAddrCountVecs,
},
metrics::AvgAmountMetrics,
}; };
const VERSION: Version = Version::new(22); const VERSION: Version = Version::new(23);
#[derive(Traversable)] #[derive(Traversable)]
pub struct AddrMetricsVecs<M: StorageMode = Rw> { pub struct AddrMetricsVecs<M: StorageMode = Rw> {
@@ -44,7 +49,10 @@ pub struct AddrMetricsVecs<M: StorageMode = Rw> {
pub activity: AddrActivityVecs<M>, pub activity: AddrActivityVecs<M>,
pub total: TotalAddrCountVecs<M>, pub total: TotalAddrCountVecs<M>,
pub new: NewAddrCountVecs<M>, pub new: NewAddrCountVecs<M>,
pub reused: ReusedAddrVecs<M>,
pub exposed: ExposedAddrVecs<M>,
pub delta: DeltaVecs, pub delta: DeltaVecs,
pub avg_amount: WithAddrTypes<AvgAmountMetrics<M>>,
#[traversable(wrap = "indexes", rename = "funded")] #[traversable(wrap = "indexes", rename = "funded")]
pub funded_index: pub funded_index:
LazyVecFrom1<FundedAddrIndex, FundedAddrIndex, FundedAddrIndex, FundedAddrData>, LazyVecFrom1<FundedAddrIndex, FundedAddrIndex, FundedAddrIndex, FundedAddrData>,
@@ -102,7 +110,7 @@ impl Vecs {
parent: &Path, parent: &Path,
parent_version: Version, parent_version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let db_path = parent.join(super::DB_NAME); let db_path = parent.join(super::DB_NAME);
let states_path = db_path.join("states"); let states_path = db_path.join("states");
@@ -146,7 +154,7 @@ impl Vecs {
let empty_addr_count = let empty_addr_count =
AddrCountsVecs::forced_import(&db, "empty_addr_count", version, indexes)?; AddrCountsVecs::forced_import(&db, "empty_addr_count", version, indexes)?;
let addr_activity = let addr_activity =
AddrActivityVecs::forced_import(&db, "addr_activity", version, indexes, cached_starts)?; AddrActivityVecs::forced_import(&db, version, indexes, cached_starts)?;
// Stored total = addr_count + empty_addr_count (global + per-type, with all derived indexes) // Stored total = addr_count + empty_addr_count (global + per-type, with all derived indexes)
let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?; let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?;
@@ -154,9 +162,19 @@ impl Vecs {
// Per-block delta of total (global + per-type) // Per-block delta of total (global + per-type)
let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes, cached_starts)?; let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes, cached_starts)?;
// Reused address tracking (counts + per-block uses + percent)
let reused_addr_count =
ReusedAddrVecs::forced_import(&db, version, indexes, cached_starts)?;
// Exposed address tracking (counts + supply) - quantum / pubkey-exposure sense
let exposed_addr_vecs = ExposedAddrVecs::forced_import(&db, version, indexes)?;
// Growth rate: delta change + rate (global + per-type) // Growth rate: delta change + rate (global + per-type)
let delta = DeltaVecs::new(version, &addr_count, cached_starts, indexes); let delta = DeltaVecs::new(version, &addr_count, cached_starts, indexes);
// Average amount (supply / utxo_count, supply / funded_addr_count) for `all` and per addr type.
let avg_amount = WithAddrTypes::<AvgAmountMetrics>::forced_import(&db, version, indexes)?;
let this = Self { let this = Self {
supply_state: BytesVec::forced_import_with( supply_state: BytesVec::forced_import_with(
vecdb::ImportOptions::new(&db, "supply_state", version) vecdb::ImportOptions::new(&db, "supply_state", version)
@@ -169,7 +187,10 @@ impl Vecs {
activity: addr_activity, activity: addr_activity,
total: total_addr_count, total: total_addr_count,
new: new_addr_count, new: new_addr_count,
reused: reused_addr_count,
exposed: exposed_addr_vecs,
delta, delta,
avg_amount,
funded_index: funded_addr_index, funded_index: funded_addr_index,
empty_index: empty_addr_index, empty_index: empty_addr_index,
}, },
@@ -285,6 +306,9 @@ impl Vecs {
self.addrs.funded.reset_height()?; self.addrs.funded.reset_height()?;
self.addrs.empty.reset_height()?; self.addrs.empty.reset_height()?;
self.addrs.activity.reset_height()?; self.addrs.activity.reset_height()?;
self.addrs.reused.reset_height()?;
self.addrs.exposed.reset_height()?;
self.addrs.avg_amount.reset_height()?;
reset_state( reset_state(
&mut self.any_addr_indexes, &mut self.any_addr_indexes,
&mut self.addrs_data, &mut self.addrs_data,
@@ -454,6 +478,52 @@ impl Vecs {
// 6b. Compute address count sum (by addr_type -> all) // 6b. Compute address count sum (by addr_type -> all)
self.addrs.funded.compute_rest(starting_indexes, exit)?; self.addrs.funded.compute_rest(starting_indexes, exit)?;
self.addrs.empty.compute_rest(starting_indexes, exit)?; self.addrs.empty.compute_rest(starting_indexes, exit)?;
self.addrs.reused.compute_rest(
starting_indexes,
&outputs.by_type,
&inputs.by_type,
exit,
)?;
let t = &self.utxo_cohorts.type_;
let type_supply_sats = ByAddrType::new(|filter| {
let Filter::Type(ot) = filter else { unreachable!() };
&t.get(ot).metrics.supply.total.sats.height
});
self.addrs.exposed.compute_rest(
starting_indexes,
prices,
&self.utxo_cohorts.all.metrics.supply.total.sats.height,
&type_supply_sats,
exit,
)?;
// Average amount (supply / utxo_count, supply / funded_addr_count) for `all` and per addr type.
let all_m = &self.utxo_cohorts.all.metrics;
self.addrs.avg_amount.all.compute(
prices,
&all_m.supply.total.sats.height,
&all_m.outputs.unspent_count.height,
&self.addrs.funded.all.height,
starting_indexes.height,
exit,
)?;
for ((ot, avg), (_, funded)) in self
.addrs
.avg_amount
.by_addr_type
.iter_mut()
.zip(self.addrs.funded.by_addr_type.iter())
{
let type_m = &t.get(ot).metrics;
avg.compute(
prices,
&type_m.supply.total.sats.height,
&type_m.outputs.unspent_count.height,
&funded.height,
starting_indexes.height,
exit,
)?;
}
// 6c. Compute total_addr_count = addr_count + empty_addr_count // 6c. Compute total_addr_count = addr_count + empty_addr_count
self.addrs.total.compute( self.addrs.total.compute(
@@ -490,6 +560,15 @@ impl Vecs {
exit, exit,
)?; )?;
let all_supply_sats = self
.utxo_cohorts
.all
.metrics
.supply
.total
.sats
.height
.read_only_clone();
let all_utxo_count = self let all_utxo_count = self
.utxo_cohorts .utxo_cohorts
.all .all
@@ -498,8 +577,13 @@ impl Vecs {
.unspent_count .unspent_count
.height .height
.read_only_clone(); .read_only_clone();
self.addr_cohorts self.addr_cohorts.compute_rest_part2(
.compute_rest_part2(prices, starting_indexes, &all_utxo_count, exit)?; prices,
starting_indexes,
&all_supply_sats,
&all_utxo_count,
exit,
)?;
let exit = exit.clone(); let exit = exit.clone();
self.db.run_bg(move |db| { self.db.run_bg(move |db| {
@@ -524,6 +608,8 @@ impl Vecs {
.min(Height::from(self.addrs.funded.min_stateful_len())) .min(Height::from(self.addrs.funded.min_stateful_len()))
.min(Height::from(self.addrs.empty.min_stateful_len())) .min(Height::from(self.addrs.empty.min_stateful_len()))
.min(Height::from(self.addrs.activity.min_stateful_len())) .min(Height::from(self.addrs.activity.min_stateful_len()))
.min(Height::from(self.addrs.reused.min_stateful_len()))
.min(Height::from(self.addrs.exposed.min_stateful_len()))
.min(Height::from(self.coinblocks_destroyed.block.len())) .min(Height::from(self.coinblocks_destroyed.block.len()))
} }
} }
@@ -1,48 +0,0 @@
use brk_types::{
Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Minute10, Minute30, Month1, Month3,
Month6, Week1, Year1, Year10,
};
use vecdb::CachedVec;
use super::Vecs;
#[derive(Clone)]
pub struct CachedMappings {
pub minute10_first_height: CachedVec<Minute10, Height>,
pub minute30_first_height: CachedVec<Minute30, Height>,
pub hour1_first_height: CachedVec<Hour1, Height>,
pub hour4_first_height: CachedVec<Hour4, Height>,
pub hour12_first_height: CachedVec<Hour12, Height>,
pub day1_first_height: CachedVec<Day1, Height>,
pub day3_first_height: CachedVec<Day3, Height>,
pub week1_first_height: CachedVec<Week1, Height>,
pub month1_first_height: CachedVec<Month1, Height>,
pub month3_first_height: CachedVec<Month3, Height>,
pub month6_first_height: CachedVec<Month6, Height>,
pub year1_first_height: CachedVec<Year1, Height>,
pub year10_first_height: CachedVec<Year10, Height>,
pub halving_identity: CachedVec<Halving, Halving>,
pub epoch_identity: CachedVec<Epoch, Epoch>,
}
impl CachedMappings {
pub fn new(vecs: &Vecs) -> Self {
Self {
minute10_first_height: CachedVec::new(&vecs.minute10.first_height),
minute30_first_height: CachedVec::new(&vecs.minute30.first_height),
hour1_first_height: CachedVec::new(&vecs.hour1.first_height),
hour4_first_height: CachedVec::new(&vecs.hour4.first_height),
hour12_first_height: CachedVec::new(&vecs.hour12.first_height),
day1_first_height: CachedVec::new(&vecs.day1.first_height),
day3_first_height: CachedVec::new(&vecs.day3.first_height),
week1_first_height: CachedVec::new(&vecs.week1.first_height),
month1_first_height: CachedVec::new(&vecs.month1.first_height),
month3_first_height: CachedVec::new(&vecs.month3.first_height),
month6_first_height: CachedVec::new(&vecs.month6.first_height),
year1_first_height: CachedVec::new(&vecs.year1.first_height),
year10_first_height: CachedVec::new(&vecs.year10.first_height),
halving_identity: CachedVec::new(&vecs.halving.identity),
epoch_identity: CachedVec::new(&vecs.epoch.identity),
}
}
}
-24
View File
@@ -1,24 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Date, Day1, Height, StoredU64, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Day1, Day1>>>,
pub date: M::Stored<EagerVec<PcoVec<Day1, Date>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Day1, Height>>>,
pub height_count: M::Stored<EagerVec<PcoVec<Day1, StoredU64>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "day1_index", version)?,
date: EagerVec::forced_import(db, "date", version + Version::ONE)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
height_count: EagerVec::forced_import(db, "height_count", version)?,
})
}
}
-20
View File
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Day3, Height, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Day3, Day3>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Day3, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "day3_index", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
-22
View File
@@ -1,22 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Epoch, Height, StoredU64, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Epoch, Epoch>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Epoch, Height>>>,
pub height_count: M::Stored<EagerVec<PcoVec<Epoch, StoredU64>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "epoch", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
height_count: EagerVec::forced_import(db, "height_count", version)?,
})
}
}
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Halving, Height, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Halving, Halving>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Halving, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "halving", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
@@ -9,7 +9,6 @@ use brk_error::Result;
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub minute10: M::Stored<EagerVec<PcoVec<Height, Minute10>>>, pub minute10: M::Stored<EagerVec<PcoVec<Height, Minute10>>>,
pub minute30: M::Stored<EagerVec<PcoVec<Height, Minute30>>>, pub minute30: M::Stored<EagerVec<PcoVec<Height, Minute30>>>,
pub hour1: M::Stored<EagerVec<PcoVec<Height, Hour1>>>, pub hour1: M::Stored<EagerVec<PcoVec<Height, Hour1>>>,
@@ -31,7 +30,6 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs { impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> { pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self { Ok(Self {
identity: EagerVec::forced_import(db, "height", version)?,
minute10: EagerVec::forced_import(db, "minute10", version)?, minute10: EagerVec::forced_import(db, "minute10", version)?,
minute30: EagerVec::forced_import(db, "minute30", version)?, minute30: EagerVec::forced_import(db, "minute30", version)?,
hour1: EagerVec::forced_import(db, "hour1", version)?, hour1: EagerVec::forced_import(db, "hour1", version)?,
-20
View File
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Height, Hour1, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Hour1, Hour1>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Hour1, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "hour1_index", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
-20
View File
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Height, Hour12, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Hour12, Hour12>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Hour12, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "hour12_index", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
-20
View File
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Height, Hour4, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Hour4, Hour4>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Hour4, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "hour4_index", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Height, Minute10, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Minute10, Minute10>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Minute10, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "minute10_index", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
@@ -1,20 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Height, Minute30, Version};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub identity: M::Stored<EagerVec<PcoVec<Minute30, Minute30>>>,
pub first_height: M::Stored<EagerVec<PcoVec<Minute30, Height>>>,
}
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "minute30_index", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}
+50 -137
View File
@@ -1,25 +1,11 @@
mod addr; mod addr;
mod cached_mappings;
mod day1;
mod day3;
mod epoch;
mod halving;
mod height; mod height;
mod hour1; mod resolution;
mod hour12;
mod hour4;
mod minute10;
mod minute30;
mod month1;
mod month3;
mod month6;
pub mod timestamp; pub mod timestamp;
mod tx_heights;
mod tx_index; mod tx_index;
mod txin_index; mod txin_index;
mod txout_index; mod txout_index;
mod week1;
mod year1;
mod year10;
use std::path::Path; use std::path::Path;
@@ -27,35 +13,37 @@ use brk_error::Result;
use brk_indexer::Indexer; use brk_indexer::Indexer;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{ use brk_types::{
Date, Day1, Day3, Height, Hour1, Hour4, Hour12, Indexes, Minute10, Minute30, Month1, Month3, Date, Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Indexes, Minute10, Minute30,
Month6, Version, Week1, Year1, Year10, Month1, Month3, Month6, Version, Week1, Year1, Year10,
}; };
use vecdb::{CachedVec, Database, Exit, ReadableVec, Rw, StorageMode}; use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::internal::db_utils::{finalize_db, open_db}; use crate::internal::db_utils::{finalize_db, open_db};
pub use addr::Vecs as AddrVecs; pub use addr::Vecs as AddrVecs;
pub use cached_mappings::CachedMappings;
pub use day1::Vecs as Day1Vecs;
pub use day3::Vecs as Day3Vecs;
pub use epoch::Vecs as EpochVecs;
pub use halving::Vecs as HalvingVecs;
pub use height::Vecs as HeightVecs; pub use height::Vecs as HeightVecs;
pub use hour1::Vecs as Hour1Vecs; pub use resolution::{DatedResolutionVecs, ResolutionVecs};
pub use hour4::Vecs as Hour4Vecs;
pub use hour12::Vecs as Hour12Vecs;
pub use minute10::Vecs as Minute10Vecs;
pub use minute30::Vecs as Minute30Vecs;
pub use month1::Vecs as Month1Vecs;
pub use month3::Vecs as Month3Vecs;
pub use month6::Vecs as Month6Vecs;
pub use timestamp::Timestamps; pub use timestamp::Timestamps;
pub use tx_heights::TxHeights;
pub use tx_index::Vecs as TxIndexVecs; pub use tx_index::Vecs as TxIndexVecs;
pub use txin_index::Vecs as TxInIndexVecs; pub use txin_index::Vecs as TxInIndexVecs;
pub use txout_index::Vecs as TxOutIndexVecs; pub use txout_index::Vecs as TxOutIndexVecs;
pub use week1::Vecs as Week1Vecs;
pub use year1::Vecs as Year1Vecs; pub type Minute10Vecs<M = Rw> = ResolutionVecs<Minute10, M>;
pub use year10::Vecs as Year10Vecs; pub type Minute30Vecs<M = Rw> = ResolutionVecs<Minute30, M>;
pub type Hour1Vecs<M = Rw> = ResolutionVecs<Hour1, M>;
pub type Hour4Vecs<M = Rw> = ResolutionVecs<Hour4, M>;
pub type Hour12Vecs<M = Rw> = ResolutionVecs<Hour12, M>;
pub type Day1Vecs<M = Rw> = DatedResolutionVecs<Day1, M>;
pub type Day3Vecs<M = Rw> = DatedResolutionVecs<Day3, M>;
pub type EpochVecs<M = Rw> = ResolutionVecs<Epoch, M>;
pub type HalvingVecs<M = Rw> = ResolutionVecs<Halving, M>;
pub type Week1Vecs<M = Rw> = DatedResolutionVecs<Week1, M>;
pub type Month1Vecs<M = Rw> = DatedResolutionVecs<Month1, M>;
pub type Month3Vecs<M = Rw> = DatedResolutionVecs<Month3, M>;
pub type Month6Vecs<M = Rw> = DatedResolutionVecs<Month6, M>;
pub type Year1Vecs<M = Rw> = DatedResolutionVecs<Year1, M>;
pub type Year10Vecs<M = Rw> = DatedResolutionVecs<Year10, M>;
pub const DB_NAME: &str = "indexes"; pub const DB_NAME: &str = "indexes";
@@ -63,7 +51,7 @@ pub const DB_NAME: &str = "indexes";
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
db: Database, db: Database,
#[traversable(skip)] #[traversable(skip)]
pub cached_mappings: CachedMappings, pub tx_heights: TxHeights,
pub addr: AddrVecs, pub addr: AddrVecs,
pub height: HeightVecs<M>, pub height: HeightVecs<M>,
pub epoch: EpochVecs<M>, pub epoch: EpochVecs<M>,
@@ -99,50 +87,32 @@ impl Vecs {
let addr = AddrVecs::forced_import(version, indexer); let addr = AddrVecs::forced_import(version, indexer);
let height = HeightVecs::forced_import(&db, version)?; let height = HeightVecs::forced_import(&db, version)?;
let epoch = EpochVecs::forced_import(&db, version)?; let epoch = ResolutionVecs::forced_import(&db, version)?;
let halving = HalvingVecs::forced_import(&db, version)?; let halving = ResolutionVecs::forced_import(&db, version)?;
let minute10 = Minute10Vecs::forced_import(&db, version)?; let minute10 = ResolutionVecs::forced_import(&db, version)?;
let minute30 = Minute30Vecs::forced_import(&db, version)?; let minute30 = ResolutionVecs::forced_import(&db, version)?;
let hour1 = Hour1Vecs::forced_import(&db, version)?; let hour1 = ResolutionVecs::forced_import(&db, version)?;
let hour4 = Hour4Vecs::forced_import(&db, version)?; let hour4 = ResolutionVecs::forced_import(&db, version)?;
let hour12 = Hour12Vecs::forced_import(&db, version)?; let hour12 = ResolutionVecs::forced_import(&db, version)?;
let day1 = Day1Vecs::forced_import(&db, version)?; let day1 = Day1Vecs::forced_import(&db, version)?;
let day3 = Day3Vecs::forced_import(&db, version)?; let day3 = DatedResolutionVecs::forced_import(&db, version)?;
let week1 = Week1Vecs::forced_import(&db, version)?; let week1 = DatedResolutionVecs::forced_import(&db, version)?;
let month1 = Month1Vecs::forced_import(&db, version)?; let month1 = DatedResolutionVecs::forced_import(&db, version)?;
let month3 = Month3Vecs::forced_import(&db, version)?; let month3 = DatedResolutionVecs::forced_import(&db, version)?;
let month6 = Month6Vecs::forced_import(&db, version)?; let month6 = DatedResolutionVecs::forced_import(&db, version)?;
let year1 = Year1Vecs::forced_import(&db, version)?; let year1 = DatedResolutionVecs::forced_import(&db, version)?;
let year10 = Year10Vecs::forced_import(&db, version)?; let year10 = DatedResolutionVecs::forced_import(&db, version)?;
let tx_index = TxIndexVecs::forced_import(&db, version, indexer)?; let tx_index = TxIndexVecs::forced_import(&db, version, indexer)?;
let txin_index = TxInIndexVecs::forced_import(version, indexer); let txin_index = TxInIndexVecs::forced_import(version, indexer);
let txout_index = TxOutIndexVecs::forced_import(version, indexer); let txout_index = TxOutIndexVecs::forced_import(version, indexer);
let cached_mappings = CachedMappings {
minute10_first_height: CachedVec::new(&minute10.first_height),
minute30_first_height: CachedVec::new(&minute30.first_height),
hour1_first_height: CachedVec::new(&hour1.first_height),
hour4_first_height: CachedVec::new(&hour4.first_height),
hour12_first_height: CachedVec::new(&hour12.first_height),
day1_first_height: CachedVec::new(&day1.first_height),
day3_first_height: CachedVec::new(&day3.first_height),
week1_first_height: CachedVec::new(&week1.first_height),
month1_first_height: CachedVec::new(&month1.first_height),
month3_first_height: CachedVec::new(&month3.first_height),
month6_first_height: CachedVec::new(&month6.first_height),
year1_first_height: CachedVec::new(&year1.first_height),
year10_first_height: CachedVec::new(&year10.first_height),
halving_identity: CachedVec::new(&halving.identity),
epoch_identity: CachedVec::new(&epoch.identity),
};
let timestamp = Timestamps::forced_import_from_locals( let timestamp = Timestamps::forced_import_from_locals(
&db, version, &minute10, &minute30, &hour1, &hour4, &hour12, &day1, &day3, &week1, &db, version, &minute10, &minute30, &hour1, &hour4, &hour12, &day1, &day3, &week1,
&month1, &month3, &month6, &year1, &year10, &month1, &month3, &month6, &year1, &year10,
)?; )?;
let this = Self { let this = Self {
cached_mappings, tx_heights: TxHeights::init(indexer),
addr, addr,
height, height,
epoch, epoch,
@@ -179,6 +149,8 @@ impl Vecs {
) -> Result<Indexes> { ) -> Result<Indexes> {
self.db.sync_bg_tasks()?; self.db.sync_bg_tasks()?;
self.tx_heights.update(indexer, starting_indexes.height);
// timestamp_monotonic must be computed first — other mappings read it // timestamp_monotonic must be computed first — other mappings read it
self.timestamp self.timestamp
.compute_monotonic(indexer, starting_indexes.height, exit)?; .compute_monotonic(indexer, starting_indexes.height, exit)?;
@@ -193,7 +165,7 @@ impl Vecs {
let starting_day1 = let starting_day1 =
self.compute_calendar_mappings(indexer, &starting_indexes, prev_height, exit)?; self.compute_calendar_mappings(indexer, &starting_indexes, prev_height, exit)?;
self.compute_period_vecs(indexer, &starting_indexes, prev_height, starting_day1, exit)?; self.compute_period_vecs(&starting_indexes, prev_height, starting_day1, exit)?;
self.timestamp.compute_per_resolution( self.timestamp.compute_per_resolution(
indexer, indexer,
@@ -253,11 +225,6 @@ impl Vecs {
&indexer.vecs.transactions.txid, &indexer.vecs.transactions.txid,
exit, exit,
)?; )?;
self.height.identity.compute_from_index(
starting_indexes.height,
&indexer.vecs.blocks.weight,
exit,
)?;
Ok(()) Ok(())
} }
@@ -313,7 +280,7 @@ impl Vecs {
starting_day1 starting_day1
}; };
self.compute_epoch(indexer, starting_indexes, prev_height, exit)?; self.compute_epoch(indexer, starting_indexes, exit)?;
self.height.week1.compute_transform( self.height.week1.compute_transform(
starting_indexes.height, starting_indexes.height,
@@ -359,65 +326,34 @@ impl Vecs {
&mut self, &mut self,
indexer: &Indexer, indexer: &Indexer,
starting_indexes: &Indexes, starting_indexes: &Indexes,
prev_height: Height,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
let starting_difficulty = self
.height
.epoch
.collect_one(prev_height)
.unwrap_or_default();
self.height.epoch.compute_from_index( self.height.epoch.compute_from_index(
starting_indexes.height, starting_indexes.height,
&indexer.vecs.blocks.weight, &indexer.vecs.blocks.weight,
exit, exit,
)?; )?;
self.epoch.first_height.compute_first_per_index( self.epoch.first_height.inner.compute_first_per_index(
starting_indexes.height, starting_indexes.height,
&self.height.epoch, &self.height.epoch,
exit, exit,
)?; )?;
self.epoch.identity.compute_from_index(
starting_difficulty,
&self.epoch.first_height,
exit,
)?;
self.epoch.height_count.compute_count_from_indexes(
starting_difficulty,
&self.epoch.first_height,
&self.timestamp.monotonic,
exit,
)?;
let starting_halving = self
.height
.halving
.collect_one(prev_height)
.unwrap_or_default();
self.height.halving.compute_from_index( self.height.halving.compute_from_index(
starting_indexes.height, starting_indexes.height,
&indexer.vecs.blocks.weight, &indexer.vecs.blocks.weight,
exit, exit,
)?; )?;
self.halving.first_height.compute_first_per_index( self.halving.first_height.inner.compute_first_per_index(
starting_indexes.height, starting_indexes.height,
&self.height.halving, &self.height.halving,
exit, exit,
)?; )?;
self.halving.identity.compute_from_index(
starting_halving,
&self.halving.first_height,
exit,
)?;
Ok(()) Ok(())
} }
fn compute_period_vecs( fn compute_period_vecs(
&mut self, &mut self,
indexer: &Indexer,
starting_indexes: &Indexes, starting_indexes: &Indexes,
prev_height: Height, prev_height: Height,
starting_day1: Day1, starting_day1: Day1,
@@ -425,19 +361,11 @@ impl Vecs {
) -> Result<()> { ) -> Result<()> {
macro_rules! basic_period { macro_rules! basic_period {
($period:ident) => { ($period:ident) => {
self.$period.first_height.compute_first_per_index( self.$period.first_height.inner.compute_first_per_index(
starting_indexes.height, starting_indexes.height,
&self.height.$period, &self.height.$period,
exit, exit,
)?; )?;
self.$period.identity.compute_from_index(
self.height
.$period
.collect_one(prev_height)
.unwrap_or_default(),
&self.$period.first_height,
exit,
)?;
}; };
} }
@@ -446,34 +374,23 @@ impl Vecs {
basic_period!(hour1); basic_period!(hour1);
basic_period!(hour4); basic_period!(hour4);
basic_period!(hour12); basic_period!(hour12);
basic_period!(day3);
self.day1.first_height.compute_first_per_index( self.day1.first_height.inner.compute_first_per_index(
starting_indexes.height, starting_indexes.height,
&self.height.day1, &self.height.day1,
exit, exit,
)?; )?;
self.day1
.identity
.compute_from_index(starting_day1, &self.day1.first_height, exit)?;
self.day1.date.compute_transform( self.day1.date.compute_transform(
starting_day1, starting_day1,
&self.day1.identity, &self.day1.first_height,
|(di, ..)| (di, Date::from(di)), |(di, ..)| (di, Date::from(di)),
exit, exit,
)?; )?;
self.day1.height_count.compute_count_from_indexes(
starting_day1,
&self.day1.first_height,
&indexer.vecs.blocks.weight,
exit,
)?;
let ts = &self.timestamp.monotonic; let ts = &self.timestamp.monotonic;
macro_rules! dated_period { macro_rules! dated_period {
($period:ident) => {{ ($period:ident) => {{
self.$period.first_height.compute_first_per_index( self.$period.first_height.inner.compute_first_per_index(
starting_indexes.height, starting_indexes.height,
&self.height.$period, &self.height.$period,
exit, exit,
@@ -483,11 +400,6 @@ impl Vecs {
.$period .$period
.collect_one(prev_height) .collect_one(prev_height)
.unwrap_or_default(); .unwrap_or_default();
self.$period.identity.compute_from_index(
start,
&self.$period.first_height,
exit,
)?;
self.$period.date.compute_transform( self.$period.date.compute_transform(
start, start,
&self.$period.first_height, &self.$period.first_height,
@@ -497,6 +409,7 @@ impl Vecs {
}}; }};
} }
dated_period!(day3);
dated_period!(week1); dated_period!(week1);
dated_period!(month1); dated_period!(month1);
dated_period!(month3); dated_period!(month3);

Some files were not shown because too many files have changed in this diff Show More