global: snapshot

This commit is contained in:
nym21
2026-01-14 16:38:53 +01:00
parent ddb1db7a8e
commit d75c2a881b
226 changed files with 7776 additions and 20942 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)),
})
}
}