Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindex-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ readme = "../README.md"


[dependencies]
bindex = { path = "../bindex-lib" }
bindex = { path = "../bindex-lib", features = ["cache"] }
chrono = { version = "0.4", default-features = false }
clap = { version = "4", features = ["derive"] }
env_logger = "0.11"
Expand Down
86 changes: 8 additions & 78 deletions bindex-cli/src/bin/bindex-cli.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use bindex::{
bitcoin::{self, consensus::deserialize, hashes::Hash, BlockHash, Txid},
bitcoin::{self, consensus::deserialize, hashes::Hash, Txid},
cache, IndexedChain, Network,
};
use chrono::{TimeZone, Utc};
use clap::Parser;
use log::*;
use std::{
collections::HashSet,
io::{BufRead, BufReader, Read, Write},
io::Read,
path::{Path, PathBuf},
process::{ChildStdin, ChildStdout, Command, Stdio},
str::FromStr,
time::Instant,
};
Expand Down Expand Up @@ -177,10 +176,6 @@ struct Args {
/// Exit after one sync is over
#[arg(short = '1', long = "sync-once", default_value_t = false)]
sync_once: bool,

/// Start Electrum server
#[arg(short = 'e', long = "electrum", default_value_t = false)]
electrum: bool,
}

fn collect_addresses(args: &Args) -> Result<HashSet<bitcoin::Address>> {
Expand All @@ -204,50 +199,6 @@ fn collect_addresses(args: &Args) -> Result<HashSet<bitcoin::Address>> {
Ok(addresses)
}

struct Electrum {
stdin: ChildStdin,
stdout: BufReader<ChildStdout>,
line: String,
}

impl Electrum {
fn start(cache_file: &Path, network: bitcoin::Network) -> Result<Self> {
let mut server = Command::new("python")
.arg("-m")
.arg("electrum.server")
.arg("--cache-db")
.arg(cache_file)
.arg("--network")
.arg(network.to_string())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
info!("Launched server @ pid={}", server.id());

let stdin = server.stdin.take().unwrap();
let stdout = BufReader::new(server.stdout.take().unwrap());

Ok(Self {
stdin,
stdout,
line: String::new(),
})
}

fn notify(&mut self, new_tip: BlockHash) -> Result<()> {
debug!("chain best block={}", new_tip);
writeln!(self.stdin, "{new_tip}")?;
self.stdin.flush()?;
Ok(())
}

fn wait(&mut self) -> Result<()> {
self.line.clear();
self.stdout.read_line(&mut self.line)?; // wait for notification
Ok(())
}
}

fn run() -> Result<()> {
let args = Args::parse();
env_logger::builder().format_timestamp_micros().init();
Expand All @@ -259,37 +210,16 @@ fn run() -> Result<()> {
let cache = cache::Cache::open(cache_db)?;
cache.add(collect_addresses(&args)?)?;

let mut server = None;
if args.electrum {
let cache_file = args
.cache_file
.ok_or("Electrum requires setting a cache file")?;
server = Some(Electrum::start(&cache_file, args.network.into())?);
}
let mut index = IndexedChain::open_default(&args.db_path, args.network)?;
let mut index = IndexedChain::open(&args.db_path, args.network)?;
loop {
// index new blocks (also handle reorgs)
let tip = loop {
let stats = index.sync_chain(1000)?;
if stats.indexed_blocks == 0 {
break stats.tip;
}
};
while index.sync_chain(1000)?.indexed_blocks > 0 {}
// make sure to update new scripthashes (even if there are no new blocks)
cache.sync(&index)?;
let entries = get_history(cache.db())?;
match server.as_mut() {
Some(s) => {
s.notify(tip)?;
s.wait()?; // Electrum should send an ACK for an index sync request
}
None => {
print_history(entries, args.history_limit);
std::thread::sleep(std::time::Duration::from_secs(1));
if args.sync_once {
break;
}
}
print_history(get_history(cache.db())?, args.history_limit);
std::thread::sleep(std::time::Duration::from_secs(1));
if args.sync_once {
break;
}
}
Ok(())
Expand Down
4 changes: 3 additions & 1 deletion bindex-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ keywords = ["bitcoin", "index", "database"]
documentation = "https://docs.rs/bindex/"
readme = "../README.md"

[features]
cache = ["dep:rusqlite"]

[dependencies]
bitcoin = { version = "0.32", default-features = false }
Expand All @@ -21,7 +23,7 @@ log = "0.4"
rocksdb = { version = "0.23", default-features = false, features = ["zstd"]}
thiserror = "2.0"
ureq = { version = "3", default-features = false }
rusqlite = "0.37"
rusqlite = { version = "0.37", optional = true }

[dev-dependencies]
hex_lit = "0.1"
17 changes: 10 additions & 7 deletions bindex-lib/src/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use log::*;
use rusqlite::OptionalExtension;

use crate::{
chain::{self, IndexedChain},
index::{self, ScriptHash},
store::{self, IndexedChain},
Location,
};

Expand All @@ -23,8 +23,8 @@ pub enum Error {
#[error("invalid address: {0}")]
Address(#[from] bitcoin::address::ParseError),

#[error("store error: {0}")]
Index(#[from] store::Error),
#[error("chain error: {0}")]
Index(#[from] chain::Error),
}

pub struct Cache {
Expand Down Expand Up @@ -116,9 +116,10 @@ impl Cache {
})?;
let mut delete_from = None;
// Find the first non-stale block (scanning backwards from tip):
let headers = chain.headers();
for row in rows_iter {
let (hash, height) = row?;
match chain.check_header(hash, height) {
match headers.get_header(hash, height) {
Ok(_header) => break,
Err(err) => {
warn!("reorg detected: {}", err);
Expand Down Expand Up @@ -155,14 +156,16 @@ impl Cache {
SELECT script_hash, block_height, block_hash
FROM max_heights LEFT JOIN headers USING (block_height)",
)?;
let headers = chain.headers();
let rows_iter = stmt.query_map([], |row| {
let script_hash = ScriptHash::from_byte_array(row.get(0)?);
let block_height: Option<usize> = row.get(1)?;
let latest_header = if let Some(height) = block_height {
let block_hash = bitcoin::BlockHash::from_byte_array(row.get(2)?);
let header = chain
.check_header(block_hash, height)
.expect("unexpected reorg");
// Stale blocks should be removed by `drop_stale_blocks` call above.
let header = headers
.get_header(block_hash, height)
.expect("unexpected stale block");
Some(header)
} else {
None
Expand Down
34 changes: 7 additions & 27 deletions bindex-lib/src/store.rs → bindex-lib/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,14 @@ pub enum Error {
#[error("Genesis block hash mismatch: {0} != {1}")]
ChainMismatch(bitcoin::BlockHash, bitcoin::BlockHash),

#[error("rusqlite failed: {0}")]
Sqlite(#[from] rusqlite::Error),

#[error("invalid address: {0}")]
Address(#[from] bitcoin::address::ParseError),

#[error("block not found: {0}")]
BlockNotFound(#[from] headers::Reorg),
}

#[derive(Debug)]
pub struct Stats {
pub tip: bitcoin::BlockHash,
pub indexed_blocks: usize,
Expand Down Expand Up @@ -61,9 +59,10 @@ pub struct IndexedChain {
impl IndexedChain {
/// Open an existing DB, or create if missing.
/// Use binary format REST API for fetching the data from bitcoind.
pub fn open(db_path: impl AsRef<Path>, url: impl Into<String>) -> Result<Self, Error> {
let db_path = db_path.as_ref();
let url = url.into();
pub fn open(db_path: impl AsRef<Path>, network: Network) -> Result<Self, Error> {
let db_path = db_path.as_ref().to_path_buf().join(network.to_string());
let url = format!("http://localhost:{}", network.default_rpc_port());

info!("index DB: {:?}, node URL: {}", db_path, url);
let agent = ureq::Agent::new_with_config(
ureq::config::Config::builder()
Expand Down Expand Up @@ -106,13 +105,6 @@ impl IndexedChain {
})
}

pub fn open_default(db_path: &str, network: Network) -> Result<Self, Error> {
let bitcoin_network: bitcoin::Network = network.into();
let default_db_path = format!("{db_path}/{bitcoin_network}");
let default_rest_url = format!("http://localhost:{}", network.default_rpc_port());
Self::open(default_db_path, default_rest_url)
}

fn drop_tip(&mut self) -> Result<bitcoin::BlockHash, Error> {
let stale = self
.headers
Expand Down Expand Up @@ -243,19 +235,7 @@ impl IndexedChain {
.get_block_part(location.indexed_header.hash(), pos)?)
}

/// Iterate over block headers.
pub fn iter_headers(&self) -> impl Iterator<Item = &bitcoin::block::Header> {
self.headers
.iter_headers()
.map(index::IndexedHeader::header)
}

/// Make sure this header has been indexed.
pub fn check_header(
&self,
hash: bitcoin::BlockHash,
height: usize,
) -> Result<&index::IndexedHeader, Error> {
Ok(self.headers.get_header(hash, height)?)
pub fn headers(&self) -> &headers::Headers {
&self.headers
}
}
17 changes: 15 additions & 2 deletions bindex-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
pub use bitcoin;

#[cfg(feature = "cache")]
pub mod cache;

mod chain;
mod client;
mod db;
mod headers;
mod index;
mod network;
mod store;

pub use chain::IndexedChain;
pub use headers::Headers;
pub use index::ScriptHash;
pub use network::Network;
pub use store::IndexedChain;

#[derive(PartialEq, Eq, PartialOrd, Clone, Copy, Debug)]
pub struct Location<'a> {
Expand All @@ -20,6 +23,16 @@ pub struct Location<'a> {
indexed_header: &'a index::IndexedHeader,
}

impl Location<'_> {
pub fn block_hash(&self) -> bitcoin::BlockHash {
self.indexed_header.hash()
}

pub fn block_height(&self) -> usize {
self.block_height
}
}

impl Ord for Location<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.txnum.cmp(&other.txnum)
Expand Down
18 changes: 18 additions & 0 deletions bindex-lib/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ impl From<Network> for bitcoin::Network {
}
}

impl From<bitcoin::Network> for Network {
fn from(value: bitcoin::Network) -> Self {
match value {
bitcoin::Network::Bitcoin => Network::Bitcoin,
bitcoin::Network::Testnet => Network::Testnet,
bitcoin::Network::Testnet4 => Network::Testnet4,
bitcoin::Network::Signet => Network::Signet,
bitcoin::Network::Regtest => Network::Regtest,
}
}
}

impl Network {
pub fn default_rpc_port(&self) -> u16 {
match self {
Expand All @@ -32,3 +44,9 @@ impl Network {
}
}
}

impl std::fmt::Display for Network {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
bitcoin::Network::from(*self).fmt(f)
}
}
5 changes: 0 additions & 5 deletions electrum.sh

This file was deleted.

25 changes: 0 additions & 25 deletions electrum/LICENCE

This file was deleted.

Empty file removed electrum/__init__.py
Empty file.
Loading