Skip to content

Commit 47f642a

Browse files
authored
Merge pull request #11 from will-bitlightlabs/feature/block-indexing-optimization
Enhance indexer transaction data analysis and storage
2 parents 0bc0b95 + ec2e560 commit 47f642a

File tree

13 files changed

+2748
-90
lines changed

13 files changed

+2748
-90
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/client.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl ConnectionDelegate<RemoteAddr, Session> for Delegate {
6464
TcpStream::connect(remote).unwrap_or_else(|err| {
6565
#[cfg(feature = "log")]
6666
log::error!("Unable to connect BP Node {remote} due to {err}");
67-
eprintln!("Unable to connect BP Node {remote}");
67+
eprintln!("Unable to connect BP Node {remote} due to {err}");
6868
exit(1);
6969
})
7070
}
@@ -74,9 +74,9 @@ impl ConnectionDelegate<RemoteAddr, Session> for Delegate {
7474
log::info!("connection to the server is established");
7575
}
7676

77-
fn on_disconnect(&mut self, err: Error, _attempt: usize) -> OnDisconnect {
77+
fn on_disconnect(&mut self, _err: Error, _attempt: usize) -> OnDisconnect {
7878
#[cfg(feature = "log")]
79-
log::error!("disconnected due to {err}");
79+
log::error!("disconnected due to {_err}");
8080
OnDisconnect::Terminate
8181
}
8282

client/src/exporter.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,27 @@ impl ConnectionDelegate<RemoteAddr, Session> for BlockExporter {
5757

5858
fn connect(&mut self, remote: &RemoteAddr) -> Session {
5959
TcpStream::connect(remote).unwrap_or_else(|err| {
60+
#[cfg(feature = "log")]
6061
log::error!(target: NAME, "Unable to connect BP Node {remote} due to {err}");
62+
#[cfg(feature = "log")]
6163
log::warn!(target: NAME, "Stopping RPC import thread");
6264
exit(1);
6365
})
6466
}
6567

6668
fn on_established(&mut self, remote: SocketAddr, _attempt: usize) {
69+
#[cfg(feature = "log")]
6770
log::info!(target: NAME, "Connected to BP Node {remote}, sending `hello(...)`");
6871
}
6972

7073
fn on_disconnect(&mut self, err: std::io::Error, _attempt: usize) -> OnDisconnect {
74+
#[cfg(feature = "log")]
7175
log::error!(target: NAME, "BP Node got disconnected due to {err}");
7276
exit(1)
7377
}
7478

7579
fn on_io_error(&mut self, err: reactor::Error<ImpossibleResource, NetTransport<Session>>) {
80+
#[cfg(feature = "log")]
7681
log::error!(target: NAME, "I/O error in communicating with BP Node: {err}");
7782
self.disconnect();
7883
}
@@ -85,21 +90,25 @@ impl ClientDelegate<RemoteAddr, Session> for BlockExporter {
8590
match msg {
8691
ImporterReply::Filters(filters) => {
8792
if self.filters_received {
93+
#[cfg(feature = "log")]
8894
log::warn!(target: NAME, "Received duplicate filters");
8995
} else {
96+
#[cfg(feature = "log")]
9097
log::info!(target: NAME, "Received filters");
9198
}
9299
self.filters = filters;
93100
self.filters_received = true;
94101
}
95102
ImporterReply::Error(failure) => {
103+
#[cfg(feature = "log")]
96104
log::error!(target: NAME, "Received error from BP Node: {failure}");
97105
self.disconnect();
98106
}
99107
}
100108
}
101109

102110
fn on_reply_unparsable(&mut self, err: <Self::Reply as Frame>::Error) {
111+
#[cfg(feature = "log")]
103112
log::error!("Invalid message from BP Node: {err}");
104113
}
105114
}

client/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ fn cb(reply: Response) {
5656
Response::Failure(failure) => {
5757
println!("Failure: {failure}");
5858
}
59-
Response::Pong(_noise) => {}
59+
Response::Pong(_noise) => {
60+
println!("Pong from server");
61+
}
6062
Response::Status(status) => {
6163
println!("{}", serde_yaml::to_string(&status).unwrap());
6264
}

providers/bitcoincore/src/main.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ use strict_encoding::Ident;
3939

4040
pub const AGENT: &str = "BC_BP";
4141

42-
pub const BLOCK_SEPARATOR: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9];
42+
pub const BITCOIN_BLOCK_SEPARATOR: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9];
43+
pub const TESTNET_BLOCK_SEPARATOR: [u8; 4] = [0x0B, 0x11, 0x09, 0x07];
44+
pub const TESTNET4_BLOCK_SEPARATOR: [u8; 4] = [0x1c, 0x16, 0x3f, 0x28];
45+
pub const SIGNET_BLOCK_SEPARATOR: [u8; 4] = [0x0A, 0x03, 0xCF, 0x40];
46+
pub const REGTEST_BLOCK_SEPARATOR: [u8; 4] = [0xFA, 0xBF, 0xB5, 0xDA];
4347

