1#![doc = include_str!("../README.md")]
2
3use std::{
4 fs,
5 io::Cursor,
6 path::Path,
7 thread::{self, sleep},
8 time::Duration,
9};
10
11use bitcoincore_rpc::{self, RpcApi};
12use brk_bundler::bundle;
13use brk_computer::Computer;
14use brk_indexer::Indexer;
15use brk_interface::Interface;
16use brk_parser::Parser;
17use brk_server::{Server, VERSION};
18use log::info;
19use vecdb::Exit;
20
21mod bridge;
22mod config;
23mod paths;
24mod website;
25
26use crate::{bridge::Bridge, config::Config, paths::*};
27
28pub fn main() -> color_eyre::Result<()> {
29 color_eyre::install()?;
30
31 fs::create_dir_all(dot_brk_path())?;
32
33 brk_logger::init(Some(&dot_brk_log_path()))?;
34
35 thread::Builder::new()
36 .stack_size(256 * 1024 * 1024)
37 .spawn(run)?
38 .join()
39 .unwrap()
40}
41
42pub fn run() -> color_eyre::Result<()> {
43 let config = Config::import()?;
44
45 let rpc = config.rpc()?;
46
47 let exit = Exit::new();
48 exit.set_ctrlc_handler();
49
50 let parser = Parser::new(config.blocksdir(), Some(config.brkdir()), rpc);
51
52 let mut indexer = Indexer::forced_import(&config.brkdir())?;
53
54 let wait_for_synced_node = |rpc_client: &bitcoincore_rpc::Client| -> color_eyre::Result<()> {
55 let is_synced = || -> color_eyre::Result<bool> {
56 let info = rpc_client.get_blockchain_info()?;
57 Ok(info.headers == info.blocks)
58 };
59
60 if !is_synced()? {
61 info!("Waiting for node to be synced...");
62 while !is_synced()? {
63 sleep(Duration::from_secs(1))
64 }
65 }
66
67 Ok(())
68 };
69
70 let mut computer = Computer::forced_import(&config.brkdir(), &indexer, config.fetcher())?;
71
72 tokio::runtime::Builder::new_multi_thread()
73 .enable_all()
74 .build()?
75 .block_on(async {
76 let interface = Interface::build(&indexer, &computer);
77
78 let website = config.website();
79
80 let downloads_path = config.downloads_dir();
81
82 let bundle_path = if website.is_some() {
83 let websites_dev_path = Path::new("../../websites");
84
85 let websites_path = if fs::exists(websites_dev_path)? {
86 websites_dev_path.to_path_buf()
87 } else {
88 let downloaded_websites_path =
89 downloads_path.join(format!("brk-{VERSION}")).join("websites");
90
91 if !fs::exists(&downloaded_websites_path)? {
92 info!("Downloading websites from Github...");
93
94 let url = format!(
95 "https://github.com/bitcoinresearchkit/brk/archive/refs/tags/v{VERSION}.zip",
96 );
97
98 let response = minreq::get(url).send()?;
99 let bytes = response.as_bytes();
100 let cursor = Cursor::new(bytes);
101
102 let mut zip = zip::ZipArchive::new(cursor).unwrap();
103
104 zip.extract(downloads_path).unwrap();
105 }
106
107 downloaded_websites_path
108 };
109
110 interface.generate_bridge_files(website, websites_path.as_path())?;
111
112 Some(bundle(&websites_path, website.to_folder_name(), true).await?)
113 } else {
114 None
115 };
116
117 let server = Server::new(
118 interface,
119 bundle_path,
120 );
121
122 tokio::spawn(async move {
123 server.serve(true).await.unwrap();
124 });
125
126 sleep(Duration::from_secs(1));
127
128 loop {
129 wait_for_synced_node(rpc)?;
130
131 let block_count = rpc.get_block_count()?;
132
133 info!("{} blocks found.", block_count + 1);
134
135 let starting_indexes =
136 indexer.index(&parser, rpc, &exit, config.check_collisions()).unwrap();
137
138 computer.compute(&indexer, starting_indexes, &exit).unwrap();
141
142 info!("Waiting for new blocks...");
143
144 while block_count == rpc.get_block_count()? {
145 sleep(Duration::from_secs(1))
146 }
147 }
148 })
149}