mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-29 23:59:27 -07:00
global: snapshot
This commit is contained in:
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
rustflags = ["-C", "target-cpu=native"]
|
||||
97
Cargo.lock
generated
97
Cargo.lock
generated
@@ -134,15 +134,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
@@ -471,14 +462,11 @@ dependencies = [
|
||||
"brk_server",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"importmap",
|
||||
"minreq",
|
||||
"serde",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"vecdb",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -748,6 +736,8 @@ dependencies = [
|
||||
"brk_traversable",
|
||||
"brk_types",
|
||||
"derive_more",
|
||||
"importmap",
|
||||
"include_dir",
|
||||
"jiff",
|
||||
"quick_cache",
|
||||
"schemars",
|
||||
@@ -1249,17 +1239,6 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
@@ -1450,7 +1429,6 @@ checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1954,9 +1932,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "importmap"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a45c4c5e8b4d0a6aed90f3a14125bd3af3bca73d49d44e8f8b764311903b5213"
|
||||
checksum = "f650142f4c83e122ae10a49dbaa88593dc4b20158124409856adbae847775938"
|
||||
dependencies = [
|
||||
"rapidhash",
|
||||
"serde",
|
||||
@@ -1964,6 +1942,25 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
|
||||
dependencies = [
|
||||
"include_dir_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir_macros"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.4"
|
||||
@@ -2644,9 +2641,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quickmatch"
|
||||
version = "0.1.8"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da298bd4f885924ca16faab70af76893797eb0164dfa4e86a14ffd983b8cb14"
|
||||
checksum = "e6f3930eacf56a69f668e37113288d810cc83a7af7afde8d7bf81d73148a5db7"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
@@ -2745,9 +2742,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rawdb"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb8999fc8f80521c13091f98e1a24bde2448b29c0637f008dc470ed84d58f703"
|
||||
checksum = "12f4f316acfc1844da8b3c84163aabd98f155df82d3b57ce4075dda550e1752d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@@ -3689,9 +3686,9 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
|
||||
|
||||
[[package]]
|
||||
name = "vecdb"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a31985bee527adda1601eab9c72de0a567be83a08e80e646b79afb77ab14970"
|
||||
checksum = "28d56bd525ca319c5772dcc91b40caa6947dfb6c56b63c4a9b2268ee24254c05"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"log",
|
||||
@@ -3710,9 +3707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vecdb_derive"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61716cbeb322dcc9838cea11b533cea278f9592b2ef2ce520595849833cfee1c"
|
||||
checksum = "17c227a593e7a08c3d00babaa81aec624052d2c6a16ba127fb8f491f273c9751"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -4206,44 +4203,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdd8a47718a4ee5fe78e07667cd36f3de80e7c2bfe727c7074245ffc7303c037"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
|
||||
@@ -79,7 +79,7 @@ serde_json = { version = "1.0.149", features = ["float_roundtrip", "preserve_ord
|
||||
smallvec = "1.15.1"
|
||||
tokio = { version = "1.49.0", features = ["rt-multi-thread"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
vecdb = { version = "0.5.9", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
vecdb = { version = "0.5.10", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
|
||||
[workspace.metadata.release]
|
||||
|
||||
@@ -6,12 +6,11 @@ homepage.workspace = true
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
full = [
|
||||
"bencher",
|
||||
"binder",
|
||||
"bindgen",
|
||||
"client",
|
||||
"computer",
|
||||
"error",
|
||||
@@ -31,7 +30,7 @@ full = [
|
||||
"types",
|
||||
]
|
||||
bencher = ["brk_bencher"]
|
||||
binder = ["brk_bindgen"]
|
||||
bindgen = ["brk_bindgen"]
|
||||
client = ["brk_client"]
|
||||
computer = ["brk_computer"]
|
||||
error = ["brk_error"]
|
||||
|
||||
@@ -61,7 +61,3 @@ Feature flags match crate names without the `brk_` prefix. Use `full` to enable
|
||||
| [brk_error](https://docs.rs/brk_error) | Error types |
|
||||
| [brk_logger](https://docs.rs/brk_logger) | Logging infrastructure |
|
||||
| [brk_bencher](https://docs.rs/brk_bencher) | Benchmarking utilities |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
#[doc(inline)]
|
||||
pub use brk_bencher as bencher;
|
||||
|
||||
#[cfg(feature = "binder")]
|
||||
#[cfg(feature = "bindgen")]
|
||||
#[doc(inline)]
|
||||
pub use brk_bindgen as binder;
|
||||
pub use brk_bindgen as bindgen;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_error = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
plotters = "0.3.7"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_cohort = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class BrkError extends Error {{
|
||||
* @typedef {{Object}} MetricPattern
|
||||
* @property {{string}} name - The metric name
|
||||
* @property {{Readonly<Partial<Record<Index, MetricEndpointBuilder<T>>>>}} by - Index endpoints as lazy getters. Access via .by.dateindex or .by['dateindex']
|
||||
* @property {{() => Index[]}} indexes - Get the list of available indexes
|
||||
* @property {{() => readonly Index[]}} indexes - Get the list of available indexes
|
||||
* @property {{(index: Index) => MetricEndpointBuilder<T>|undefined}} get - Get an endpoint for a specific index
|
||||
*/
|
||||
|
||||
@@ -333,14 +333,14 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
|
||||
// Generate index array constants (e.g., _i1 = ["dateindex", "height"])
|
||||
for (i, pattern) in patterns.iter().enumerate() {
|
||||
write!(output, "const _i{} = [", i + 1).unwrap();
|
||||
write!(output, "const _i{} = /** @type {{const}} */ ([", i + 1).unwrap();
|
||||
for (j, index) in pattern.indexes.iter().enumerate() {
|
||||
if j > 0 {
|
||||
write!(output, ", ").unwrap();
|
||||
}
|
||||
write!(output, "\"{}\"", index.serialize_long()).unwrap();
|
||||
}
|
||||
writeln!(output, "];").unwrap();
|
||||
writeln!(output, "]);").unwrap();
|
||||
}
|
||||
writeln!(output).unwrap();
|
||||
|
||||
@@ -352,11 +352,10 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
* @template T
|
||||
* @param {{BrkClientBase}} client
|
||||
* @param {{string}} name - The metric vec name
|
||||
* @param {{readonly string[]}} indexes - The supported indexes
|
||||
* @returns {{MetricPattern<T>}}
|
||||
* @param {{readonly Index[]}} indexes - The supported indexes
|
||||
*/
|
||||
function _mp(client, name, indexes) {{
|
||||
const by = {{}};
|
||||
const by = /** @type {{any}} */ ({{}});
|
||||
for (const idx of indexes) {{
|
||||
Object.defineProperty(by, idx, {{
|
||||
get() {{ return _endpoint(client, name, idx); }},
|
||||
@@ -368,6 +367,7 @@ function _mp(client, name, indexes) {{
|
||||
name,
|
||||
by,
|
||||
indexes() {{ return indexes; }},
|
||||
/** @param {{Index}} index */
|
||||
get(index) {{ return indexes.includes(index) ? _endpoint(client, name, index) : undefined; }}
|
||||
}};
|
||||
}}
|
||||
@@ -392,7 +392,7 @@ function _mp(client, name, indexes) {{
|
||||
|
||||
writeln!(
|
||||
output,
|
||||
"/** @template T @typedef {{{{ name: string, by: {}, indexes: () => Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }}}} {} */",
|
||||
"/** @template T @typedef {{{{ name: string, by: {}, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }}}} {} */",
|
||||
by_type, pattern.name
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2,7 +2,10 @@ use std::{collections::BTreeMap, io};
|
||||
|
||||
use crate::ref_to_type_name;
|
||||
use oas3::Spec;
|
||||
use oas3::spec::{ObjectOrReference, ObjectSchema, Operation, ParameterIn, PathItem, Schema, SchemaType, SchemaTypeSet};
|
||||
use oas3::spec::{
|
||||
ObjectOrReference, ObjectSchema, Operation, ParameterIn, PathItem, Schema, SchemaType,
|
||||
SchemaTypeSet,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
/// Type schema extracted from OpenAPI components
|
||||
@@ -156,7 +159,7 @@ pub fn extract_endpoints(spec: &Spec) -> Vec<Endpoint> {
|
||||
|
||||
for (path, path_item) in paths {
|
||||
for (method, operation) in get_operations(path_item) {
|
||||
if let Some(endpoint) = extract_endpoint(path, &method, operation) {
|
||||
if let Some(endpoint) = extract_endpoint(path, method, operation) {
|
||||
endpoints.push(endpoint);
|
||||
}
|
||||
}
|
||||
@@ -218,11 +221,7 @@ fn extract_path_parameters(path: &str, operation: &Operation) -> Vec<Parameter>
|
||||
// Extract parameter names from the path in order (e.g., "/api/metric/{metric}/{index}" -> ["metric", "index"])
|
||||
let path_order: Vec<&str> = path
|
||||
.split('/')
|
||||
.filter_map(|segment| {
|
||||
segment
|
||||
.strip_prefix('{')
|
||||
.and_then(|s| s.strip_suffix('}'))
|
||||
})
|
||||
.filter_map(|segment| segment.strip_prefix('{').and_then(|s| s.strip_suffix('}')))
|
||||
.collect();
|
||||
|
||||
// Get all path parameters from the operation
|
||||
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_alloc = { workspace = true }
|
||||
@@ -23,15 +22,11 @@ brk_rpc = { workspace = true }
|
||||
brk_server = { workspace = true }
|
||||
clap = { version = "4.5.54", features = ["derive", "string"] }
|
||||
color-eyre = { workspace = true }
|
||||
importmap = "0.1.3"
|
||||
# importmap = { path = "../../../importmap" }
|
||||
tracing = { workspace = true }
|
||||
minreq = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
toml = "0.9.11"
|
||||
vecdb = { workspace = true }
|
||||
zip = { version = "7.0.0", default-features = false, features = ["deflate"] }
|
||||
|
||||
[[bin]]
|
||||
name = "brk"
|
||||
|
||||
@@ -21,6 +21,30 @@ Run a full BRK instance: index the blockchain, compute metrics, serve the API, a
|
||||
cargo install --locked brk_cli
|
||||
```
|
||||
|
||||
For pre-release versions (alpha, beta, rc), specify the version explicitly:
|
||||
|
||||
```bash
|
||||
# Find the latest version
|
||||
cargo search brk_cli
|
||||
|
||||
# Install a specific version
|
||||
cargo install --locked brk_cli --version "VERSION"
|
||||
```
|
||||
|
||||
See [crates.io/crates/brk_cli/versions](https://crates.io/crates/brk_cli/versions) for all available versions.
|
||||
|
||||
For better performance, build with native CPU optimizations:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS="-C target-cpu=native" cargo install --locked brk_cli
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Bitcoin Core with accessible `blk*.dat` files
|
||||
- ~400 GB disk space
|
||||
- 12+ GB RAM recommended
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
@@ -45,11 +69,7 @@ brk --help
|
||||
|
||||
## Performance
|
||||
|
||||
| Machine | Time | Disk | Peak Disk | Memory | Peak Memory |
|
||||
|---------|------|------|-----------|--------|-------------|
|
||||
| MBP M3 Pro (36GB, internal SSD) | 5.2h | 341 GB | 415 GB | 6.4 GB | 12 GB |
|
||||
|
||||
Full benchmark data: [`https://github.com/bitcoinresearchkit/benches/tree/main/brk`](/benches/brk)
|
||||
See [brk_computer](https://docs.rs/brk_computer) for full pipeline benchmarks.
|
||||
|
||||
## Built On
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,8 @@ use brk_rpc::{Auth, Client};
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::{default_brk_path, dot_brk_path, website::Website};
|
||||
use crate::{default_brk_path, dot_brk_path, fix_user_path, website::Website};
|
||||
|
||||
const DOWNLOADS: &str = "downloads";
|
||||
|
||||
#[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[command(version, about)]
|
||||
@@ -234,58 +233,36 @@ Finally, you can run the program with '-h' for help."
|
||||
self.bitcoindir
|
||||
.as_ref()
|
||||
.map_or_else(Client::default_bitcoin_path, |s| {
|
||||
Self::fix_user_path(s.as_ref())
|
||||
fix_user_path(s.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn blocksdir(&self) -> PathBuf {
|
||||
self.blocksdir.as_ref().map_or_else(
|
||||
|| self.bitcoindir().join("blocks"),
|
||||
|blocksdir| Self::fix_user_path(blocksdir.as_str()),
|
||||
|blocksdir| fix_user_path(blocksdir.as_str()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn brkdir(&self) -> PathBuf {
|
||||
self.brkdir
|
||||
.as_ref()
|
||||
.map_or_else(default_brk_path, |s| Self::fix_user_path(s.as_ref()))
|
||||
.map_or_else(default_brk_path, |s| fix_user_path(s.as_ref()))
|
||||
}
|
||||
|
||||
pub fn harsdir(&self) -> PathBuf {
|
||||
self.brkdir().join("hars")
|
||||
}
|
||||
|
||||
pub fn downloads_dir(&self) -> PathBuf {
|
||||
dot_brk_path().join(DOWNLOADS)
|
||||
}
|
||||
|
||||
fn path_cookiefile(&self) -> PathBuf {
|
||||
self.rpccookiefile.as_ref().map_or_else(
|
||||
|| self.bitcoindir().join(".cookie"),
|
||||
|p| Self::fix_user_path(p.as_str()),
|
||||
|p| fix_user_path(p.as_str()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fix_user_path(path: &str) -> PathBuf {
|
||||
let fix = move |pattern: &str| {
|
||||
if path.starts_with(pattern) {
|
||||
let path = &path
|
||||
.replace(&format!("{pattern}/"), "")
|
||||
.replace(pattern, "");
|
||||
|
||||
let home = std::env::var("HOME").unwrap();
|
||||
|
||||
Some(Path::new(&home).join(path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
fix("~").unwrap_or_else(|| fix("$HOME").unwrap_or_else(|| PathBuf::from(&path)))
|
||||
}
|
||||
|
||||
pub fn website(&self) -> Website {
|
||||
self.website.unwrap_or(Website::Bitview)
|
||||
self.website.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fetch(&self) -> bool {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
io::Cursor,
|
||||
path::PathBuf,
|
||||
thread::{self, sleep},
|
||||
time::Duration,
|
||||
@@ -16,8 +15,7 @@ use brk_iterator::Blocks;
|
||||
use brk_mempool::Mempool;
|
||||
use brk_query::AsyncQuery;
|
||||
use brk_reader::Reader;
|
||||
use brk_server::{Server, VERSION};
|
||||
use importmap::ImportMap;
|
||||
use brk_server::{Server, WebsiteSource};
|
||||
use tracing::info;
|
||||
use vecdb::Exit;
|
||||
|
||||
@@ -25,7 +23,7 @@ mod config;
|
||||
mod paths;
|
||||
mod website;
|
||||
|
||||
use crate::{config::Config, paths::*};
|
||||
use crate::{config::Config, paths::*, website::Website};
|
||||
|
||||
pub fn main() -> color_eyre::Result<()> {
|
||||
// Can't increase main thread's stack size, thus we need to use another thread
|
||||
@@ -80,89 +78,22 @@ pub fn run() -> color_eyre::Result<()> {
|
||||
|
||||
let query = AsyncQuery::build(&reader, &indexer, &computer, Some(mempool));
|
||||
|
||||
let website = config.website();
|
||||
|
||||
let downloads_path = config.downloads_dir();
|
||||
let data_path = config.brkdir();
|
||||
|
||||
let future = async move {
|
||||
// Try to find local dev directories - check cwd and parent directories
|
||||
let find_dev_dirs = || -> Option<(PathBuf, PathBuf)> {
|
||||
let mut dir = std::env::current_dir().ok()?;
|
||||
loop {
|
||||
let websites = dir.join("websites");
|
||||
let modules = dir.join("modules");
|
||||
if websites.exists() && modules.exists() {
|
||||
return Some((websites, modules));
|
||||
}
|
||||
// Stop at workspace root (crates/ indicates we're there)
|
||||
if dir.join("crates").exists() {
|
||||
return None;
|
||||
}
|
||||
dir = dir.parent()?.to_path_buf();
|
||||
}
|
||||
};
|
||||
|
||||
let dev_dirs = find_dev_dirs();
|
||||
let is_dev = dev_dirs.is_some();
|
||||
|
||||
let bundle_path = if website.is_some() {
|
||||
let websites_path = if let Some((websites, _modules)) = dev_dirs {
|
||||
websites
|
||||
} else {
|
||||
let downloaded_brk_path = downloads_path.join(format!("brk-{VERSION}"));
|
||||
let downloaded_websites_path = downloaded_brk_path.join("websites");
|
||||
|
||||
if !fs::exists(&downloaded_websites_path)? {
|
||||
info!("Downloading source from Github...");
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/bitcoinresearchkit/brk/archive/refs/tags/v{VERSION}.zip",
|
||||
);
|
||||
|
||||
let response = minreq::get(url).with_timeout(60).send()?;
|
||||
let bytes = response.as_bytes();
|
||||
let cursor = Cursor::new(bytes);
|
||||
|
||||
let mut zip = zip::ZipArchive::new(cursor).unwrap();
|
||||
|
||||
zip.extract(downloads_path).unwrap();
|
||||
}
|
||||
|
||||
downloaded_websites_path
|
||||
};
|
||||
|
||||
Some(websites_path.join(website.to_folder_name()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Generate import map for cache busting (disabled in dev mode)
|
||||
if let Some(ref path) = bundle_path {
|
||||
let map = if is_dev {
|
||||
ImportMap::empty()
|
||||
} else {
|
||||
match ImportMap::scan(path, "") {
|
||||
Ok(map) => map,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to generate importmap: {e}");
|
||||
ImportMap::empty()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let html_path = path.join("index.html");
|
||||
if let Ok(html) = fs::read_to_string(&html_path)
|
||||
&& let Some(updated) = map.update_html(&html)
|
||||
{
|
||||
let _ = fs::write(&html_path, updated);
|
||||
if !is_dev {
|
||||
info!("Updated importmap in index.html");
|
||||
}
|
||||
let website_source = match config.website() {
|
||||
Website::Enabled(false) => WebsiteSource::Disabled,
|
||||
Website::Path(p) => WebsiteSource::Filesystem(p),
|
||||
Website::Enabled(true) => {
|
||||
// Prefer local filesystem if available, otherwise use embedded
|
||||
match find_local_website_dir() {
|
||||
Some(path) => WebsiteSource::Filesystem(path),
|
||||
None => WebsiteSource::Embedded,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let server = Server::new(&query, data_path, bundle_path);
|
||||
let future = async move {
|
||||
let server = Server::new(&query, data_path, website_source);
|
||||
|
||||
tokio::spawn(async move {
|
||||
server.serve(true).await.unwrap();
|
||||
@@ -201,3 +132,12 @@ pub fn run() -> color_eyre::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Path to website directory relative to this crate (only valid at dev machine)
|
||||
const DEV_WEBSITE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../website");
|
||||
|
||||
/// Returns local website path if it exists (dev mode)
|
||||
fn find_local_website_dir() -> Option<PathBuf> {
|
||||
let path = PathBuf::from(DEV_WEBSITE_DIR);
|
||||
path.exists().then_some(path)
|
||||
}
|
||||
|
||||
@@ -12,3 +12,12 @@ pub fn dot_brk_log_path() -> PathBuf {
|
||||
pub fn default_brk_path() -> PathBuf {
|
||||
dot_brk_path()
|
||||
}
|
||||
|
||||
pub fn fix_user_path(path: &str) -> PathBuf {
|
||||
if let Some(rest) = path.strip_prefix("~/").or(path.strip_prefix("$HOME/"))
|
||||
&& let Ok(home) = std::env::var("HOME")
|
||||
{
|
||||
return PathBuf::from(home).join(rest);
|
||||
}
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
use clap::ValueEnum;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, ValueEnum)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
use crate::paths::fix_user_path;
|
||||
|
||||
/// Website configuration:
|
||||
/// - `true` or omitted: serve embedded website
|
||||
/// - `false`: disable website serving
|
||||
/// - `"/path/to/website"`: serve custom website from path
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Website {
|
||||
None,
|
||||
Bitview,
|
||||
Custom,
|
||||
Enabled(bool),
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
impl Website {
|
||||
pub fn is_none(&self) -> bool {
|
||||
self == &Self::None
|
||||
}
|
||||
|
||||
pub fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
|
||||
pub fn to_folder_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Custom => "custom",
|
||||
Self::Bitview => "bitview",
|
||||
Self::None => unreachable!(),
|
||||
}
|
||||
impl Default for Website {
|
||||
fn default() -> Self {
|
||||
Self::Enabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl FromStr for Website {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s.to_lowercase().as_str() {
|
||||
"true" | "1" | "yes" | "on" => Self::Enabled(true),
|
||||
"false" | "0" | "no" | "off" => Self::Enabled(false),
|
||||
_ => Self::Path(fix_user_path(s)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
keywords = ["bitcoin", "blockchain", "analytics", "on-chain"]
|
||||
categories = ["api-bindings", "cryptography::cryptocurrencies"]
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ brk_client = "0.1"
|
||||
use brk_client::{BrkClient, Index};
|
||||
|
||||
fn main() -> brk_client::Result<()> {
|
||||
let client = BrkClient::new("http://localhost:3000");
|
||||
let client = BrkClient::new("http://localhost:3110");
|
||||
|
||||
// Blockchain data (mempool.space compatible)
|
||||
let block = client.get_block_by_height(800000)?;
|
||||
@@ -47,11 +47,7 @@ fn main() -> brk_client::Result<()> {
|
||||
use brk_client::{BrkClient, BrkClientOptions};
|
||||
|
||||
let client = BrkClient::with_options(BrkClientOptions {
|
||||
base_url: "http://localhost:3000".to_string(),
|
||||
base_url: "http://localhost:3110".to_string(),
|
||||
timeout_secs: 60,
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -1585,6 +1585,40 @@ impl BitcoinPattern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct ClassAveragePricePattern<T> {
|
||||
pub _2015: MetricPattern4<T>,
|
||||
pub _2016: MetricPattern4<T>,
|
||||
pub _2017: MetricPattern4<T>,
|
||||
pub _2018: MetricPattern4<T>,
|
||||
pub _2019: MetricPattern4<T>,
|
||||
pub _2020: MetricPattern4<T>,
|
||||
pub _2021: MetricPattern4<T>,
|
||||
pub _2022: MetricPattern4<T>,
|
||||
pub _2023: MetricPattern4<T>,
|
||||
pub _2024: MetricPattern4<T>,
|
||||
pub _2025: MetricPattern4<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> ClassAveragePricePattern<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
_2015: MetricPattern4::new(client.clone(), _m(&acc, "2015_average_price")),
|
||||
_2016: MetricPattern4::new(client.clone(), _m(&acc, "2016_average_price")),
|
||||
_2017: MetricPattern4::new(client.clone(), _m(&acc, "2017_average_price")),
|
||||
_2018: MetricPattern4::new(client.clone(), _m(&acc, "2018_average_price")),
|
||||
_2019: MetricPattern4::new(client.clone(), _m(&acc, "2019_average_price")),
|
||||
_2020: MetricPattern4::new(client.clone(), _m(&acc, "2020_average_price")),
|
||||
_2021: MetricPattern4::new(client.clone(), _m(&acc, "2021_average_price")),
|
||||
_2022: MetricPattern4::new(client.clone(), _m(&acc, "2022_average_price")),
|
||||
_2023: MetricPattern4::new(client.clone(), _m(&acc, "2023_average_price")),
|
||||
_2024: MetricPattern4::new(client.clone(), _m(&acc, "2024_average_price")),
|
||||
_2025: MetricPattern4::new(client.clone(), _m(&acc, "2025_average_price")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct DollarsPattern<T> {
|
||||
pub average: MetricPattern2<T>,
|
||||
@@ -1620,35 +1654,33 @@ impl<T: DeserializeOwned> DollarsPattern<T> {
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct ClassAveragePricePattern<T> {
|
||||
pub _2015: MetricPattern4<T>,
|
||||
pub _2016: MetricPattern4<T>,
|
||||
pub _2017: MetricPattern4<T>,
|
||||
pub _2018: MetricPattern4<T>,
|
||||
pub _2019: MetricPattern4<T>,
|
||||
pub _2020: MetricPattern4<T>,
|
||||
pub _2021: MetricPattern4<T>,
|
||||
pub _2022: MetricPattern4<T>,
|
||||
pub _2023: MetricPattern4<T>,
|
||||
pub _2024: MetricPattern4<T>,
|
||||
pub _2025: MetricPattern4<T>,
|
||||
pub struct RelativePattern2 {
|
||||
pub neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
pub net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
pub supply_in_loss_rel_to_own_supply: MetricPattern1<StoredF64>,
|
||||
pub supply_in_profit_rel_to_own_supply: MetricPattern1<StoredF64>,
|
||||
pub unrealized_loss_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
pub unrealized_profit_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> ClassAveragePricePattern<T> {
|
||||
impl RelativePattern2 {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
_2015: MetricPattern4::new(client.clone(), _m(&acc, "2015_returns")),
|
||||
_2016: MetricPattern4::new(client.clone(), _m(&acc, "2016_returns")),
|
||||
_2017: MetricPattern4::new(client.clone(), _m(&acc, "2017_returns")),
|
||||
_2018: MetricPattern4::new(client.clone(), _m(&acc, "2018_returns")),
|
||||
_2019: MetricPattern4::new(client.clone(), _m(&acc, "2019_returns")),
|
||||
_2020: MetricPattern4::new(client.clone(), _m(&acc, "2020_returns")),
|
||||
_2021: MetricPattern4::new(client.clone(), _m(&acc, "2021_returns")),
|
||||
_2022: MetricPattern4::new(client.clone(), _m(&acc, "2022_returns")),
|
||||
_2023: MetricPattern4::new(client.clone(), _m(&acc, "2023_returns")),
|
||||
_2024: MetricPattern4::new(client.clone(), _m(&acc, "2024_returns")),
|
||||
_2025: MetricPattern4::new(client.clone(), _m(&acc, "2025_returns")),
|
||||
neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_market_cap")),
|
||||
neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_total_unrealized_pnl")),
|
||||
net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_market_cap")),
|
||||
net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_total_unrealized_pnl")),
|
||||
supply_in_loss_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_own_supply")),
|
||||
supply_in_profit_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_own_supply")),
|
||||
unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_market_cap")),
|
||||
unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_total_unrealized_pnl")),
|
||||
unrealized_profit_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_market_cap")),
|
||||
unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_total_unrealized_pnl")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1685,38 +1717,6 @@ impl RelativePattern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct RelativePattern2 {
|
||||
pub neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
pub net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
pub supply_in_loss_rel_to_own_supply: MetricPattern1<StoredF64>,
|
||||
pub supply_in_profit_rel_to_own_supply: MetricPattern1<StoredF64>,
|
||||
pub unrealized_loss_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
pub unrealized_profit_rel_to_own_market_cap: MetricPattern1<StoredF32>,
|
||||
pub unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1<StoredF32>,
|
||||
}
|
||||
|
||||
impl RelativePattern2 {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_market_cap")),
|
||||
neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_total_unrealized_pnl")),
|
||||
net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_market_cap")),
|
||||
net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_total_unrealized_pnl")),
|
||||
supply_in_loss_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_own_supply")),
|
||||
supply_in_profit_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_own_supply")),
|
||||
unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_market_cap")),
|
||||
unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_total_unrealized_pnl")),
|
||||
unrealized_profit_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_market_cap")),
|
||||
unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_total_unrealized_pnl")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CountPattern2<T> {
|
||||
pub average: MetricPattern1<T>,
|
||||
@@ -1779,36 +1779,6 @@ impl AddrCountPattern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct FeeRatePattern<T> {
|
||||
pub average: MetricPattern1<T>,
|
||||
pub max: MetricPattern1<T>,
|
||||
pub median: MetricPattern11<T>,
|
||||
pub min: MetricPattern1<T>,
|
||||
pub pct10: MetricPattern11<T>,
|
||||
pub pct25: MetricPattern11<T>,
|
||||
pub pct75: MetricPattern11<T>,
|
||||
pub pct90: MetricPattern11<T>,
|
||||
pub txindex: MetricPattern27<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> FeeRatePattern<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
average: MetricPattern1::new(client.clone(), _m(&acc, "average")),
|
||||
max: MetricPattern1::new(client.clone(), _m(&acc, "max")),
|
||||
median: MetricPattern11::new(client.clone(), _m(&acc, "median")),
|
||||
min: MetricPattern1::new(client.clone(), _m(&acc, "min")),
|
||||
pct10: MetricPattern11::new(client.clone(), _m(&acc, "pct10")),
|
||||
pct25: MetricPattern11::new(client.clone(), _m(&acc, "pct25")),
|
||||
pct75: MetricPattern11::new(client.clone(), _m(&acc, "pct75")),
|
||||
pct90: MetricPattern11::new(client.clone(), _m(&acc, "pct90")),
|
||||
txindex: MetricPattern27::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct FullnessPattern<T> {
|
||||
pub average: MetricPattern2<T>,
|
||||
@@ -1839,6 +1809,36 @@ impl<T: DeserializeOwned> FullnessPattern<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct FeeRatePattern<T> {
|
||||
pub average: MetricPattern1<T>,
|
||||
pub max: MetricPattern1<T>,
|
||||
pub median: MetricPattern11<T>,
|
||||
pub min: MetricPattern1<T>,
|
||||
pub pct10: MetricPattern11<T>,
|
||||
pub pct25: MetricPattern11<T>,
|
||||
pub pct75: MetricPattern11<T>,
|
||||
pub pct90: MetricPattern11<T>,
|
||||
pub txindex: MetricPattern27<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> FeeRatePattern<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
average: MetricPattern1::new(client.clone(), _m(&acc, "average")),
|
||||
max: MetricPattern1::new(client.clone(), _m(&acc, "max")),
|
||||
median: MetricPattern11::new(client.clone(), _m(&acc, "median")),
|
||||
min: MetricPattern1::new(client.clone(), _m(&acc, "min")),
|
||||
pct10: MetricPattern11::new(client.clone(), _m(&acc, "pct10")),
|
||||
pct25: MetricPattern11::new(client.clone(), _m(&acc, "pct25")),
|
||||
pct75: MetricPattern11::new(client.clone(), _m(&acc, "pct75")),
|
||||
pct90: MetricPattern11::new(client.clone(), _m(&acc, "pct90")),
|
||||
txindex: MetricPattern27::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _0satsPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
@@ -1868,79 +1868,27 @@ impl _0satsPattern {
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _100btcPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
pub cost_basis: CostBasisPattern,
|
||||
pub outputs: OutputsPattern,
|
||||
pub realized: RealizedPattern,
|
||||
pub relative: RelativePattern,
|
||||
pub supply: SupplyPattern2,
|
||||
pub unrealized: UnrealizedPattern,
|
||||
pub struct PeriodCagrPattern {
|
||||
pub _10y: MetricPattern4<StoredF32>,
|
||||
pub _2y: MetricPattern4<StoredF32>,
|
||||
pub _3y: MetricPattern4<StoredF32>,
|
||||
pub _4y: MetricPattern4<StoredF32>,
|
||||
pub _5y: MetricPattern4<StoredF32>,
|
||||
pub _6y: MetricPattern4<StoredF32>,
|
||||
pub _8y: MetricPattern4<StoredF32>,
|
||||
}
|
||||
|
||||
impl _100btcPattern {
|
||||
impl PeriodCagrPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
activity: ActivityPattern2::new(client.clone(), acc.clone()),
|
||||
cost_basis: CostBasisPattern::new(client.clone(), acc.clone()),
|
||||
outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")),
|
||||
realized: RealizedPattern::new(client.clone(), acc.clone()),
|
||||
relative: RelativePattern::new(client.clone(), acc.clone()),
|
||||
supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")),
|
||||
unrealized: UnrealizedPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _10yTo12yPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
pub cost_basis: CostBasisPattern2,
|
||||
pub outputs: OutputsPattern,
|
||||
pub realized: RealizedPattern2,
|
||||
pub relative: RelativePattern2,
|
||||
pub supply: SupplyPattern2,
|
||||
pub unrealized: UnrealizedPattern,
|
||||
}
|
||||
|
||||
impl _10yTo12yPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
activity: ActivityPattern2::new(client.clone(), acc.clone()),
|
||||
cost_basis: CostBasisPattern2::new(client.clone(), acc.clone()),
|
||||
outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")),
|
||||
realized: RealizedPattern2::new(client.clone(), acc.clone()),
|
||||
relative: RelativePattern2::new(client.clone(), acc.clone()),
|
||||
supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")),
|
||||
unrealized: UnrealizedPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _10yPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
pub cost_basis: CostBasisPattern,
|
||||
pub outputs: OutputsPattern,
|
||||
pub realized: RealizedPattern4,
|
||||
pub relative: RelativePattern,
|
||||
pub supply: SupplyPattern2,
|
||||
pub unrealized: UnrealizedPattern,
|
||||
}
|
||||
|
||||
impl _10yPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
activity: ActivityPattern2::new(client.clone(), acc.clone()),
|
||||
cost_basis: CostBasisPattern::new(client.clone(), acc.clone()),
|
||||
outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")),
|
||||
realized: RealizedPattern4::new(client.clone(), acc.clone()),
|
||||
relative: RelativePattern::new(client.clone(), acc.clone()),
|
||||
supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")),
|
||||
unrealized: UnrealizedPattern::new(client.clone(), acc.clone()),
|
||||
_10y: MetricPattern4::new(client.clone(), _p("10y", &acc)),
|
||||
_2y: MetricPattern4::new(client.clone(), _p("2y", &acc)),
|
||||
_3y: MetricPattern4::new(client.clone(), _p("3y", &acc)),
|
||||
_4y: MetricPattern4::new(client.clone(), _p("4y", &acc)),
|
||||
_5y: MetricPattern4::new(client.clone(), _p("5y", &acc)),
|
||||
_6y: MetricPattern4::new(client.clone(), _p("6y", &acc)),
|
||||
_8y: MetricPattern4::new(client.clone(), _p("8y", &acc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1998,27 +1946,79 @@ impl _0satsPattern2 {
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct PeriodCagrPattern {
|
||||
pub _10y: MetricPattern4<StoredF32>,
|
||||
pub _2y: MetricPattern4<StoredF32>,
|
||||
pub _3y: MetricPattern4<StoredF32>,
|
||||
pub _4y: MetricPattern4<StoredF32>,
|
||||
pub _5y: MetricPattern4<StoredF32>,
|
||||
pub _6y: MetricPattern4<StoredF32>,
|
||||
pub _8y: MetricPattern4<StoredF32>,
|
||||
pub struct _100btcPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
pub cost_basis: CostBasisPattern,
|
||||
pub outputs: OutputsPattern,
|
||||
pub realized: RealizedPattern,
|
||||
pub relative: RelativePattern,
|
||||
pub supply: SupplyPattern2,
|
||||
pub unrealized: UnrealizedPattern,
|
||||
}
|
||||
|
||||
impl PeriodCagrPattern {
|
||||
impl _100btcPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
_10y: MetricPattern4::new(client.clone(), _p("10y", &acc)),
|
||||
_2y: MetricPattern4::new(client.clone(), _p("2y", &acc)),
|
||||
_3y: MetricPattern4::new(client.clone(), _p("3y", &acc)),
|
||||
_4y: MetricPattern4::new(client.clone(), _p("4y", &acc)),
|
||||
_5y: MetricPattern4::new(client.clone(), _p("5y", &acc)),
|
||||
_6y: MetricPattern4::new(client.clone(), _p("6y", &acc)),
|
||||
_8y: MetricPattern4::new(client.clone(), _p("8y", &acc)),
|
||||
activity: ActivityPattern2::new(client.clone(), acc.clone()),
|
||||
cost_basis: CostBasisPattern::new(client.clone(), acc.clone()),
|
||||
outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")),
|
||||
realized: RealizedPattern::new(client.clone(), acc.clone()),
|
||||
relative: RelativePattern::new(client.clone(), acc.clone()),
|
||||
supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")),
|
||||
unrealized: UnrealizedPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _10yPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
pub cost_basis: CostBasisPattern,
|
||||
pub outputs: OutputsPattern,
|
||||
pub realized: RealizedPattern4,
|
||||
pub relative: RelativePattern,
|
||||
pub supply: SupplyPattern2,
|
||||
pub unrealized: UnrealizedPattern,
|
||||
}
|
||||
|
||||
impl _10yPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
activity: ActivityPattern2::new(client.clone(), acc.clone()),
|
||||
cost_basis: CostBasisPattern::new(client.clone(), acc.clone()),
|
||||
outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")),
|
||||
realized: RealizedPattern4::new(client.clone(), acc.clone()),
|
||||
relative: RelativePattern::new(client.clone(), acc.clone()),
|
||||
supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")),
|
||||
unrealized: UnrealizedPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _10yTo12yPattern {
|
||||
pub activity: ActivityPattern2,
|
||||
pub cost_basis: CostBasisPattern2,
|
||||
pub outputs: OutputsPattern,
|
||||
pub realized: RealizedPattern2,
|
||||
pub relative: RelativePattern2,
|
||||
pub supply: SupplyPattern2,
|
||||
pub unrealized: UnrealizedPattern,
|
||||
}
|
||||
|
||||
impl _10yTo12yPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
activity: ActivityPattern2::new(client.clone(), acc.clone()),
|
||||
cost_basis: CostBasisPattern2::new(client.clone(), acc.clone()),
|
||||
outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")),
|
||||
realized: RealizedPattern2::new(client.clone(), acc.clone()),
|
||||
relative: RelativePattern2::new(client.clone(), acc.clone()),
|
||||
supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")),
|
||||
unrealized: UnrealizedPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2066,91 +2066,19 @@ impl<T: DeserializeOwned> SplitPattern2<T> {
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CostBasisPattern2 {
|
||||
pub max: MetricPattern1<Dollars>,
|
||||
pub min: MetricPattern1<Dollars>,
|
||||
pub percentiles: PercentilesPattern,
|
||||
pub struct ActiveSupplyPattern {
|
||||
pub bitcoin: MetricPattern1<Bitcoin>,
|
||||
pub dollars: MetricPattern1<Dollars>,
|
||||
pub sats: MetricPattern1<Sats>,
|
||||
}
|
||||
|
||||
impl CostBasisPattern2 {
|
||||
impl ActiveSupplyPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")),
|
||||
min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")),
|
||||
percentiles: PercentilesPattern::new(client.clone(), _m(&acc, "cost_basis")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct SegwitAdoptionPattern {
|
||||
pub base: MetricPattern11<StoredF32>,
|
||||
pub cumulative: MetricPattern2<StoredF32>,
|
||||
pub sum: MetricPattern2<StoredF32>,
|
||||
}
|
||||
|
||||
impl SegwitAdoptionPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
base: MetricPattern11::new(client.clone(), acc.clone()),
|
||||
cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")),
|
||||
sum: MetricPattern2::new(client.clone(), _m(&acc, "sum")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct UnclaimedRewardsPattern {
|
||||
pub bitcoin: BitcoinPattern2<Bitcoin>,
|
||||
pub dollars: BlockCountPattern<Dollars>,
|
||||
pub sats: BlockCountPattern<Sats>,
|
||||
}
|
||||
|
||||
impl UnclaimedRewardsPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
bitcoin: BitcoinPattern2::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: BlockCountPattern::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: BlockCountPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CoinbasePattern {
|
||||
pub bitcoin: BitcoinPattern,
|
||||
pub dollars: DollarsPattern<Dollars>,
|
||||
pub sats: DollarsPattern<Sats>,
|
||||
}
|
||||
|
||||
impl CoinbasePattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
bitcoin: BitcoinPattern::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: DollarsPattern::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: DollarsPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _2015Pattern {
|
||||
pub bitcoin: MetricPattern4<Bitcoin>,
|
||||
pub dollars: MetricPattern4<Dollars>,
|
||||
pub sats: MetricPattern4<Sats>,
|
||||
}
|
||||
|
||||
impl _2015Pattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
bitcoin: MetricPattern4::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: MetricPattern4::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: MetricPattern4::new(client.clone(), acc.clone()),
|
||||
bitcoin: MetricPattern1::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: MetricPattern1::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: MetricPattern1::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2174,35 +2102,107 @@ impl CoinbasePattern2 {
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct ActiveSupplyPattern {
|
||||
pub bitcoin: MetricPattern1<Bitcoin>,
|
||||
pub dollars: MetricPattern1<Dollars>,
|
||||
pub sats: MetricPattern1<Sats>,
|
||||
pub struct _2015Pattern {
|
||||
pub bitcoin: MetricPattern4<Bitcoin>,
|
||||
pub dollars: MetricPattern4<Dollars>,
|
||||
pub sats: MetricPattern4<Sats>,
|
||||
}
|
||||
|
||||
impl ActiveSupplyPattern {
|
||||
impl _2015Pattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
bitcoin: MetricPattern1::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: MetricPattern1::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: MetricPattern1::new(client.clone(), acc.clone()),
|
||||
bitcoin: MetricPattern4::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: MetricPattern4::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: MetricPattern4::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _1dReturns1mSdPattern {
|
||||
pub sd: MetricPattern4<StoredF32>,
|
||||
pub sma: MetricPattern4<StoredF32>,
|
||||
pub struct CoinbasePattern {
|
||||
pub bitcoin: BitcoinPattern,
|
||||
pub dollars: DollarsPattern<Dollars>,
|
||||
pub sats: DollarsPattern<Sats>,
|
||||
}
|
||||
|
||||
impl _1dReturns1mSdPattern {
|
||||
impl CoinbasePattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
sd: MetricPattern4::new(client.clone(), _m(&acc, "sd")),
|
||||
sma: MetricPattern4::new(client.clone(), _m(&acc, "sma")),
|
||||
bitcoin: BitcoinPattern::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: DollarsPattern::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: DollarsPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct UnclaimedRewardsPattern {
|
||||
pub bitcoin: BitcoinPattern2<Bitcoin>,
|
||||
pub dollars: BlockCountPattern<Dollars>,
|
||||
pub sats: BlockCountPattern<Sats>,
|
||||
}
|
||||
|
||||
impl UnclaimedRewardsPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
bitcoin: BitcoinPattern2::new(client.clone(), _m(&acc, "btc")),
|
||||
dollars: BlockCountPattern::new(client.clone(), _m(&acc, "usd")),
|
||||
sats: BlockCountPattern::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct SegwitAdoptionPattern {
|
||||
pub base: MetricPattern11<StoredF32>,
|
||||
pub cumulative: MetricPattern2<StoredF32>,
|
||||
pub sum: MetricPattern2<StoredF32>,
|
||||
}
|
||||
|
||||
impl SegwitAdoptionPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
base: MetricPattern11::new(client.clone(), acc.clone()),
|
||||
cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")),
|
||||
sum: MetricPattern2::new(client.clone(), _m(&acc, "sum")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CostBasisPattern2 {
|
||||
pub max: MetricPattern1<Dollars>,
|
||||
pub min: MetricPattern1<Dollars>,
|
||||
pub percentiles: PercentilesPattern,
|
||||
}
|
||||
|
||||
impl CostBasisPattern2 {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")),
|
||||
min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")),
|
||||
percentiles: PercentilesPattern::new(client.clone(), _m(&acc, "cost_basis")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CostBasisPattern {
|
||||
pub max: MetricPattern1<Dollars>,
|
||||
pub min: MetricPattern1<Dollars>,
|
||||
}
|
||||
|
||||
impl CostBasisPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")),
|
||||
min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2240,49 +2240,17 @@ impl RelativePattern4 {
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CostBasisPattern {
|
||||
pub max: MetricPattern1<Dollars>,
|
||||
pub min: MetricPattern1<Dollars>,
|
||||
pub struct _1dReturns1mSdPattern {
|
||||
pub sd: MetricPattern4<StoredF32>,
|
||||
pub sma: MetricPattern4<StoredF32>,
|
||||
}
|
||||
|
||||
impl CostBasisPattern {
|
||||
impl _1dReturns1mSdPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")),
|
||||
min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct SatsPattern<T> {
|
||||
pub ohlc: MetricPattern1<T>,
|
||||
pub split: SplitPattern2<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> SatsPattern<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
ohlc: MetricPattern1::new(client.clone(), _m(&acc, "ohlc")),
|
||||
split: SplitPattern2::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct BitcoinPattern2<T> {
|
||||
pub cumulative: MetricPattern2<T>,
|
||||
pub sum: MetricPattern1<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> BitcoinPattern2<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")),
|
||||
sum: MetricPattern1::new(client.clone(), acc.clone()),
|
||||
sd: MetricPattern4::new(client.clone(), _m(&acc, "sd")),
|
||||
sma: MetricPattern4::new(client.clone(), _m(&acc, "sma")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2303,6 +2271,38 @@ impl<T: DeserializeOwned> BlockCountPattern<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct SatsPattern<T> {
|
||||
pub ohlc: MetricPattern1<T>,
|
||||
pub split: SplitPattern2<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> SatsPattern<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
ohlc: MetricPattern1::new(client.clone(), _m(&acc, "ohlc_sats")),
|
||||
split: SplitPattern2::new(client.clone(), _m(&acc, "sats")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct BitcoinPattern2<T> {
|
||||
pub cumulative: MetricPattern2<T>,
|
||||
pub sum: MetricPattern1<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> BitcoinPattern2<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")),
|
||||
sum: MetricPattern1::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct OutputsPattern {
|
||||
pub utxo_count: MetricPattern1<StoredU64>,
|
||||
@@ -9300,8 +9300,8 @@ impl MetricsTree_Market_Ath {
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Market_Dca {
|
||||
pub class_average_price: MetricsTree_Market_Dca_ClassAveragePrice,
|
||||
pub class_returns: ClassAveragePricePattern<StoredF32>,
|
||||
pub class_average_price: ClassAveragePricePattern<Dollars>,
|
||||
pub class_returns: MetricsTree_Market_Dca_ClassReturns,
|
||||
pub class_stack: MetricsTree_Market_Dca_ClassStack,
|
||||
pub period_average_price: PeriodAveragePricePattern<Dollars>,
|
||||
pub period_cagr: PeriodCagrPattern,
|
||||
@@ -9313,8 +9313,8 @@ pub struct MetricsTree_Market_Dca {
|
||||
impl MetricsTree_Market_Dca {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
class_average_price: MetricsTree_Market_Dca_ClassAveragePrice::new(client.clone(), format!("{base_path}_class_average_price")),
|
||||
class_returns: ClassAveragePricePattern::new(client.clone(), "dca_class".to_string()),
|
||||
class_average_price: ClassAveragePricePattern::new(client.clone(), "dca_class".to_string()),
|
||||
class_returns: MetricsTree_Market_Dca_ClassReturns::new(client.clone(), format!("{base_path}_class_returns")),
|
||||
class_stack: MetricsTree_Market_Dca_ClassStack::new(client.clone(), format!("{base_path}_class_stack")),
|
||||
period_average_price: PeriodAveragePricePattern::new(client.clone(), "dca_average_price".to_string()),
|
||||
period_cagr: PeriodCagrPattern::new(client.clone(), "dca_cagr".to_string()),
|
||||
@@ -9326,34 +9326,34 @@ impl MetricsTree_Market_Dca {
|
||||
}
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Market_Dca_ClassAveragePrice {
|
||||
pub _2015: MetricPattern4<Dollars>,
|
||||
pub _2016: MetricPattern4<Dollars>,
|
||||
pub _2017: MetricPattern4<Dollars>,
|
||||
pub _2018: MetricPattern4<Dollars>,
|
||||
pub _2019: MetricPattern4<Dollars>,
|
||||
pub _2020: MetricPattern4<Dollars>,
|
||||
pub _2021: MetricPattern4<Dollars>,
|
||||
pub _2022: MetricPattern4<Dollars>,
|
||||
pub _2023: MetricPattern4<Dollars>,
|
||||
pub _2024: MetricPattern4<Dollars>,
|
||||
pub _2025: MetricPattern4<Dollars>,
|
||||
pub struct MetricsTree_Market_Dca_ClassReturns {
|
||||
pub _2015: MetricPattern4<StoredF32>,
|
||||
pub _2016: MetricPattern4<StoredF32>,
|
||||
pub _2017: MetricPattern4<StoredF32>,
|
||||
pub _2018: MetricPattern4<StoredF32>,
|
||||
pub _2019: MetricPattern4<StoredF32>,
|
||||
pub _2020: MetricPattern4<StoredF32>,
|
||||
pub _2021: MetricPattern4<StoredF32>,
|
||||
pub _2022: MetricPattern4<StoredF32>,
|
||||
pub _2023: MetricPattern4<StoredF32>,
|
||||
pub _2024: MetricPattern4<StoredF32>,
|
||||
pub _2025: MetricPattern4<StoredF32>,
|
||||
}
|
||||
|
||||
impl MetricsTree_Market_Dca_ClassAveragePrice {
|
||||
impl MetricsTree_Market_Dca_ClassReturns {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
_2015: MetricPattern4::new(client.clone(), "dca_class_2015_average_price".to_string()),
|
||||
_2016: MetricPattern4::new(client.clone(), "dca_class_2016_average_price".to_string()),
|
||||
_2017: MetricPattern4::new(client.clone(), "dca_class_2017_average_price".to_string()),
|
||||
_2018: MetricPattern4::new(client.clone(), "dca_class_2018_average_price".to_string()),
|
||||
_2019: MetricPattern4::new(client.clone(), "dca_class_2019_average_price".to_string()),
|
||||
_2020: MetricPattern4::new(client.clone(), "dca_class_2020_average_price".to_string()),
|
||||
_2021: MetricPattern4::new(client.clone(), "dca_class_2021_average_price".to_string()),
|
||||
_2022: MetricPattern4::new(client.clone(), "dca_class_2022_average_price".to_string()),
|
||||
_2023: MetricPattern4::new(client.clone(), "dca_class_2023_average_price".to_string()),
|
||||
_2024: MetricPattern4::new(client.clone(), "dca_class_2024_average_price".to_string()),
|
||||
_2025: MetricPattern4::new(client.clone(), "dca_class_2025_average_price".to_string()),
|
||||
_2015: MetricPattern4::new(client.clone(), "dca_class_2015_returns".to_string()),
|
||||
_2016: MetricPattern4::new(client.clone(), "dca_class_2016_returns".to_string()),
|
||||
_2017: MetricPattern4::new(client.clone(), "dca_class_2017_returns".to_string()),
|
||||
_2018: MetricPattern4::new(client.clone(), "dca_class_2018_returns".to_string()),
|
||||
_2019: MetricPattern4::new(client.clone(), "dca_class_2019_returns".to_string()),
|
||||
_2020: MetricPattern4::new(client.clone(), "dca_class_2020_returns".to_string()),
|
||||
_2021: MetricPattern4::new(client.clone(), "dca_class_2021_returns".to_string()),
|
||||
_2022: MetricPattern4::new(client.clone(), "dca_class_2022_returns".to_string()),
|
||||
_2023: MetricPattern4::new(client.clone(), "dca_class_2023_returns".to_string()),
|
||||
_2024: MetricPattern4::new(client.clone(), "dca_class_2024_returns".to_string()),
|
||||
_2025: MetricPattern4::new(client.clone(), "dca_class_2025_returns".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11757,7 +11757,7 @@ impl MetricsTree_Price_Oracle {
|
||||
Self {
|
||||
ohlc_cents: MetricPattern6::new(client.clone(), "oracle_ohlc_cents".to_string()),
|
||||
ohlc_dollars: MetricPattern6::new(client.clone(), "oracle_ohlc".to_string()),
|
||||
price_cents: MetricPattern11::new(client.clone(), "orange_price_cents".to_string()),
|
||||
price_cents: MetricPattern11::new(client.clone(), "oracle_price_cents".to_string()),
|
||||
tx_count: MetricPattern6::new(client.clone(), "oracle_tx_count".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_error = { workspace = true }
|
||||
|
||||
@@ -4,13 +4,13 @@ UTXO and address cohort filtering for on-chain analytics.
|
||||
|
||||
## What It Enables
|
||||
|
||||
Slice the UTXO set and address population by age, amount, output type, halving epoch, or holder classification (STH/LTH). Build complex cohorts by combining filters for metrics like "realized cap of 1+ BTC UTXOs older than 155 days."
|
||||
Slice the UTXO set and address population by age, amount, output type, halving epoch, or holder classification (STH/LTH). Build complex cohorts by combining filters for metrics like "realized cap of 1+ BTC UTXOs older than 150 days."
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Age-based**: `TimeFilter::GreaterOrEqual(155)`, `TimeFilter::Range(30..90)`, `TimeFilter::LowerThan(7)`
|
||||
- **Age-based**: `TimeFilter::GreaterOrEqual(hours)`, `TimeFilter::Range(hours..hours)`, `TimeFilter::LowerThan(hours)`
|
||||
- **Amount-based**: `AmountFilter::GreaterOrEqual(Sats::_1BTC)`, `AmountFilter::Range(Sats::_100K..Sats::_1M)`
|
||||
- **Term classification**: `Term::Sth` (short-term holders, <155 days), `Term::Lth` (long-term holders)
|
||||
- **Term classification**: `Term::Sth` (short-term holders, <150 days), `Term::Lth` (long-term holders)
|
||||
- **Epoch filters**: Group by halving epoch
|
||||
- **Type filters**: Segment by output type (P2PKH, P2TR, etc.)
|
||||
- **Context-aware naming**: Automatic prefix generation (`utxos_`, `addrs_`) based on cohort context
|
||||
@@ -25,6 +25,7 @@ pub enum Filter {
|
||||
Time(TimeFilter), // Age-based
|
||||
Amount(AmountFilter), // Value-based
|
||||
Epoch(HalvingEpoch), // Halving epoch
|
||||
Year(Year), // Calendar year
|
||||
Type(OutputType), // P2PKH, P2TR, etc.
|
||||
}
|
||||
```
|
||||
@@ -32,15 +33,16 @@ pub enum Filter {
|
||||
## Core API
|
||||
|
||||
```rust,ignore
|
||||
let filter = Filter::Time(TimeFilter::GreaterOrEqual(155));
|
||||
// TimeFilter values are in hours (e.g., 3600 hours = 150 days)
|
||||
let filter = Filter::Time(TimeFilter::GreaterOrEqual(3600));
|
||||
|
||||
// Check membership
|
||||
filter.contains_time(200); // true
|
||||
filter.contains_time(4000); // true (4000 hours > 3600 hours)
|
||||
filter.contains_amount(sats);
|
||||
|
||||
// Generate metric names (via CohortContext)
|
||||
let ctx = CohortContext::Utxo;
|
||||
ctx.full_name(&filter, "min_age_155d"); // "utxos_min_age_155d"
|
||||
ctx.full_name(&filter, "min_age_150d"); // "utxos_min_age_150d"
|
||||
```
|
||||
|
||||
## Built On
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
|
||||
@@ -19,14 +19,14 @@ Compute 1000+ on-chain metrics from indexed blockchain data: supply breakdowns,
|
||||
## Core API
|
||||
|
||||
```rust,ignore
|
||||
let mut computer = Computer::forced_import(&outputs_path, &indexer, fetcher)?;
|
||||
let mut computer = Computer::forced_import(&outputs_path, &indexer, Some(fetcher))?;
|
||||
|
||||
// Compute all metrics for new blocks
|
||||
computer.compute(&indexer, starting_indexes, &reader, &exit)?;
|
||||
|
||||
// Access computed data
|
||||
let supply = computer.distribution.utxo_cohorts.all.metrics.supply.height_to_supply.get(height)?;
|
||||
let realized_cap = computer.distribution.utxo_cohorts.all.metrics.realized.height_to_realized_cap.get(height)?;
|
||||
// Access computed data via traversable vecs
|
||||
let supply = computer.distribution.utxo_cohorts.all.metrics.supply.total.sats.height;
|
||||
let realized_cap = computer.distribution.utxo_cohorts.all.metrics.realized.unwrap().realized_cap.height;
|
||||
```
|
||||
|
||||
## Metric Categories
|
||||
@@ -45,9 +45,9 @@ let realized_cap = computer.distribution.utxo_cohorts.all.metrics.realized.heigh
|
||||
## Cohort System
|
||||
|
||||
UTXO and address cohorts support filtering by:
|
||||
- **Age**: STH (<150d), LTH (≥150d), age bands (1d, 1w, 1m, 3m, 6m, 1y, 2y, ...)
|
||||
- **Age**: STH (<150d), LTH (≥150d), 21 age bands (<1h, 1h-1d, 1d-1w, 1w-1m, 1m-2m, ..., 6m-1y, 1y-2y, ..., 12y-15y, 15y+)
|
||||
- **Amount**: 0-0.001 BTC, 0.001-0.01, ..., 10k+ BTC
|
||||
- **Type**: P2PKH, P2SH, P2WPKH, P2WSH, P2TR
|
||||
- **Type**: P2PK, P2PKH, P2MS, P2SH, P2WPKH, P2WSH, P2TR, P2A
|
||||
- **Epoch**: By halving epoch
|
||||
|
||||
## Performance
|
||||
@@ -58,9 +58,10 @@ Full pipeline benchmarks (indexer + computer):
|
||||
|
||||
| Machine | Time | Disk | Peak Disk | Memory | Peak Memory |
|
||||
|---------|------|------|-----------|--------|-------------|
|
||||
| MBP M3 Pro (36GB, internal SSD) | 5.2h | 341 GB | 415 GB | 6.4 GB | 12 GB |
|
||||
| MBP M3 Pro (36GB, internal SSD) | 4.4h | 345 GB | 348 GB | 3.3 GB | 11 GB |
|
||||
| Mac Mini M4 (16GB, external SSD) | 7h | 344 GB | 346 GB | 4 GB | 10 GB |
|
||||
|
||||
Full benchmark data: [`https://github.com/bitcoinresearchkit/benches/tree/main/brk`](/benches/brk)
|
||||
Full benchmark data: [bitcoinresearchkit/benches](https://github.com/bitcoinresearchkit/benches/tree/main/brk)
|
||||
|
||||
## Recommended: mimalloc v3
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -187,12 +187,14 @@ impl Vecs {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check witness size (SegWit era only, activated Aug 2017)
|
||||
// Check witness size per input (SegWit era only, activated Aug 2017)
|
||||
// Pre-SegWit transactions have no witness data
|
||||
// Python checks each input's witness ≤ 500 bytes; we approximate with average
|
||||
if cached_year >= 2017 {
|
||||
let base_size: StoredU32 = txindex_to_base_size_iter.get_at_unwrap(txindex);
|
||||
let total_size: StoredU32 = txindex_to_total_size_iter.get_at_unwrap(txindex);
|
||||
if *total_size - *base_size > 500 {
|
||||
let witness_size = *total_size - *base_size;
|
||||
if witness_size / *input_count as u32 > 500 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -379,10 +381,9 @@ impl Vecs {
|
||||
}
|
||||
};
|
||||
|
||||
self.ohlc_cents
|
||||
.truncate_push_at(dateindex.to_usize(), ohlc)?;
|
||||
self.ohlc_cents.truncate_push(dateindex, ohlc)?;
|
||||
self.tx_count
|
||||
.truncate_push_at(dateindex.to_usize(), StoredU32::from(tx_count))?;
|
||||
.truncate_push(dateindex, StoredU32::from(tx_count))?;
|
||||
}
|
||||
|
||||
// Write daily data
|
||||
|
||||
@@ -48,11 +48,12 @@ impl OracleConfig {
|
||||
blocks_per_window: 144, // ~1 day
|
||||
min_tx_count: 1000,
|
||||
},
|
||||
// 2017+: Modern era ($1,000 - $1,000,000+)
|
||||
// 2017+: Modern era ($10,000 - $500,000)
|
||||
// Matches Python's slide range of -141 to 201
|
||||
_ => Self {
|
||||
min_price_cents: 100_000, // $1,000
|
||||
max_price_cents: 100_000_000, // $1,000,000
|
||||
blocks_per_window: 144, // ~1 day
|
||||
min_price_cents: 1_000_000, // $10,000 (gives max_slide = 200)
|
||||
max_price_cents: 50_000_000, // $500,000 (gives min_slide ≈ -140)
|
||||
blocks_per_window: 144, // ~1 day
|
||||
min_tx_count: 2000,
|
||||
},
|
||||
}
|
||||
@@ -90,9 +91,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_config_for_year() {
|
||||
// 2017+ config matches Python: $10,000 to $500,000
|
||||
let c2020 = OracleConfig::for_year(2020);
|
||||
assert_eq!(c2020.min_price_cents, 100_000);
|
||||
assert_eq!(c2020.max_price_cents, 100_000_000);
|
||||
assert_eq!(c2020.min_price_cents, 1_000_000);
|
||||
assert_eq!(c2020.max_price_cents, 50_000_000);
|
||||
|
||||
let c2015 = OracleConfig::for_year(2015);
|
||||
assert_eq!(c2015.min_price_cents, 10_000);
|
||||
@@ -101,13 +103,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_slide_range() {
|
||||
// 2024 config: $1,000 to $1,000,000
|
||||
// 2024 config: $10,000 to $500,000 (matches Python's -141 to 201)
|
||||
let config = OracleConfig::for_year(2024);
|
||||
let (min, max) = config.slide_range();
|
||||
// $1,000,000 = 10^8 cents → slide = (7-8)*200 = -200
|
||||
// $1,000 = 10^5 cents → slide = (7-5)*200 = 400
|
||||
assert_eq!(min, -200);
|
||||
assert_eq!(max, 400);
|
||||
// $500,000 = 5*10^7 cents → slide = (7-7.699)*200 ≈ -140
|
||||
// $10,000 = 10^6 cents → slide = (7-6)*200 = 200
|
||||
assert!((-141..=-139).contains(&min)); // ~-140
|
||||
assert_eq!(max, 200);
|
||||
|
||||
// 2015 config: $100 to $20,000
|
||||
let config = OracleConfig::for_year(2015);
|
||||
|
||||
@@ -11,10 +11,12 @@ pub const MAX_LOG_BTC: f64 = 2.0; // 10^2 BTC = 100 BTC
|
||||
pub const NUM_DECADES: usize = 8; // -6 to +2
|
||||
pub const TOTAL_BINS: usize = NUM_DECADES * BINS_PER_DECADE; // 1600 bins
|
||||
|
||||
/// Minimum output value to consider (10,000 sats = 0.0001 BTC)
|
||||
pub const MIN_OUTPUT_SATS: Sats = Sats::_10K;
|
||||
/// Maximum output value to consider (10 BTC)
|
||||
pub const MAX_OUTPUT_SATS: Sats = Sats::_10BTC;
|
||||
/// Minimum output value to consider (~1,000 sats = 0.00001 BTC)
|
||||
/// Matches Python: zeros bins 0-200 which is 10^-5 BTC
|
||||
pub const MIN_OUTPUT_SATS: Sats = Sats::_1K;
|
||||
/// Maximum output value to consider (100 BTC)
|
||||
/// Matches Python: zeros bins 1601+ which is ~10^2 BTC
|
||||
pub const MAX_OUTPUT_SATS: Sats = Sats::_100BTC;
|
||||
|
||||
/// Round BTC bin indices that should be smoothed to avoid false positives
|
||||
/// These are bins where round BTC amounts would naturally cluster
|
||||
|
||||
@@ -5,8 +5,11 @@ use vecdb::{BytesVec, Database, ImportableVec, IterableCloneableVec, LazyVecFrom
|
||||
use super::Vecs;
|
||||
|
||||
impl Vecs {
|
||||
pub fn forced_import(db: &Database, version: Version) -> Result<Self> {
|
||||
let price_cents = PcoVec::forced_import(db, "orange_price_cents", version)?;
|
||||
pub fn forced_import(db: &Database, parent_version: Version) -> Result<Self> {
|
||||
// v2: Fixed spike stencil positions and Gaussian center to match Python's empirical data
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
let price_cents = PcoVec::forced_import(db, "oracle_price_cents", version)?;
|
||||
let ohlc_cents = BytesVec::forced_import(db, "oracle_ohlc_cents", version)?;
|
||||
let tx_count = PcoVec::forced_import(db, "oracle_tx_count", version)?;
|
||||
|
||||
|
||||
@@ -12,68 +12,66 @@ use super::histogram::{BINS_PER_DECADE, Histogram, TOTAL_BINS};
|
||||
/// Number of parallel chunks for stencil sliding
|
||||
const PARALLEL_CHUNKS: i32 = 4;
|
||||
|
||||
/// USD spike stencil entries: (bin offset from $100 center, weight)
|
||||
/// USD spike stencil entries: (bin offset from center_bin, weight)
|
||||
/// These represent the expected frequency of round USD amounts in transactions
|
||||
/// Offset formula: log10(USD/100) * 200 bins/decade
|
||||
/// Companion spikes at ±2 bins from main spike (Rust 200 bins/decade ≈ Python's ±1 at 180 bins/decade)
|
||||
/// Matches Python's 29 entries from utxo_oracle.py lines 1013-1041
|
||||
/// Positions derived from Python's empirical data (utxo_oracle.py lines 1013-1041)
|
||||
/// Offset = python_stencil_index - 402 (since Python stencil starts at bin 199, center is 601)
|
||||
const SPIKE_STENCIL: &[(i32, f64)] = &[
|
||||
// $1 (single)
|
||||
(-400, 0.00130),
|
||||
// $5 (single)
|
||||
(-260, 0.00168),
|
||||
// $10 (main + companion)
|
||||
(-200, 0.00347),
|
||||
(-198, 0.00199),
|
||||
// $15 (single)
|
||||
(-165, 0.00191),
|
||||
// $20 (main + companion)
|
||||
(-140, 0.00334),
|
||||
(-138, 0.00259),
|
||||
// $30 (main + companion)
|
||||
(-105, 0.00258),
|
||||
(-103, 0.00273),
|
||||
// $50 (main + 2 companions)
|
||||
// $1 (single) - Python index 40
|
||||
(-362, 0.00130),
|
||||
// $5 (single) - Python index 141
|
||||
(-261, 0.00168),
|
||||
// $10 (main + companion) - Python indices 201-202
|
||||
(-201, 0.00347),
|
||||
(-200, 0.00199),
|
||||
// $15 (single) - Python index 236
|
||||
(-166, 0.00191),
|
||||
// $20 (main + companion) - Python indices 261-262
|
||||
(-141, 0.00334),
|
||||
(-140, 0.00259),
|
||||
// $30 (main + companion) - Python indices 296-297
|
||||
(-106, 0.00258),
|
||||
(-105, 0.00273),
|
||||
// $50 (main + 2 companions) - Python indices 340-342
|
||||
(-62, 0.00308),
|
||||
(-60, 0.00561),
|
||||
(-58, 0.00309),
|
||||
// $100 (main + 3 companions) - center
|
||||
(-61, 0.00561),
|
||||
(-60, 0.00309),
|
||||
// $100 (main + 3 companions) - Python indices 400-403
|
||||
(-2, 0.00292),
|
||||
(0, 0.00617),
|
||||
(2, 0.00442),
|
||||
(4, 0.00263),
|
||||
// $150 (single)
|
||||
(35, 0.00286),
|
||||
// $200 (main + companion)
|
||||
(60, 0.00410),
|
||||
(62, 0.00335),
|
||||
// $300 (main + companion)
|
||||
(95, 0.00252),
|
||||
(97, 0.00278),
|
||||
// $500 (single)
|
||||
(140, 0.00379),
|
||||
// $1000 (main + companion)
|
||||
(200, 0.00369),
|
||||
(202, 0.00239),
|
||||
// $1500 (single)
|
||||
(235, 0.00128),
|
||||
// $2000 (main + companion)
|
||||
(260, 0.00165),
|
||||
(262, 0.00140),
|
||||
// $5000 (single)
|
||||
(340, 0.00115),
|
||||
// $10000 (single)
|
||||
(400, 0.00083),
|
||||
(-1, 0.00617),
|
||||
(0, 0.00442),
|
||||
(1, 0.00263),
|
||||
// $150 (single) - Python index 436
|
||||
(34, 0.00286),
|
||||
// $200 (main + companion) - Python indices 461-462
|
||||
(59, 0.00410),
|
||||
(60, 0.00335),
|
||||
// $300 (main + companion) - Python indices 496-497
|
||||
(94, 0.00252),
|
||||
(95, 0.00278),
|
||||
// $500 (single) - Python index 541
|
||||
(139, 0.00379),
|
||||
// $1000 (main + companion) - Python indices 601-602
|
||||
(199, 0.00369),
|
||||
(200, 0.00239),
|
||||
// $1500 (single) - Python index 636
|
||||
(234, 0.00128),
|
||||
// $2000 (main + companion) - Python indices 661-662
|
||||
(259, 0.00165),
|
||||
(260, 0.00140),
|
||||
// $5000 (single) - Python index 741
|
||||
(339, 0.00115),
|
||||
// $10000 (single) - Python index 801
|
||||
(399, 0.00083),
|
||||
];
|
||||
|
||||
/// Width of the smooth stencil in bins (Gaussian sigma)
|
||||
/// Python uses std_dev=201 with 803 bins. Our histogram has 1600 bins (2x),
|
||||
/// so we use 201 * (1600/803) ≈ 400 bins sigma equivalent
|
||||
const SMOOTH_WIDTH: f64 = 400.0;
|
||||
/// Both Python and Rust use 200 bins per decade, so sigma is the same
|
||||
const SMOOTH_WIDTH: f64 = 201.0;
|
||||
|
||||
/// Linear term coefficient for smooth stencil (per Python: 0.0000005 * x)
|
||||
/// Scaled for our larger histogram: 0.0000005 * (803/1600) ≈ 0.00000025
|
||||
const SMOOTH_LINEAR_COEF: f64 = 0.00000025;
|
||||
/// NOT scaled - the linear term uses window position (0-802), same as Python
|
||||
const SMOOTH_LINEAR_COEF: f64 = 0.0000005;
|
||||
|
||||
/// Weight given to smooth stencil vs spike stencil
|
||||
const SMOOTH_WEIGHT: f64 = 0.65;
|
||||
@@ -84,6 +82,12 @@ const SPIKE_WEIGHT: f64 = 1.0;
|
||||
/// This avoids computing exp() billions of times
|
||||
const SMOOTH_RANGE: usize = 800;
|
||||
|
||||
/// Gaussian center bin offset from spike center
|
||||
/// Python's Gaussian has mean=411 in 803-element stencil
|
||||
/// Stencil starts at bin 199, so Gaussian centers at bin 199+411=610
|
||||
/// Spike center is at bin 601, so Gaussian is offset by +9 bins
|
||||
const GAUSSIAN_CENTER_OFFSET: i32 = 9;
|
||||
|
||||
/// Lazily initialized Gaussian weight lookup table
|
||||
fn gaussian_weights() -> &'static [f64; SMOOTH_RANGE + 1] {
|
||||
use std::sync::OnceLock;
|
||||
@@ -110,16 +114,6 @@ fn gaussian_weights() -> &'static [f64; SMOOTH_RANGE + 1] {
|
||||
pub fn find_best_price(histogram: &Histogram, min_slide: i32, max_slide: i32) -> Option<Cents> {
|
||||
let bins = histogram.bins();
|
||||
|
||||
// Pre-compute the linear term sum (constant for all slide positions)
|
||||
// linear_sum = Σ bins[i] * SMOOTH_LINEAR_COEF * i
|
||||
let linear_sum: f64 = bins
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.filter(|(_, v)| *v > 0.0)
|
||||
.map(|(i, v)| v * SMOOTH_LINEAR_COEF * i as f64)
|
||||
.sum();
|
||||
|
||||
// Collect non-zero bins: Vec for Gaussian (needs iteration), HashMap for spike (needs lookup)
|
||||
let non_zero_bins: Vec<(usize, f64)> = bins
|
||||
.iter()
|
||||
@@ -147,7 +141,7 @@ pub fn find_best_price(histogram: &Histogram, min_slide: i32, max_slide: i32) ->
|
||||
let mut local_total = 0.0;
|
||||
|
||||
for slide in chunk_start..=chunk_end {
|
||||
let score = compute_score_fast(&non_zero_bins, &bin_map, linear_sum, slide);
|
||||
let score = compute_score_fast(&non_zero_bins, &bin_map, slide);
|
||||
local_total += score;
|
||||
if score > local_best_score {
|
||||
local_best_score = score;
|
||||
@@ -170,8 +164,8 @@ pub fn find_best_price(histogram: &Histogram, min_slide: i32, max_slide: i32) ->
|
||||
);
|
||||
|
||||
// Compute neighbor scores for sub-bin interpolation (matches Python behavior)
|
||||
let neighbor_up_score = compute_score_fast(&non_zero_bins, &bin_map, linear_sum, best_position + 1);
|
||||
let neighbor_down_score = compute_score_fast(&non_zero_bins, &bin_map, linear_sum, best_position - 1);
|
||||
let neighbor_up_score = compute_score_fast(&non_zero_bins, &bin_map, best_position + 1);
|
||||
let neighbor_down_score = compute_score_fast(&non_zero_bins, &bin_map, best_position - 1);
|
||||
|
||||
// Find best neighbor
|
||||
let (best_neighbor_offset, neighbor_score) = if neighbor_up_score > neighbor_down_score {
|
||||
@@ -204,7 +198,6 @@ pub fn find_best_price(histogram: &Histogram, min_slide: i32, max_slide: i32) ->
|
||||
fn compute_score_fast(
|
||||
non_zero_bins: &[(usize, f64)],
|
||||
bin_map: &FxHashMap<usize, f64>,
|
||||
linear_sum: f64,
|
||||
slide: i32,
|
||||
) -> f64 {
|
||||
let spike_score = compute_spike_score_hash(bin_map, slide);
|
||||
@@ -212,17 +205,40 @@ fn compute_score_fast(
|
||||
// Python: smooth weight only applied for slide < 150
|
||||
if slide < 150 {
|
||||
let gaussian_score = compute_gaussian_score_sparse(non_zero_bins, slide);
|
||||
let linear_score = compute_linear_score_sparse(non_zero_bins, slide);
|
||||
// Combine Gaussian and linear parts of smooth score
|
||||
let smooth_score = 0.0015 * gaussian_score + linear_sum;
|
||||
let smooth_score = 0.0015 * gaussian_score + linear_score;
|
||||
SMOOTH_WEIGHT * smooth_score + SPIKE_WEIGHT * spike_score
|
||||
} else {
|
||||
SPIKE_WEIGHT * spike_score
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the linear part of the smooth stencil (per-slide, matches Python)
|
||||
/// Python: sum(shifted_curve[n] * 0.0000005 * n) where n is window position (0-802)
|
||||
fn compute_linear_score_sparse(non_zero_bins: &[(usize, f64)], slide: i32) -> f64 {
|
||||
// Window starts at left_p001 + slide = (center_bin - 402) + slide = 199 + slide
|
||||
// Python: left_p001 = center_p001 - int((803+1)/2) = 601 - 402 = 199
|
||||
let window_start = 199 + slide;
|
||||
let window_end = window_start + 803; // 803 elements like Python's stencil
|
||||
let mut score = 0.0;
|
||||
|
||||
for &(i, bin_value) in non_zero_bins {
|
||||
let bin_idx = i as i32;
|
||||
if bin_idx >= window_start && bin_idx < window_end {
|
||||
let window_pos = bin_idx - window_start;
|
||||
score += bin_value * SMOOTH_LINEAR_COEF * window_pos as f64;
|
||||
}
|
||||
}
|
||||
|
||||
score
|
||||
}
|
||||
|
||||
/// Compute just the Gaussian part of the smooth stencil (sparse iteration)
|
||||
/// Note: Gaussian center is offset from spike center by GAUSSIAN_CENTER_OFFSET
|
||||
fn compute_gaussian_score_sparse(non_zero_bins: &[(usize, f64)], slide: i32) -> f64 {
|
||||
let center = center_bin() as i32 + slide;
|
||||
// Python's Gaussian is centered at bin 610 (not 601), so we add the offset
|
||||
let center = center_bin() as i32 + GAUSSIAN_CENTER_OFFSET + slide;
|
||||
let weights = gaussian_weights();
|
||||
let mut score = 0.0;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_error = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
|
||||
@@ -24,23 +24,24 @@ let mut indexer = Indexer::forced_import(&outputs_dir)?;
|
||||
let starting_indexes = indexer.index(&blocks, &client, &exit)?;
|
||||
|
||||
// Access indexed data
|
||||
let height = indexer.stores.txidprefix_to_txindex.get(&txid_prefix)?;
|
||||
let blockhash = indexer.vecs.block.height_to_blockhash.get(height)?;
|
||||
let txindex = indexer.stores.txidprefix_to_txindex.get(&txid_prefix)?;
|
||||
let blockhash = indexer.vecs.blocks.blockhash.get(height)?;
|
||||
```
|
||||
|
||||
## Data Structures
|
||||
|
||||
**Vecs** (append-only vectors):
|
||||
- Block: `height_to_blockhash`, `height_to_timestamp`, `height_to_difficulty`
|
||||
- Transaction: `txindex_to_txid`, `txindex_to_height`, `txindex_to_base_size`
|
||||
- Input/Output: `txinindex_to_outpoint`, `txoutindex_to_value`, `txoutindex_to_outputtype`
|
||||
- Address: Per-type `typeindex_to_addressbytes`
|
||||
- `blocks`: `blockhash`, `timestamp`, `difficulty`, `total_size`, `weight`
|
||||
- `transactions`: `txid`, `first_txinindex`, `first_txoutindex`
|
||||
- `inputs`: `outpoint`, `txindex`
|
||||
- `outputs`: `value`, `outputtype`, `typeindex`, `txindex`
|
||||
- `addresses`: Per-type `p2pkhbytes`, `p2shbytes`, `p2wpkhbytes`, etc.
|
||||
|
||||
**Stores** (key-value lookups):
|
||||
- `txidprefix_to_txindex` - TXID lookup via 10-byte prefix
|
||||
- `blockhashprefix_to_height` - Block lookup via 4-byte prefix
|
||||
- `addresshash_to_addressindex` - Address lookup per type
|
||||
- `addressindex_to_unspent_outpoints` - Live UTXO set per address
|
||||
- `addresstype_to_addresshash_to_addressindex` - Address lookup per type
|
||||
- `addresstype_to_addressindex_and_unspentoutpoint` - Live UTXO set per address
|
||||
|
||||
## Processing Pipeline
|
||||
|
||||
@@ -55,10 +56,10 @@ let blockhash = indexer.vecs.block.height_to_blockhash.get(height)?;
|
||||
|
||||
| Machine | Time | Disk | Peak Disk | Memory | Peak Memory |
|
||||
|---------|------|------|-----------|--------|-------------|
|
||||
| MBP M3 Pro (36GB, internal SSD) | 3.1h | 233 GB | 307 GB | 5.5 GB | 11 GB |
|
||||
| MBP M3 Pro (36GB, internal SSD) | 3h | 247 GB | 314 GB | 5.2 GB | 11 GB |
|
||||
| Mac Mini M4 (16GB, external SSD) | 4.9h | 233 GB | 303 GB | 5.4 GB | 11 GB |
|
||||
|
||||
Full benchmark data: [`https://github.com/bitcoinresearchkit/benches/tree/main/brk_indexer`](/benches/brk_indexer)
|
||||
Full benchmark data: [bitcoinresearchkit/benches](https://github.com/bitcoinresearchkit/benches/tree/main/brk_indexer)
|
||||
|
||||
## Recommended: mimalloc v3
|
||||
|
||||
@@ -66,6 +67,7 @@ Use [mimalloc v3](https://crates.io/crates/mimalloc) as the global allocator to
|
||||
|
||||
## Built On
|
||||
|
||||
- `vecdb` for append-only vectors
|
||||
- `brk_cohort` for address type handling
|
||||
- `brk_iterator` for block iteration
|
||||
- `brk_store` for key-value storage
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_error = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
jiff = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
axum = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_error = { workspace = true }
|
||||
|
||||
@@ -36,10 +36,10 @@ let tracker = mempool.get_addresses();
|
||||
|
||||
Returns `RecommendedFees` with sat/vB rates for different confirmation targets:
|
||||
|
||||
- `fastest_fee` - Next block
|
||||
- `half_hour_fee` - ~3 blocks
|
||||
- `hour_fee` - ~6 blocks
|
||||
- `economy_fee` - ~144 blocks
|
||||
- `fastest_fee` - Next block (index 0)
|
||||
- `half_hour_fee` - ~3 blocks (index 2)
|
||||
- `hour_fee` - ~6 blocks (index 5)
|
||||
- `economy_fee` - ~8 blocks (index 7, last projected block)
|
||||
- `minimum_fee` - Relay minimum
|
||||
|
||||
## Block Projection
|
||||
@@ -48,7 +48,7 @@ Builds projected blocks by:
|
||||
1. Constructing transaction dependency graph
|
||||
2. Calculating effective fee rates (including ancestors)
|
||||
3. Selecting transactions greedily by ancestor-aware fee rate
|
||||
4. Partitioning into ~4MB virtual blocks
|
||||
4. Partitioning into 1MB vsize blocks
|
||||
|
||||
## Built On
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
tokio = ["dep:tokio"]
|
||||
@@ -24,6 +23,6 @@ brk_types = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
# quickmatch = { path = "../../../quickmatch" }
|
||||
quickmatch = "0.1.8"
|
||||
quickmatch = "0.2.0"
|
||||
tokio = { workspace = true, optional = true }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
@@ -25,20 +25,19 @@ let height = query.height();
|
||||
|
||||
// Metric queries
|
||||
let data = query.search_and_format(MetricSelection {
|
||||
vecids: vec!["supply".into()],
|
||||
metrics: vec!["supply".into()],
|
||||
index: Index::Height,
|
||||
range: DataRange::Last(100),
|
||||
..Default::default()
|
||||
range: DataRangeFormat::default(),
|
||||
})?;
|
||||
|
||||
// Block queries
|
||||
let info = query.block_info(Height::new(840_000))?;
|
||||
let info = query.block_by_height(Height::new(840_000))?;
|
||||
|
||||
// Transaction queries
|
||||
let tx = query.transaction(&txid)?;
|
||||
let tx = query.transaction(txid.into())?;
|
||||
|
||||
// Address queries
|
||||
let stats = query.address_stats(&address)?;
|
||||
let stats = query.address(address)?;
|
||||
```
|
||||
|
||||
## Query Types
|
||||
@@ -46,11 +45,11 @@ let stats = query.address_stats(&address)?;
|
||||
| Domain | Methods |
|
||||
|--------|---------|
|
||||
| Metrics | `metrics`, `search_and_format`, `metric_to_indexes` |
|
||||
| Blocks | `block_info`, `block_txs`, `block_status`, `block_timestamp` |
|
||||
| Transactions | `transaction`, `tx_status`, `tx_merkle_proof` |
|
||||
| Addresses | `address_stats`, `address_txs`, `address_utxos` |
|
||||
| Mining | `difficulty_adjustments`, `hashrate`, `pools`, `epochs` |
|
||||
| Mempool | `mempool_info`, `mempool_fees`, `mempool_projected_blocks` |
|
||||
| Blocks | `block`, `block_by_height`, `blocks`, `block_txs`, `block_status`, `block_by_timestamp` |
|
||||
| Transactions | `transaction`, `transaction_status`, `transaction_hex`, `outspend`, `outspends` |
|
||||
| Addresses | `address`, `address_txids`, `address_utxos` |
|
||||
| Mining | `difficulty_adjustments`, `hashrate`, `mining_pools`, `reward_stats` |
|
||||
| Mempool | `mempool_info`, `recommended_fees`, `mempool_blocks` |
|
||||
|
||||
## Async Usage
|
||||
|
||||
@@ -58,7 +57,7 @@ let stats = query.address_stats(&address)?;
|
||||
let async_query = AsyncQuery::build(&reader, &indexer, &computer, mempool);
|
||||
|
||||
// Run blocking queries in thread pool
|
||||
let result = async_query.run(|q| q.block_info(height)).await;
|
||||
let result = async_query.run(|q| q.block_by_height(height)).await;
|
||||
|
||||
// Access inner Query
|
||||
let height = async_query.inner().height();
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ brk_rpc = { workspace = true }
|
||||
brk_types = { workspace = true }
|
||||
brk_traversable = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
include_dir = "0.7"
|
||||
vecdb = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
quick_cache = "0.6.18"
|
||||
@@ -34,5 +35,12 @@ tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tower-http = { version = "0.6.8", features = ["compression-full", "trace"] }
|
||||
|
||||
[build-dependencies]
|
||||
# importmap = { path = "../../../importmap" }
|
||||
importmap = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
brk_mempool = { workspace = true }
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["importmap"]
|
||||
|
||||
@@ -8,7 +8,7 @@ Serve BRK data via REST API with OpenAPI documentation, response caching, MCP en
|
||||
|
||||
## Key Features
|
||||
|
||||
- **OpenAPI spec**: Auto-generated documentation at `/api/openapi.json`
|
||||
- **OpenAPI spec**: Auto-generated documentation at `/api.json` (interactive docs at `/api`)
|
||||
- **Response caching**: LRU cache with 5000 entries for repeated queries
|
||||
- **Compression**: Brotli, gzip, deflate, zstd support
|
||||
- **Static files**: Optional web interface hosting
|
||||
@@ -17,7 +17,8 @@ Serve BRK data via REST API with OpenAPI documentation, response caching, MCP en
|
||||
## Core API
|
||||
|
||||
```rust,ignore
|
||||
let server = Server::new(&async_query, Some(files_path));
|
||||
let server = Server::new(&async_query, data_path, WebsiteSource::Filesystem(files_path));
|
||||
// Or WebsiteSource::Embedded, or WebsiteSource::Disabled
|
||||
server.serve(true).await?; // true enables MCP endpoint
|
||||
```
|
||||
|
||||
@@ -25,17 +26,18 @@ server.serve(true).await?; // true enables MCP endpoint
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `/api/blocks/{height}` | Block info, transactions, status |
|
||||
| `/api/txs/{txid}` | Transaction details, status, merkle proof |
|
||||
| `/api/addresses/{addr}` | Address stats, transactions, UTXOs |
|
||||
| `/api/block-height/{height}` | Block by height |
|
||||
| `/api/block/{hash}` | Block info, transactions, status |
|
||||
| `/api/tx/{txid}` | Transaction details, status, hex |
|
||||
| `/api/address/{addr}` | Address stats, transactions, UTXOs |
|
||||
| `/api/metrics` | Metric catalog and data queries |
|
||||
| `/api/mining/*` | Hashrate, difficulty, pools, epochs |
|
||||
| `/api/v1/mining/*` | Hashrate, difficulty, pools, rewards |
|
||||
| `/api/mempool/*` | Fee estimates, projected blocks |
|
||||
| `/mcp` | MCP endpoint (if enabled) |
|
||||
|
||||
## Caching
|
||||
|
||||
Uses ETag-based caching with stale-while-revalidate semantics:
|
||||
Uses ETag-based caching with must-revalidate semantics:
|
||||
- Height-indexed data: Cache until height changes
|
||||
- Date-indexed data: Cache with longer TTL
|
||||
- Mempool data: Short TTL, frequent updates
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
use std::{env, path::Path};
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
fn main() {
|
||||
let is_dev = env::var("PROFILE").is_ok_and(|p| p == "debug");
|
||||
|
||||
// Generate importmap for website (updates index.html in place)
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let website_path = Path::new(&manifest_dir).join("../../website");
|
||||
|
||||
println!("cargo:rerun-if-changed=../../website");
|
||||
|
||||
if website_path.exists() {
|
||||
// Skip importmap hashing in dev mode (files change often)
|
||||
let map = if is_dev {
|
||||
importmap::ImportMap::empty()
|
||||
} else {
|
||||
importmap::ImportMap::scan(&website_path, "").unwrap_or_else(|_| importmap::ImportMap::empty())
|
||||
};
|
||||
|
||||
let _ = map.update_html_file(&website_path.join("index.html"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use brk_mempool::Mempool;
|
||||
use brk_query::AsyncQuery;
|
||||
use brk_reader::Reader;
|
||||
use brk_rpc::{Auth, Client};
|
||||
use brk_server::Server;
|
||||
use brk_server::{Server, WebsiteSource};
|
||||
use tracing::info;
|
||||
use vecdb::Exit;
|
||||
|
||||
@@ -54,7 +54,7 @@ fn run() -> Result<()> {
|
||||
|
||||
// Option 1: block_on to run and properly propagate errors
|
||||
runtime.block_on(async move {
|
||||
let server = Server::new(&query, outputs_dir, None);
|
||||
let server = Server::new(&query, outputs_dir, WebsiteSource::Disabled);
|
||||
|
||||
let handle = tokio::spawn(async move { server.serve(true).await });
|
||||
|
||||
|
||||
@@ -10,121 +10,63 @@ use axum::{
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use brk_error::{Error, Result};
|
||||
use brk_error::Result;
|
||||
use quick_cache::sync::GuardResult;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{AppState, HeaderMapExtended, ModifiedState, ResponseExtended};
|
||||
use crate::{
|
||||
AppState, EMBEDDED_WEBSITE, HeaderMapExtended, ModifiedState, ResponseExtended, WebsiteSource,
|
||||
};
|
||||
|
||||
pub async fn file_handler(
|
||||
headers: HeaderMap,
|
||||
State(state): State<AppState>,
|
||||
path: extract::Path<String>,
|
||||
) -> Response {
|
||||
any_handler(headers, state, Some(path))
|
||||
any_handler(headers, state, Some(path.0))
|
||||
}
|
||||
|
||||
pub async fn index_handler(headers: HeaderMap, State(state): State<AppState>) -> Response {
|
||||
any_handler(headers, state, None)
|
||||
}
|
||||
|
||||
fn any_handler(
|
||||
headers: HeaderMap,
|
||||
state: AppState,
|
||||
path: Option<extract::Path<String>>,
|
||||
) -> Response {
|
||||
let files_path = state.files_path.as_ref().unwrap();
|
||||
|
||||
if let Some(path) = path.as_ref() {
|
||||
// Sanitize path components to prevent traversal attacks
|
||||
let sanitized: String = path
|
||||
.0
|
||||
.split('/')
|
||||
.filter(|component| !component.is_empty() && *component != "." && *component != "..")
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
|
||||
let mut path = files_path.join(&sanitized);
|
||||
|
||||
// Canonicalize and verify the path stays within the project root
|
||||
// (allows symlinks to modules/ which is outside the website directory)
|
||||
if let Ok(canonical) = path.canonicalize()
|
||||
&& let Ok(canonical_base) = files_path.canonicalize()
|
||||
{
|
||||
// Allow paths within files_path OR within project root (2 levels up)
|
||||
let project_root = canonical_base.parent().and_then(|p| p.parent());
|
||||
let allowed = canonical.starts_with(&canonical_base)
|
||||
|| project_root.is_some_and(|root| canonical.starts_with(root));
|
||||
if !allowed {
|
||||
let mut response: Response<Body> =
|
||||
(StatusCode::FORBIDDEN, "Access denied".to_string()).into_response();
|
||||
response.headers_mut().insert_cors();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip hash from import-mapped URLs (e.g., foo.abc12345.js -> foo.js)
|
||||
if !path.exists()
|
||||
&& let Some(unhashed) = strip_importmap_hash(&path)
|
||||
&& unhashed.exists()
|
||||
{
|
||||
path = unhashed;
|
||||
}
|
||||
|
||||
if !path.exists() || path.is_dir() {
|
||||
if path.extension().is_some() {
|
||||
let mut response: Response<Body> = (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"File doesn't exist".to_string(),
|
||||
)
|
||||
.into_response();
|
||||
|
||||
response.headers_mut().insert_cors();
|
||||
|
||||
return response;
|
||||
} else {
|
||||
path = files_path.join("index.html");
|
||||
}
|
||||
}
|
||||
|
||||
path_to_response(&headers, &state, &path)
|
||||
} else {
|
||||
path_to_response(&headers, &state, &files_path.join("index.html"))
|
||||
}
|
||||
}
|
||||
|
||||
fn path_to_response(headers: &HeaderMap, state: &AppState, path: &Path) -> Response {
|
||||
match path_to_response_(headers, state, path) {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
let mut response =
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
|
||||
|
||||
response.headers_mut().insert_cors();
|
||||
|
||||
response
|
||||
fn any_handler(headers: HeaderMap, state: AppState, path: Option<String>) -> Response {
|
||||
match &state.website {
|
||||
WebsiteSource::Disabled => unreachable!("routes not added when disabled"),
|
||||
WebsiteSource::Embedded => embedded_handler(&state, path),
|
||||
WebsiteSource::Filesystem(files_path) => {
|
||||
filesystem_handler(headers, &state, files_path, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn path_to_response_(headers: &HeaderMap, state: &AppState, path: &Path) -> Result<Response> {
|
||||
let (modified, date) = headers.check_if_modified_since(path)?;
|
||||
if !cfg!(debug_assertions) && modified == ModifiedState::NotModifiedSince {
|
||||
return Ok(Response::new_not_modified());
|
||||
}
|
||||
/// Sanitize path to prevent traversal attacks
|
||||
fn sanitize_path(path: &str) -> String {
|
||||
path.split('/')
|
||||
.filter(|c| !c.is_empty() && *c != "." && *c != "..")
|
||||
.collect::<Vec<_>>()
|
||||
.join("/")
|
||||
}
|
||||
|
||||
let serialized_path = path.to_str().unwrap();
|
||||
/// Check if path requires revalidation (HTML files, service worker)
|
||||
fn must_revalidate(path: &Path) -> bool {
|
||||
path.extension().is_some_and(|ext| ext == "html")
|
||||
|| path
|
||||
.to_str()
|
||||
.is_some_and(|p| p.ends_with("service-worker.js"))
|
||||
}
|
||||
|
||||
let must_revalidate = path
|
||||
.extension()
|
||||
.is_some_and(|extension| extension == "html")
|
||||
|| serialized_path.ends_with("service-worker.js");
|
||||
/// Build response with proper headers and caching
|
||||
fn build_response(state: &AppState, path: &Path, content: Vec<u8>, cache_key: &str) -> Response {
|
||||
let must_revalidate = must_revalidate(path);
|
||||
|
||||
// Use cache for non-HTML files in release mode
|
||||
let guard_res = if !cfg!(debug_assertions) && !must_revalidate {
|
||||
Some(state.cache.get_value_or_guard(
|
||||
&path.to_str().unwrap().to_owned(),
|
||||
Some(Duration::from_millis(50)),
|
||||
))
|
||||
Some(
|
||||
state
|
||||
.cache
|
||||
.get_value_or_guard(&cache_key.to_owned(), Some(Duration::from_millis(50))),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -132,19 +74,10 @@ fn path_to_response_(headers: &HeaderMap, state: &AppState, path: &Path) -> Resu
|
||||
let mut response = if let Some(GuardResult::Value(v)) = guard_res {
|
||||
Response::new(Body::from(v))
|
||||
} else {
|
||||
let content = fs::read(path).unwrap_or_else(|error| {
|
||||
error!("{error}");
|
||||
let path = path.to_str().unwrap();
|
||||
info!("Can't read file {path}");
|
||||
panic!("")
|
||||
});
|
||||
|
||||
if let Some(GuardResult::Guard(g)) = guard_res {
|
||||
g.insert(content.clone().into())
|
||||
.map_err(|_| Error::QuickCacheError)?;
|
||||
let _ = g.insert(content.clone().into());
|
||||
}
|
||||
|
||||
Response::new(content.into())
|
||||
Response::new(Body::from(content))
|
||||
};
|
||||
|
||||
let headers = response.headers_mut();
|
||||
@@ -157,7 +90,129 @@ fn path_to_response_(headers: &HeaderMap, state: &AppState, path: &Path) -> Resu
|
||||
headers.insert_cache_control_immutable();
|
||||
}
|
||||
|
||||
headers.insert_last_modified(date);
|
||||
response
|
||||
}
|
||||
|
||||
fn embedded_handler(state: &AppState, path: Option<String>) -> Response {
|
||||
let path = path.unwrap_or_else(|| "index.html".to_string());
|
||||
let sanitized = sanitize_path(&path);
|
||||
|
||||
// Try to get file, with importmap hash stripping and SPA fallback
|
||||
let file = EMBEDDED_WEBSITE
|
||||
.get_file(&sanitized)
|
||||
.or_else(|| {
|
||||
strip_importmap_hash(Path::new(&sanitized))
|
||||
.and_then(|unhashed| EMBEDDED_WEBSITE.get_file(unhashed.to_str()?))
|
||||
})
|
||||
.or_else(|| {
|
||||
// If no extension, serve index.html (SPA routing)
|
||||
if Path::new(&sanitized).extension().is_none() {
|
||||
EMBEDDED_WEBSITE.get_file("index.html")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let Some(file) = file else {
|
||||
let mut response: Response<Body> =
|
||||
(StatusCode::NOT_FOUND, "File not found".to_string()).into_response();
|
||||
response.headers_mut().insert_cors();
|
||||
return response;
|
||||
};
|
||||
|
||||
build_response(
|
||||
state,
|
||||
Path::new(file.path()),
|
||||
file.contents().to_vec(),
|
||||
&file.path().to_string_lossy(),
|
||||
)
|
||||
}
|
||||
|
||||
fn filesystem_handler(
|
||||
headers: HeaderMap,
|
||||
state: &AppState,
|
||||
files_path: &Path,
|
||||
path: Option<String>,
|
||||
) -> Response {
|
||||
let path = if let Some(path) = path {
|
||||
let sanitized = sanitize_path(&path);
|
||||
let mut path = files_path.join(&sanitized);
|
||||
|
||||
// Canonicalize and verify the path stays within the project root
|
||||
// (allows symlinks to modules/ which is outside the website directory)
|
||||
if let Ok(canonical) = path.canonicalize()
|
||||
&& let Ok(canonical_base) = files_path.canonicalize()
|
||||
{
|
||||
let project_root = canonical_base.parent().and_then(|p| p.parent());
|
||||
let allowed = canonical.starts_with(&canonical_base)
|
||||
|| project_root.is_some_and(|root| canonical.starts_with(root));
|
||||
if !allowed {
|
||||
let mut response: Response<Body> =
|
||||
(StatusCode::FORBIDDEN, "Access denied".to_string()).into_response();
|
||||
response.headers_mut().insert_cors();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip hash from import-mapped URLs
|
||||
if !path.exists()
|
||||
&& let Some(unhashed) = strip_importmap_hash(&path)
|
||||
&& unhashed.exists()
|
||||
{
|
||||
path = unhashed;
|
||||
}
|
||||
|
||||
// SPA fallback
|
||||
if !path.exists() || path.is_dir() {
|
||||
if path.extension().is_some() {
|
||||
let mut response: Response<Body> = (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"File doesn't exist".to_string(),
|
||||
)
|
||||
.into_response();
|
||||
response.headers_mut().insert_cors();
|
||||
return response;
|
||||
} else {
|
||||
path = files_path.join("index.html");
|
||||
}
|
||||
}
|
||||
|
||||
path
|
||||
} else {
|
||||
files_path.join("index.html")
|
||||
};
|
||||
|
||||
path_to_response(&headers, state, &path)
|
||||
}
|
||||
|
||||
fn path_to_response(headers: &HeaderMap, state: &AppState, path: &Path) -> Response {
|
||||
match path_to_response_(headers, state, path) {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
let mut response =
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
|
||||
response.headers_mut().insert_cors();
|
||||
response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn path_to_response_(headers: &HeaderMap, state: &AppState, path: &Path) -> Result<Response> {
|
||||
let (modified, date) = headers.check_if_modified_since(path)?;
|
||||
if !cfg!(debug_assertions) && modified == ModifiedState::NotModifiedSince {
|
||||
return Ok(Response::new_not_modified());
|
||||
}
|
||||
|
||||
let content = fs::read(path).unwrap_or_else(|error| {
|
||||
error!("{error}");
|
||||
let path = path.to_str().unwrap();
|
||||
info!("Can't read file {path}");
|
||||
panic!("")
|
||||
});
|
||||
|
||||
let cache_key = path.to_str().unwrap();
|
||||
let mut response = build_response(state, path, content, cache_key);
|
||||
response.headers_mut().insert_last_modified(date);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use aide::axum::ApiRouter;
|
||||
use axum::{response::Redirect, routing::get};
|
||||
|
||||
use super::AppState;
|
||||
use super::{AppState, WebsiteSource};
|
||||
|
||||
mod file;
|
||||
|
||||
use file::{file_handler, index_handler};
|
||||
|
||||
pub trait FilesRoutes {
|
||||
fn add_files_routes(self, path: Option<&PathBuf>) -> Self;
|
||||
fn add_files_routes(self, website: &WebsiteSource) -> Self;
|
||||
}
|
||||
|
||||
impl FilesRoutes for ApiRouter<AppState> {
|
||||
fn add_files_routes(self, path: Option<&PathBuf>) -> Self {
|
||||
if path.is_some() {
|
||||
fn add_files_routes(self, website: &WebsiteSource) -> Self {
|
||||
if website.is_enabled() {
|
||||
self.route("/{*path}", get(file_handler))
|
||||
.route("/", get(index_handler))
|
||||
} else {
|
||||
|
||||
@@ -15,11 +15,29 @@ use axum::{
|
||||
use brk_error::Result;
|
||||
use brk_mcp::route::mcp_router;
|
||||
use brk_query::AsyncQuery;
|
||||
use include_dir::{include_dir, Dir};
|
||||
use quick_cache::sync::Cache;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
||||
use tracing::{error, info};
|
||||
|
||||
/// Embedded website assets
|
||||
pub static EMBEDDED_WEBSITE: Dir = include_dir!("$CARGO_MANIFEST_DIR/../../website");
|
||||
|
||||
/// Source for serving the website
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WebsiteSource {
|
||||
Disabled,
|
||||
Embedded,
|
||||
Filesystem(PathBuf),
|
||||
}
|
||||
|
||||
impl WebsiteSource {
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
!matches!(self, Self::Disabled)
|
||||
}
|
||||
}
|
||||
|
||||
mod api;
|
||||
pub mod cache;
|
||||
mod extended;
|
||||
@@ -37,12 +55,12 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub struct Server(AppState);
|
||||
|
||||
impl Server {
|
||||
pub fn new(query: &AsyncQuery, data_path: PathBuf, files_path: Option<PathBuf>) -> Self {
|
||||
pub fn new(query: &AsyncQuery, data_path: PathBuf, website: WebsiteSource) -> Self {
|
||||
Self(AppState {
|
||||
client: query.client().clone(),
|
||||
query: query.clone(),
|
||||
data_path,
|
||||
files_path,
|
||||
website,
|
||||
cache: Arc::new(Cache::new(5_000)),
|
||||
started_at: jiff::Timestamp::now(),
|
||||
started_instant: Instant::now(),
|
||||
@@ -87,7 +105,7 @@ impl Server {
|
||||
let vecs = state.query.inner().vecs();
|
||||
let router = ApiRouter::new()
|
||||
.add_api_routes()
|
||||
.add_files_routes(state.files_path.as_ref())
|
||||
.add_files_routes(&state.website)
|
||||
.route(
|
||||
"/discord",
|
||||
get(Redirect::temporary("https://discord.gg/WACpShCB7M")),
|
||||
|
||||
@@ -13,7 +13,7 @@ use quick_cache::sync::Cache;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
CacheParams, CacheStrategy,
|
||||
CacheParams, CacheStrategy, WebsiteSource,
|
||||
extended::{ResponseExtended, ResultExtended},
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct AppState {
|
||||
#[deref]
|
||||
pub query: AsyncQuery,
|
||||
pub data_path: PathBuf,
|
||||
pub files_path: Option<PathBuf>,
|
||||
pub website: WebsiteSource,
|
||||
pub cache: Arc<Cache<String, Bytes>>,
|
||||
pub client: Client,
|
||||
pub started_at: Timestamp,
|
||||
|
||||
@@ -8,7 +8,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
brk_error = { workspace = true }
|
||||
|
||||
@@ -19,7 +19,7 @@ Persist and query Bitcoin index data (address→outputs, txid→height, etc.) wi
|
||||
```rust,ignore
|
||||
let store: Store<Txid, Height> = Store::import(
|
||||
&db, &path, "txid_to_height",
|
||||
Version::new(1), Mode::default(), Kind::Random
|
||||
Version::new(1), Mode::Any, Kind::Random
|
||||
)?;
|
||||
|
||||
store.insert(txid, height);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
derive = ["brk_traversable_derive"]
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
|
||||
@@ -9,8 +9,8 @@ Work with Bitcoin primitives (heights, satoshis, addresses, transactions) throug
|
||||
## Key Features
|
||||
|
||||
- **Bitcoin primitives**: `Height`, `Sats`, `Txid`, `BlockHash`, `Outpoint` with full arithmetic and conversion support
|
||||
- **Address types**: All output types (P2PKH, P2SH, P2WPKH, P2WSH, P2TR, P2PK, P2A, OP_RETURN) with address index variants
|
||||
- **Time indexes**: `DateIndex`, `WeekIndex`, `MonthIndex`, `QuarterIndex`, `YearIndex`, `DecadeIndex` with cross-index conversion
|
||||
- **Address types**: All output types (P2PK33, P2PK65, P2PKH, P2MS, P2SH, P2WPKH, P2WSH, P2TR, P2A, OP_RETURN) with address index variants
|
||||
- **Time indexes**: `DateIndex`, `WeekIndex`, `MonthIndex`, `QuarterIndex`, `SemesterIndex`, `YearIndex`, `DecadeIndex` with cross-index conversion
|
||||
- **Protocol types**: `DifficultyEpoch`, `HalvingEpoch`, `TxVersion`, `RawLocktime`
|
||||
- **Financial types**: `Dollars`, `Cents`, `OHLC` (Open/High/Low/Close)
|
||||
- **Serialization**: Serde + JSON Schema generation via schemars
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fn main() {
|
||||
let profile = std::env::var("PROFILE").unwrap_or_default();
|
||||
|
||||
if profile == "release" {
|
||||
println!("cargo:rustc-flag=-C");
|
||||
println!("cargo:rustc-flag=target-cpu=native");
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,7 @@
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
BRK is designed to be self-hosted. Install and run with:
|
||||
|
||||
```bash
|
||||
cargo install --locked brk_cli
|
||||
brk --bitcoindir ~/.bitcoin --brkdir ~/.brk
|
||||
```
|
||||
|
||||
Requirements:
|
||||
- Bitcoin Core with accessible `blk*.dat` files
|
||||
- ~400 GB disk space
|
||||
- 12+ GB RAM recommended
|
||||
|
||||
See the [CLI documentation](https://docs.rs/brk_cli) for configuration options.
|
||||
BRK is designed to be self-hosted. See the [brk_cli documentation](https://docs.rs/brk_cli) for installation, requirements, and configuration options.
|
||||
|
||||
## Professional Hosting
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ BRK parses, indexes, and analyzes Bitcoin blockchain data. It combines on-chain
|
||||
|
||||
**Use the Public API** — Access data without running infrastructure. Client libraries available for [JavaScript](https://www.npmjs.com/package/brk-client), [Python](https://pypi.org/project/brk-client/), and [Rust](https://crates.io/crates/brk_client). See the [API reference](https://bitcoinresearchkit.org/api) for endpoints.
|
||||
|
||||
**Self-Host** — Run your own instance with Bitcoin Core. Install via `cargo install --locked brk_cli` or use [Docker](https://github.com/bitcoinresearchkit/brk/tree/main/docker). Requires ~400 GB disk and 12+ GB RAM. See the [hosting guide](./HOSTING.md).
|
||||
**Self-Host** — Run your own instance with Bitcoin Core. Install via [`brk_cli`](https://docs.rs/brk_cli) or use [Docker](https://github.com/bitcoinresearchkit/brk/tree/main/docker). See the [hosting guide](./HOSTING.md).
|
||||
|
||||
**Use as a Library** — Build custom tools with the Rust crates. Use individual components or the [umbrella crate](https://docs.rs/brk). See [architecture](./ARCHITECTURE.md) for how they fit together.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
2
modules/lean-qr/2.7.1/index.mjs
Normal file
2
modules/lean-qr/2.7.1/index.mjs
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5000
modules/lightweight-charts/5.0.9/dist/typings.d.ts
vendored
5000
modules/lightweight-charts/5.0.9/dist/typings.d.ts
vendored
File diff suppressed because it is too large
Load Diff
1
modules/modern-screenshot/4.6.6/.gitignore
vendored
1
modules/modern-screenshot/4.6.6/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.js
|
||||
354
modules/modern-screenshot/4.6.6/dist/index.d.ts
vendored
354
modules/modern-screenshot/4.6.6/dist/index.d.ts
vendored
@@ -1,354 +0,0 @@
|
||||
// @ts-nocheck
|
||||
interface Options {
|
||||
/**
|
||||
* Width in pixels to be applied to node before rendering.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* Height in pixels to be applied to node before rendering.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* A number between `0` and `1` indicating image quality (e.g. 0.92 => 92%) of the JPEG image.
|
||||
*/
|
||||
quality?: number;
|
||||
/**
|
||||
* A string indicating the image format. The default type is image/png; that type is also used if the given type isn't supported.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* The pixel ratio of captured image.
|
||||
*
|
||||
* DPI = 96 * scale
|
||||
*
|
||||
* default: 1
|
||||
*/
|
||||
scale?: number;
|
||||
/**
|
||||
* A string value for the background color, any valid CSS color value.
|
||||
*/
|
||||
backgroundColor?: string | null;
|
||||
/**
|
||||
* An object whose properties to be copied to node's style before rendering.
|
||||
*/
|
||||
style?: Partial<CSSStyleDeclaration> | null;
|
||||
/**
|
||||
* A function taking DOM node as argument. Should return `true` if passed
|
||||
* node should be included in the output. Excluding node means excluding
|
||||
* it's children as well.
|
||||
*/
|
||||
filter?: ((el: Node) => boolean) | null;
|
||||
/**
|
||||
* Maximum canvas size (pixels).
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
|
||||
*/
|
||||
maximumCanvasSize?: number;
|
||||
/**
|
||||
* Load media timeout and fetch remote asset timeout (millisecond).
|
||||
*
|
||||
* default: 30000
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* Embed assets progress.
|
||||
*/
|
||||
progress?: ((current: number, total: number) => void) | null;
|
||||
/**
|
||||
* Enable debug mode to view the execution time log.
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Custom implementation to get image data for a custom URL.
|
||||
* This can be helpful for Capacitor or Cordova when using
|
||||
* native fetch to bypass CORS issues.
|
||||
*
|
||||
* If returns a string, will completely bypass any `Options.fetch`
|
||||
* settings with your custom implementation.
|
||||
*
|
||||
* If returns false, will fall back to normal fetch implementation
|
||||
*
|
||||
* @param url
|
||||
* @returns A data URL for the image
|
||||
*/
|
||||
fetchFn?: ((url: string) => Promise<string | false>) | null;
|
||||
/**
|
||||
* The options of fetch resources.
|
||||
*/
|
||||
fetch?: {
|
||||
/**
|
||||
* The second parameter of `window.fetch` RequestInit
|
||||
*
|
||||
* default: {
|
||||
* cache: 'force-cache',
|
||||
* }
|
||||
*/
|
||||
requestInit?: RequestInit;
|
||||
/**
|
||||
* Set to `true` to append the current time as a query string to URL
|
||||
* requests to enable cache busting.
|
||||
*
|
||||
* default: false
|
||||
*/
|
||||
bypassingCache?: boolean | RegExp;
|
||||
/**
|
||||
* A data URL for a placeholder image that will be used when fetching
|
||||
* an image fails. Defaults to an empty string and will render empty
|
||||
* areas for failed images.
|
||||
*
|
||||
* default: data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
|
||||
*/
|
||||
placeholderImage?: string | ((cloned: HTMLImageElement | SVGImageElement) => string | Promise<string>);
|
||||
};
|
||||
/**
|
||||
* The options of fonts download and embed.
|
||||
*/
|
||||
font?: false | {
|
||||
/**
|
||||
* Font minify
|
||||
*/
|
||||
minify?: (font: ArrayBuffer, subset: string) => ArrayBuffer;
|
||||
/**
|
||||
* The preferred font format. If specified all other font formats are ignored.
|
||||
*/
|
||||
preferredFormat?: 'woff' | 'woff2' | 'truetype' | 'opentype' | 'embedded-opentype' | 'svg' | string;
|
||||
/**
|
||||
* A CSS string to specify for font embeds. If specified only this CSS will
|
||||
* be present in the resulting image.
|
||||
*/
|
||||
cssText?: string;
|
||||
};
|
||||
/**
|
||||
* All enabled features
|
||||
*
|
||||
* default: true
|
||||
*/
|
||||
features?: boolean | {
|
||||
/**
|
||||
* Copy scrollbar css styles
|
||||
*
|
||||
* default: true
|
||||
*/
|
||||
copyScrollbar?: boolean;
|
||||
/**
|
||||
* Remove abnormal attributes to cloned node (for normalize XML)
|
||||
*
|
||||
* default: true
|
||||
*/
|
||||
removeAbnormalAttributes?: boolean;
|
||||
/**
|
||||
* Remove control characters (for normalize XML)
|
||||
*
|
||||
* default: true
|
||||
*/
|
||||
removeControlCharacter?: boolean;
|
||||
/**
|
||||
* Fix svg+xml image decode (for Safari、Firefox)
|
||||
*
|
||||
* default: true
|
||||
*/
|
||||
fixSvgXmlDecode?: boolean;
|
||||
/**
|
||||
* Render scrolled children with scrolled content
|
||||
*
|
||||
* default: false
|
||||
*/
|
||||
restoreScrollPosition?: boolean;
|
||||
};
|
||||
/**
|
||||
* Canvas `drawImage` interval
|
||||
* is used to fix errors in decoding images in Safari、Firefox
|
||||
*
|
||||
* default: 100
|
||||
*/
|
||||
drawImageInterval?: number;
|
||||
/**
|
||||
* Web Worker script url
|
||||
*/
|
||||
workerUrl?: string | null;
|
||||
/**
|
||||
* Web Worker number
|
||||
*/
|
||||
workerNumber?: number;
|
||||
/**
|
||||
* Triggered after each node is cloned
|
||||
*/
|
||||
onCloneEachNode?: ((cloned: Node) => void | Promise<void>) | null;
|
||||
/**
|
||||
* Triggered after a node is cloned
|
||||
*/
|
||||
onCloneNode?: ((cloned: Node) => void | Promise<void>) | null;
|
||||
/**
|
||||
* Triggered after a node is embed
|
||||
*/
|
||||
onEmbedNode?: ((cloned: Node) => void | Promise<void>) | null;
|
||||
/**
|
||||
* Triggered after a ForeignObjectSvg is created
|
||||
*/
|
||||
onCreateForeignObjectSvg?: ((svg: SVGSVGElement) => void | Promise<void>) | null;
|
||||
/**
|
||||
* An array of style property names.
|
||||
* Can be used to manually specify which style properties are
|
||||
* included when cloning nodes.
|
||||
* This can be useful for performance-critical scenarios.
|
||||
*/
|
||||
includeStyleProperties?: string[] | null;
|
||||
}
|
||||
|
||||
interface Request {
|
||||
type: 'image' | 'text';
|
||||
resolve?: (response: string) => void;
|
||||
reject?: (error: Error) => void;
|
||||
response: Promise<string>;
|
||||
}
|
||||
interface InternalContext<T extends Node> {
|
||||
/**
|
||||
* FLAG
|
||||
*/
|
||||
__CONTEXT__: true;
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
log: {
|
||||
time: (label: string) => void;
|
||||
timeEnd: (label: string) => void;
|
||||
warn: (...args: any[]) => void;
|
||||
};
|
||||
/**
|
||||
* Node
|
||||
*/
|
||||
node: T;
|
||||
/**
|
||||
* Owner document
|
||||
*/
|
||||
ownerDocument?: Document;
|
||||
/**
|
||||
* Owner window
|
||||
*/
|
||||
ownerWindow?: Window;
|
||||
/**
|
||||
* DPI
|
||||
*
|
||||
* scale === 1 ? null : 96 * scale
|
||||
*/
|
||||
dpi: number | null;
|
||||
/**
|
||||
* The `style` element under the root `svg` element
|
||||
*/
|
||||
svgStyleElement?: HTMLStyleElement;
|
||||
/**
|
||||
* The `defs` element under the root `svg` element
|
||||
*/
|
||||
svgDefsElement?: SVGDefsElement;
|
||||
/**
|
||||
* The `svgStyleElement` class styles
|
||||
*
|
||||
* Map<cssText, class[]>
|
||||
*/
|
||||
svgStyles: Map<string, string[]>;
|
||||
/**
|
||||
* The map of default `getComputedStyle` for all tagnames
|
||||
*/
|
||||
defaultComputedStyles: Map<string, Map<string, any>>;
|
||||
/**
|
||||
* The IFrame sandbox used to get the `defaultComputedStyles`
|
||||
*/
|
||||
sandbox?: HTMLIFrameElement;
|
||||
/**
|
||||
* Web Workers
|
||||
*/
|
||||
workers: Worker[];
|
||||
/**
|
||||
* The map of `font-family` values for all cloend elements
|
||||
*/
|
||||
fontFamilies: Map<string, Set<string>>;
|
||||
/**
|
||||
* Map<CssUrl, DataUrl>
|
||||
*/
|
||||
fontCssTexts: Map<string, string>;
|
||||
/**
|
||||
* `headers.accept` to use when `window.fetch` fetches images
|
||||
*/
|
||||
acceptOfImage: string;
|
||||
/**
|
||||
* All requests for `fetch`
|
||||
*/
|
||||
requests: Map<string, Request>;
|
||||
/**
|
||||
* Canvas multiple draw image fix svg+xml image decoding in Safari and Firefox
|
||||
*/
|
||||
drawImageCount: number;
|
||||
/**
|
||||
* Wait for all tasks embedded in
|
||||
*/
|
||||
tasks: Promise<void>[];
|
||||
/**
|
||||
* Automatically destroy context
|
||||
*/
|
||||
autoDestruct: boolean;
|
||||
/**
|
||||
* Is enable
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
isEnable: (key: string) => boolean;
|
||||
/**
|
||||
* [cloning phase] To get the node style set by the user
|
||||
*/
|
||||
currentNodeStyle?: Map<string, [string, string]>;
|
||||
currentParentNodeStyle?: Map<string, [string, string]>;
|
||||
/**
|
||||
* [cloning phase] shadowDOM root list
|
||||
*/
|
||||
shadowRoots: ShadowRoot[];
|
||||
}
|
||||
type Context<T extends Node = Node> = InternalContext<T> & Required<Options>;
|
||||
|
||||
declare function domToBlob<T extends Node>(node: T, options?: Options): Promise<Blob>;
|
||||
declare function domToBlob<T extends Node>(context: Context<T>): Promise<Blob>;
|
||||
|
||||
declare function domToCanvas<T extends Node>(node: T, options?: Options): Promise<HTMLCanvasElement>;
|
||||
declare function domToCanvas<T extends Node>(context: Context<T>): Promise<HTMLCanvasElement>;
|
||||
|
||||
declare function domToDataUrl<T extends Node>(node: T, options?: Options): Promise<string>;
|
||||
declare function domToDataUrl<T extends Node>(context: Context<T>): Promise<string>;
|
||||
|
||||
declare function domToForeignObjectSvg<T extends Node>(node: T, options?: Options): Promise<SVGElement>;
|
||||
declare function domToForeignObjectSvg<T extends Node>(context: Context<T>): Promise<SVGElement>;
|
||||
|
||||
declare function domToImage<T extends Node>(node: T, options?: Options): Promise<HTMLImageElement>;
|
||||
declare function domToImage<T extends Node>(context: Context<T>): Promise<HTMLImageElement>;
|
||||
|
||||
declare function domToJpeg<T extends Node>(node: T, options?: Options): Promise<string>;
|
||||
declare function domToJpeg<T extends Node>(context: Context<T>): Promise<string>;
|
||||
|
||||
declare function domToPixel<T extends Node>(node: T, options?: Options): Promise<Uint8ClampedArray>;
|
||||
declare function domToPixel<T extends Node>(context: Context<T>): Promise<Uint8ClampedArray>;
|
||||
|
||||
declare function domToPng<T extends Node>(node: T, options?: Options): Promise<string>;
|
||||
declare function domToPng<T extends Node>(context: Context<T>): Promise<string>;
|
||||
|
||||
declare function domToSvg<T extends Node>(node: T, options?: Options): Promise<string>;
|
||||
declare function domToSvg<T extends Node>(context: Context<T>): Promise<string>;
|
||||
|
||||
declare function domToWebp<T extends Node>(node: T, options?: Options): Promise<string>;
|
||||
declare function domToWebp<T extends Node>(context: Context<T>): Promise<string>;
|
||||
|
||||
declare function createContext<T extends Node>(node: T, options?: Options & {
|
||||
autoDestruct?: boolean;
|
||||
}): Promise<Context<T>>;
|
||||
|
||||
declare function destroyContext(context: Context): void;
|
||||
|
||||
type Media = HTMLVideoElement | HTMLImageElement | SVGImageElement;
|
||||
interface LoadMediaOptions {
|
||||
ownerDocument?: Document;
|
||||
timeout?: number;
|
||||
onError?: (error: Error) => void;
|
||||
onWarn?: (...args: any[]) => void;
|
||||
}
|
||||
declare function loadMedia<T extends Media>(media: T, options?: LoadMediaOptions): Promise<T>;
|
||||
declare function loadMedia(media: string, options?: LoadMediaOptions): Promise<HTMLImageElement>;
|
||||
declare function waitUntilLoad(node: Node, options?: LoadMediaOptions): Promise<void>;
|
||||
|
||||
export { type Context, type Options, createContext, destroyContext, domToBlob, domToCanvas, domToDataUrl, domToForeignObjectSvg, domToImage, domToJpeg, domToPixel, domToPng, domToSvg, domToWebp, loadMedia, waitUntilLoad };
|
||||
1653
modules/modern-screenshot/4.6.6/dist/index.mjs
vendored
1653
modules/modern-screenshot/4.6.6/dist/index.mjs
vendored
File diff suppressed because it is too large
Load Diff
14
modules/modern-screenshot/4.6.7/dist/index.js
vendored
14
modules/modern-screenshot/4.6.7/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
function changeJpegDpi(uint8Array, dpi) {
|
||||
uint8Array[13] = 1;
|
||||
uint8Array[14] = dpi >> 8;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ export interface SignalOptions<T> {
|
||||
equals?: ((prev: T, next: T) => boolean) | false;
|
||||
pureWrite?: boolean;
|
||||
unobserved?: () => void;
|
||||
lazy?: boolean;
|
||||
}
|
||||
export interface RawSignal<T> {
|
||||
id?: string;
|
||||
@@ -64,6 +65,7 @@ export interface Computed<T> extends RawSignal<T>, Owner {
|
||||
_nextHeap: Computed<any> | undefined;
|
||||
_prevHeap: Computed<any>;
|
||||
_fn: (prev?: T) => T;
|
||||
_inFlight: Promise<T> | AsyncIterable<T> | null;
|
||||
_child: FirewallSignal<any> | null;
|
||||
_notifyQueue?: (statusFlagsChanged: boolean, prevStatusFlags: number) => void;
|
||||
}
|
||||
@@ -74,16 +76,11 @@ export interface Root extends Owner {
|
||||
}
|
||||
export declare let context: Owner | null;
|
||||
export declare function recompute(el: Computed<any>, create?: boolean): void;
|
||||
export declare function handleAsync<T>(el: Computed<T>, result: T | Promise<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
|
||||
export declare function dispose(node: Computed<unknown>): void;
|
||||
export declare function getNextChildId(owner: Owner): string;
|
||||
export declare function computed<T>(fn: (prev?: T) => T): Computed<T>;
|
||||
export declare function computed<T>(fn: (prev: T) => T, initialValue?: T, options?: SignalOptions<T>): Computed<T>;
|
||||
export declare function asyncComputed<T>(asyncFn: (prev?: T, refreshing?: boolean) => T | Promise<T> | AsyncIterable<T>): Computed<T> & {
|
||||
_refresh: () => void;
|
||||
};
|
||||
export declare function asyncComputed<T>(asyncFn: (prev: T, refreshing?: boolean) => T | Promise<T> | AsyncIterable<T>, initialValue: T, options?: SignalOptions<T>): Computed<T> & {
|
||||
_refresh: () => void;
|
||||
};
|
||||
export declare function computed<T>(fn: (prev?: T) => T | Promise<T> | AsyncIterable<T>): Computed<T>;
|
||||
export declare function computed<T>(fn: (prev: T) => T | Promise<T> | AsyncIterable<T>, initialValue?: T, options?: SignalOptions<T>): Computed<T>;
|
||||
export declare function signal<T>(v: T, options?: SignalOptions<T>): Signal<T>;
|
||||
export declare function signal<T>(v: T, options?: SignalOptions<T>, firewall?: Computed<any>): FirewallSignal<T>;
|
||||
export declare function isEqual<T>(a: T, b: T): boolean;
|
||||
@@ -121,3 +118,5 @@ export declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
|
||||
export declare function staleValues<T>(fn: () => T, set?: boolean): T;
|
||||
export declare function pending<T>(fn: () => T): T;
|
||||
export declare function isPending(fn: () => any): boolean;
|
||||
export declare function refresh<T>(fn: () => T): T;
|
||||
export declare function isRefreshing(): boolean;
|
||||
@@ -1,6 +1,6 @@
|
||||
export { ContextNotFoundError, NoOwnerError, NotReadyError } from "./error.js";
|
||||
export { createContext, getContext, setContext, type Context, type ContextRecord } from "./context.js";
|
||||
export { getObserver, isEqual, untrack, getOwner, runWithOwner, createOwner, createRoot, computed, dispose, signal, asyncComputed, read, setSignal, onCleanup, getNextChildId, isPending, pending, staleValues, type Owner, type Computed, type Root, type Signal, type SignalOptions } from "./core.js";
|
||||
export { getObserver, isEqual, untrack, getOwner, runWithOwner, createOwner, createRoot, computed, dispose, signal, read, setSignal, onCleanup, getNextChildId, isPending, pending, refresh, isRefreshing, staleValues, handleAsync, type Owner, type Computed, type Root, type Signal, type SignalOptions } from "./core.js";
|
||||
export { effect, type Effect } from "./effect.js";
|
||||
export { flush, Queue, type IQueue, type QueueCallback } from "./scheduler.js";
|
||||
export * from "./constants.js";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user