4448
/// Command-line arguments
4549
#[derive(Parser)]
@@ -104,6 +108,15 @@ fn read_blocks(client: Client<ExporterPub>, args: Args) {
104108
exit(1);
105109
}
106110

111+
// Select the correct block separator according to the network type
112+
let block_separator = match args.network {
113+
Network::Mainnet => BITCOIN_BLOCK_SEPARATOR,
114+
Network::Testnet3 => TESTNET_BLOCK_SEPARATOR,
115+
Network::Testnet4 => TESTNET4_BLOCK_SEPARATOR,
116+
Network::Signet => SIGNET_BLOCK_SEPARATOR,
117+
Network::Regtest => REGTEST_BLOCK_SEPARATOR,
118+
};
119+
107120
let mut file_no: u32 = 0;
108121
let mut total_blocks: u32 = 0;
109122
let mut total_tx: u64 = 0;
@@ -138,7 +151,13 @@ fn read_blocks(client: Client<ExporterPub>, args: Args) {
138151
exit(4);
139152
}
140153
}
141-
if buf != BLOCK_SEPARATOR {
154+
155+
if buf == [0x00, 0x00, 0x00, 0x00] {
156+
log::info!("Reached end of block file");
157+
break;
158+
}
159+
160+
if buf != block_separator {
142161
log::error!(
143162
"Invalid block separator 0x{:02X}{:02X}{:02X}{:02X} before block #{block_no}",
144163
buf[0],

src/bin/bpd.rs

Lines changed: 130 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,30 @@ extern crate clap;
2727
mod opts;
2828

2929
use std::fs;
30+
use std::path::Path;
3031
use std::process::{ExitCode, Termination, exit};
3132

3233
pub use bpnode;
33-
use bpnode::{Broker, BrokerError, Config, PATH_INDEXDB};
34+
use bpnode::{Broker, BrokerError, Config, PATH_INDEXDB, initialize_db_tables};
35+
use bpwallet::Network;
3436
use clap::Parser;
3537
use loglevel::LogLevel;
3638
use redb::Database;
3739

3840
use crate::opts::{Command, Opts};
3941

42+
// Exit status codes for different error conditions
43+
// see also constants in `db.rs`
44+
const EXIT_PATH_ACCESS_ERROR: i32 = 1;
45+
const EXIT_DB_EXISTS_ERROR: i32 = 2;
46+
const EXIT_DIR_CREATE_ERROR: i32 = 3;
47+
const EXIT_DB_CREATE_ERROR: i32 = 4;
48+
const EXIT_DB_OPEN_ERROR: i32 = 5;
49+
const EXIT_NETWORK_MISMATCH: i32 = 10;
50+
const EXIT_NO_NETWORK_INFO: i32 = 11;
51+
const EXIT_DB_NOT_FOUND: i32 = 12;
52+
53+
/// Wrapper for result status to implement Termination trait
4054
struct Status(Result<(), BrokerError>);
4155

4256
impl Termination for Status {
@@ -58,37 +72,124 @@ fn main() -> Status {
5872
log::debug!("Command-line arguments: {:#?}", &opts);
5973

6074
match opts.command {
61-
Some(Command::Init) => {
62-
eprint!("Initializing ... ");
63-
let index_path = opts.general.data_dir.join(PATH_INDEXDB);
64-
match fs::exists(&index_path) {
65-
Err(err) => {
66-
eprintln!("unable to access path '{}': {err}", index_path.display());
67-
exit(1);
68-
}
69-
Ok(true) => {
70-
eprintln!("index database directory already exists, cancelling");
71-
exit(2);
72-
}
73-
Ok(false) => {}
74-
}
75-
if let Err(err) = fs::create_dir_all(&opts.general.data_dir) {
75+
Some(Command::Init) => initialize_database(&opts),
76+
None => run_node(opts),
77+
}
78+
}
79+
80+
/// Initialize a new database for the BP Node
81+
fn initialize_database(opts: &Opts) -> Status {
82+
eprint!("Initializing ... ");
83+
84+
// Prepare the database path
85+
let index_path = opts.general.data_dir.join(PATH_INDEXDB);
86+
87+
// Check if database already exists
88+
if let Err(err) = check_db_path(&index_path, false) {
89+
return err;
90+
}
91+
92+
// Create data directory if needed
93+
if let Err(err) = fs::create_dir_all(&opts.general.data_dir) {
94+
eprintln!(
95+
"Unable to create data directory at '{}'\n{err}",
96+
opts.general.data_dir.display()
97+
);
98+
exit(EXIT_DIR_CREATE_ERROR);
99+
}
100+
101+
// Create the database
102+
let db = match Database::create(&index_path) {
103+
Ok(db) => db,
104+
Err(err) => {
105+
eprintln!("Unable to create index database.\n{err}");
106+
exit(EXIT_DB_CREATE_ERROR);
107+
}
108+
};
109+
110+
// Initialize database with network information and create all tables
111+
let network = opts.general.network;
112+
initialize_db_tables(&db, network);
113+
114+
eprintln!("Index database initialized for {} network, exiting", network);
115+
Status(Ok(()))
116+
}
117+
118+
/// Run the BP Node service
119+
fn run_node(opts: Opts) -> Status {
120+
let conf = Config::from(opts);
121+
let index_path = conf.data_dir.join(PATH_INDEXDB);
122+
123+
// Check if database exists
124+
if let Err(err) = check_db_path(&index_path, true) {
125+
return err;
126+
}
127+
128+
// Verify network configuration
129+
verify_network_configuration(&index_path, &conf.network);
130+
131+
// Start the broker service
132+
Status(Broker::start(conf).and_then(|runtime| runtime.run()))
133+
}
134+
135+
/// Check if database path exists or not, depending on expected state
136+
fn check_db_path(index_path: &Path, should_exist: bool) -> Result<(), Status> {
137+
match fs::exists(index_path) {
138+
Err(err) => {
139+
eprintln!("Unable to access path '{}': {err}", index_path.display());
140+
exit(EXIT_PATH_ACCESS_ERROR);
141+
}
142+
Ok(exists) => {
143+
if exists && !should_exist {
144+
eprintln!("Index database directory already exists, cancelling");
145+
exit(EXIT_DB_EXISTS_ERROR);
146+
} else if !exists && should_exist {
76147
eprintln!(
77-
"unable to create data directory at '{}'\n{err}",
78-
opts.general.data_dir.display()
148+
"ERROR: Database not found! Please initialize with 'bpd init' command first."
79149
);
80-
exit(3);
150+
exit(EXIT_DB_NOT_FOUND);
81151
}
82-
if let Err(err) = Database::create(&index_path) {
83-
eprintln!("unable to create index database.\n{err}");
84-
exit(4);
85-
}
86-
eprintln!("index database initialized, exiting");
87-
Status(Ok(()))
88-
}
89-
None => {
90-
let conf = Config::from(opts);
91-
Status(Broker::start(conf).and_then(|runtime| runtime.run()))
92152
}
93153
}
154+
Ok(())
155+
}
156+
157+
/// Verify that database network configuration matches the configured network
158+
fn verify_network_configuration(index_path: &Path, configured_network: &Network) {
159+
let Ok(db) = Database::open(index_path)
160+
.inspect_err(|err| eprintln!("Error: could not open the database due to {err}"))
161+
else {
162+
exit(EXIT_DB_OPEN_ERROR)
163+
};
164+
let Ok(tx) = db
165+
.begin_read()
166+
.inspect_err(|err| eprintln!("Error: could not access the database due to {err}"))
167+
else {
168+
exit(EXIT_DB_OPEN_ERROR)
169+
};
170+
let Ok(main_table) = tx
171+
.open_table(bpnode::db::TABLE_MAIN)
172+
.inspect_err(|err| eprintln!("Error: could not open the main table due to {err}"))
173+
else {
174+
exit(EXIT_DB_OPEN_ERROR)
175+
};
176+
let Ok(Some(network_rec)) = main_table.get(bpnode::REC_NETWORK) else {
177+
// Network information isn't found in the database
178+
eprintln!("ERROR: Database exists but doesn't contain network information.");
179+
eprintln!("Please reinitialize the database with `bpd init` command.");
180+
exit(EXIT_NO_NETWORK_INFO);
181+
};
182+
let stored_network = String::from_utf8_lossy(network_rec.value());
183+
if stored_network != configured_network.to_string() {
184+
eprintln!("ERROR: Database network mismatch!");
185+
eprintln!("Configured network: {}", configured_network);
186+
eprintln!("Database network: {}", stored_network);
187+
eprintln!("Each BP-Node instance works with a single chain.");
188+
eprintln!(
189+
"To use a different network, create a separate instance with a different data \
190+
directory."
191+
);
192+
exit(EXIT_NETWORK_MISMATCH);
193+
}
194+
log::info!("Database network matches configured network: {}", stored_network);
94195
}

0 commit comments

Comments
 (0)