mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-17 10:19:44 -07:00
init: separate parser crate
This commit is contained in:
Generated
+1094
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "barser"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
biter = "0.2.2"
|
||||
color-eyre = "0.6.3"
|
||||
ctrlc = "3.4.5"
|
||||
derive_deref = "1.1.1"
|
||||
fjall = "2.4.4"
|
||||
rayon = "1.10.0"
|
||||
@@ -0,0 +1,35 @@
|
||||
use biter::bitcoin::BlockHash;
|
||||
use color_eyre::eyre::eyre;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use crate::structs::{Database, DatabaseTrait, Height, Version};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct BlockhashPrefixToHeight(Database);
|
||||
|
||||
impl BlockhashPrefixToHeight {
|
||||
pub fn import() -> color_eyre::Result<Self> {
|
||||
Ok(Self(Database::import(
|
||||
"blockhash_suffix_to_height",
|
||||
Self::version(),
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, blockhash: &BlockHash, height: Height) -> color_eyre::Result<()> {
|
||||
if let Some(_height) = self.fetch_update(blockhash[..8].into(), height.into(), height)? {
|
||||
// dbg!(height, Height::from(other), hash);
|
||||
return Err(eyre!("BlockhashSuffixToHeight: key collision"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, blockhash: &BlockHash) {
|
||||
self.0.remove((&blockhash[..]).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseTrait for BlockhashPrefixToHeight {
|
||||
fn version() -> Version {
|
||||
Version::from(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use biter::bitcoin::{hashes::Hash, BlockHash};
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use fjall::Slice;
|
||||
|
||||
use crate::structs::{Database, DatabaseTrait, Height, Version};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct HeightToBlockhash(Database);
|
||||
|
||||
impl HeightToBlockhash {
|
||||
pub fn import() -> color_eyre::Result<Self> {
|
||||
Ok(Self(Database::import(
|
||||
"height_to_blockhash",
|
||||
Self::version(),
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, height: Height, blockhash: &BlockHash) {
|
||||
self.0.insert(height.into(), blockhash[..].into(), height)
|
||||
}
|
||||
|
||||
pub fn get(&self, height: Height) -> fjall::Result<Option<BlockHash>> {
|
||||
self.0
|
||||
.get(Slice::from(height))
|
||||
.map(|opt| opt.map(|slice| BlockHash::from_slice(&slice).unwrap()))
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, height: Height) {
|
||||
self.0.remove(Slice::from(height))
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseTrait for HeightToBlockhash {
|
||||
fn version() -> Version {
|
||||
Version::from(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use fjall::Slice;
|
||||
|
||||
use crate::structs::{Database, DatabaseTrait, Height, Txindex, Version};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct HeightToTxindex(Database);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum HeightToTxindexPosition {
|
||||
First,
|
||||
Last,
|
||||
}
|
||||
|
||||
impl HeightToTxindex {
|
||||
pub fn import(position: HeightToTxindexPosition) -> color_eyre::Result<Self> {
|
||||
Ok(Self(Database::import(
|
||||
&format!(
|
||||
"height_to_{}_txindex",
|
||||
format!("{position:?}").to_lowercase()
|
||||
),
|
||||
Self::version(),
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, height: Height, txindex: Txindex) {
|
||||
self.0.insert(height.into(), txindex.into(), height)
|
||||
}
|
||||
|
||||
pub fn get(&self, height: Height) -> fjall::Result<Option<Txindex>> {
|
||||
self.0
|
||||
.get(Slice::from(height))
|
||||
.map(|opt| opt.map(|slice| slice.into()))
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, height: Height) {
|
||||
self.0.remove(Slice::from(height))
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseTrait for HeightToTxindex {
|
||||
fn version() -> Version {
|
||||
Version::from(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
use std::{ops::Sub, thread};
|
||||
|
||||
use biter::{
|
||||
bitcoin::{hashes::Hash, BlockHash, Txid},
|
||||
bitcoincore_rpc::Client,
|
||||
};
|
||||
pub use blockhash_prefix_to_height::*;
|
||||
use color_eyre::eyre::ContextCompat;
|
||||
use fjall::Slice;
|
||||
|
||||
mod blockhash_prefix_to_height;
|
||||
mod height_to_blockhash;
|
||||
mod height_to_txindex;
|
||||
mod txid_prefix_to_txindex;
|
||||
mod txindex_to_txid;
|
||||
mod txoutindex_to_amount;
|
||||
|
||||
pub use height_to_blockhash::*;
|
||||
pub use height_to_txindex::*;
|
||||
pub use txid_prefix_to_txindex::*;
|
||||
pub use txindex_to_txid::*;
|
||||
pub use txoutindex_to_amount::*;
|
||||
|
||||
use crate::structs::{Height, Txindex, Txoutindex};
|
||||
|
||||
pub struct Databases {
|
||||
pub blockhash_prefix_to_height: BlockhashPrefixToHeight,
|
||||
pub height_to_blockhash: HeightToBlockhash,
|
||||
pub height_to_first_txindex: HeightToTxindex,
|
||||
pub height_to_last_txindex: HeightToTxindex,
|
||||
pub txid_prefix_to_txindex: TxidPrefixToTxindex,
|
||||
pub txindex_to_txid: TxindexToTxid,
|
||||
pub txoutindex_to_amount: TxoutindexToAmount,
|
||||
}
|
||||
|
||||
const UNSAFE_BLOCKS: usize = 100;
|
||||
|
||||
impl Databases {
|
||||
pub fn import() -> color_eyre::Result<Self> {
|
||||
thread::scope(|scope| {
|
||||
let blockhash_prefix_to_height_handle = scope.spawn(BlockhashPrefixToHeight::import);
|
||||
let height_to_blockhash_handle = scope.spawn(HeightToBlockhash::import);
|
||||
let height_to_first_txindex_handle =
|
||||
scope.spawn(|| HeightToTxindex::import(HeightToTxindexPosition::First));
|
||||
let height_to_last_txindex_handle =
|
||||
scope.spawn(|| HeightToTxindex::import(HeightToTxindexPosition::Last));
|
||||
let txid_prefix_to_txindex_handle = scope.spawn(TxidPrefixToTxindex::import);
|
||||
let txindex_to_txid_handle = scope.spawn(TxindexToTxid::import);
|
||||
let txoutindex_to_amount_handle = scope.spawn(TxoutindexToAmount::import);
|
||||
|
||||
Ok(Self {
|
||||
blockhash_prefix_to_height: blockhash_prefix_to_height_handle.join().unwrap()?,
|
||||
height_to_blockhash: height_to_blockhash_handle.join().unwrap()?,
|
||||
height_to_first_txindex: height_to_first_txindex_handle.join().unwrap()?,
|
||||
height_to_last_txindex: height_to_last_txindex_handle.join().unwrap()?,
|
||||
txid_prefix_to_txindex: txid_prefix_to_txindex_handle.join().unwrap()?,
|
||||
txindex_to_txid: txindex_to_txid_handle.join().unwrap()?,
|
||||
txoutindex_to_amount: txoutindex_to_amount_handle.join().unwrap()?,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn export(&mut self, height: Height) -> color_eyre::Result<()> {
|
||||
thread::scope(|scope| {
|
||||
scope.spawn(|| self.blockhash_prefix_to_height.export(height).unwrap());
|
||||
scope.spawn(|| self.height_to_blockhash.export(height).unwrap());
|
||||
scope.spawn(|| self.height_to_first_txindex.export(height).unwrap());
|
||||
scope.spawn(|| self.height_to_last_txindex.export(height).unwrap());
|
||||
scope.spawn(|| self.txid_prefix_to_txindex.export(height).unwrap());
|
||||
scope.spawn(|| self.txindex_to_txid.export(height).unwrap());
|
||||
scope.spawn(|| self.txoutindex_to_amount.export(height).unwrap());
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_height(&self, rpc: &Client) -> color_eyre::Result<Height> {
|
||||
let safe_height = Height::try_from(rpc)?.sub(UNSAFE_BLOCKS);
|
||||
Ok(self
|
||||
.min_height()
|
||||
.map(|h| h.sub(UNSAFE_BLOCKS))
|
||||
.unwrap_or_default()
|
||||
.min(safe_height))
|
||||
}
|
||||
|
||||
fn min_height(&self) -> Option<Height> {
|
||||
[
|
||||
self.blockhash_prefix_to_height.height(),
|
||||
self.height_to_blockhash.height(),
|
||||
self.height_to_first_txindex.height(),
|
||||
self.height_to_last_txindex.height(),
|
||||
self.txid_prefix_to_txindex.height(),
|
||||
self.txindex_to_txid.height(),
|
||||
self.txoutindex_to_amount.height(),
|
||||
]
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.min()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn has_different_blockhash(
|
||||
&self,
|
||||
height: Height,
|
||||
blockhash: &BlockHash,
|
||||
) -> fjall::Result<bool> {
|
||||
Ok(self
|
||||
.height_to_blockhash
|
||||
.get(height)?
|
||||
.is_some_and(|saved_blockhash| blockhash != &saved_blockhash))
|
||||
}
|
||||
|
||||
pub fn erase_from(&mut self, height: Height) -> color_eyre::Result<()> {
|
||||
let mut txindex = None;
|
||||
|
||||
self.height_to_blockhash
|
||||
.range(Slice::from(height)..)
|
||||
.try_for_each(|slice| -> color_eyre::Result<()> {
|
||||
let (slice_height, slice_blockhash) = slice?;
|
||||
let height = Height::from(slice_height);
|
||||
let blockhash = BlockHash::from_slice(&slice_blockhash)?;
|
||||
|
||||
self.height_to_blockhash.remove(height);
|
||||
self.blockhash_prefix_to_height.remove(&blockhash);
|
||||
if txindex.is_none() {
|
||||
txindex.replace(
|
||||
self.height_to_first_txindex
|
||||
.get(height)?
|
||||
.context("for height to have first txindex")?,
|
||||
);
|
||||
}
|
||||
self.height_to_first_txindex.remove(height);
|
||||
self.height_to_last_txindex.remove(height);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let txindex = txindex.context("txindex to not be none by now")?;
|
||||
|
||||
self.txindex_to_txid
|
||||
.range(Slice::from(txindex)..)
|
||||
.try_for_each(|slice| -> color_eyre::Result<()> {
|
||||
let (slice_txindex, slice_txid) = slice?;
|
||||
let txindex = Txindex::from(slice_txindex);
|
||||
let txid = Txid::from_slice(&slice_txid)?;
|
||||
|
||||
self.txindex_to_txid.remove(txindex);
|
||||
self.txid_prefix_to_txindex.remove(&txid);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let txoutindex = Txoutindex::from(txindex);
|
||||
|
||||
self.txoutindex_to_amount
|
||||
.range(Slice::from(txoutindex)..)
|
||||
.try_for_each(|slice| -> color_eyre::Result<()> {
|
||||
let (slice_txoutindex, _) = slice?;
|
||||
let txoutindex = Txoutindex::from(slice_txoutindex);
|
||||
|
||||
self.txoutindex_to_amount.remove(txoutindex);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use biter::bitcoin::Txid;
|
||||
use color_eyre::eyre::eyre;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use fjall::Slice;
|
||||
|
||||
use crate::structs::{Database, DatabaseTrait, Height, Txindex, Version};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct TxidPrefixToTxindex(Database);
|
||||
|
||||
impl TxidPrefixToTxindex {
|
||||
pub fn import() -> color_eyre::Result<Self> {
|
||||
Ok(Self(Database::import(
|
||||
"txid_prefix_to_txindex",
|
||||
Self::version(),
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
txid: &Txid,
|
||||
txindex: Txindex,
|
||||
height: Height,
|
||||
) -> color_eyre::Result<()> {
|
||||
if let Some(_txindex) =
|
||||
self.fetch_update(Self::txid_to_key(txid), txindex.into(), height)?
|
||||
{
|
||||
return Err(eyre!("TxidPrefixToTxindex: key collision"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, txid: &Txid) {
|
||||
self.0.remove(Self::txid_to_key(txid))
|
||||
}
|
||||
|
||||
fn txid_to_key(txid: &Txid) -> Slice {
|
||||
txid[0..8].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseTrait for TxidPrefixToTxindex {
|
||||
fn version() -> Version {
|
||||
Version::from(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use biter::bitcoin::Txid;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use fjall::Slice;
|
||||
|
||||
use crate::structs::{Database, DatabaseTrait, Height, Txindex, Version};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct TxindexToTxid(Database);
|
||||
|
||||
impl TxindexToTxid {
|
||||
pub fn import() -> color_eyre::Result<Self> {
|
||||
Ok(Self(Database::import("txindex_to_txid", Self::version())?))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, txindex: Txindex, txid: &Txid, height: Height) {
|
||||
self.0.insert(txindex.into(), txid[..].into(), height)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, txindex: Txindex) {
|
||||
self.0.remove(Slice::from(txindex))
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseTrait for TxindexToTxid {
|
||||
fn version() -> Version {
|
||||
Version::from(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use fjall::Slice;
|
||||
|
||||
use crate::structs::{Amount, Database, DatabaseTrait, Height, Txoutindex, Version};
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct TxoutindexToAmount(Database);
|
||||
|
||||
impl TxoutindexToAmount {
|
||||
pub fn import() -> color_eyre::Result<Self> {
|
||||
Ok(Self(Database::import(
|
||||
"txoutindex_to_amount",
|
||||
Self::version(),
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, txoutindex: Txoutindex, amount: Amount, height: Height) {
|
||||
self.0.insert(txoutindex.into(), amount.into(), height)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, txoutindex: Txoutindex) {
|
||||
self.0.remove(Slice::from(txoutindex))
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseTrait for TxoutindexToAmount {
|
||||
fn version() -> Version {
|
||||
Version::from(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::path::Path;
|
||||
|
||||
use biter::bitcoincore_rpc::{Auth, Client};
|
||||
|
||||
mod databases;
|
||||
mod structs;
|
||||
|
||||
use databases::Databases;
|
||||
use structs::{Exit, Height, Txindex, Txoutindex};
|
||||
|
||||
// https://github.com/fjall-rs/fjall/discussions/72
|
||||
// https://github.com/romanz/electrs/blob/master/doc/schema.md
|
||||
|
||||
const DAILY_BLOCK_TARGET: usize = 144;
|
||||
const MONTHLY_BLOCK_TARGET: usize = DAILY_BLOCK_TARGET * 30;
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
let i = std::time::Instant::now();
|
||||
|
||||
let data_dir = Path::new("../bitcoin");
|
||||
let cookie = Path::new(data_dir).join(".cookie");
|
||||
let rpc = Client::new("http://localhost:8332", Auth::CookieFile(cookie)).unwrap();
|
||||
|
||||
let exit = Exit::new();
|
||||
|
||||
let mut dbs = Databases::import()?;
|
||||
|
||||
let mut height = dbs.start_height(&rpc)?;
|
||||
|
||||
let mut txindex = dbs
|
||||
.height_to_last_txindex
|
||||
.get(height)?
|
||||
.unwrap_or(Txindex::default());
|
||||
|
||||
let export = |dbs: &mut Databases, height: Height| -> color_eyre::Result<()> {
|
||||
exit.block();
|
||||
println!("Exporting...");
|
||||
dbs.export(height)?;
|
||||
println!("Export done");
|
||||
exit.unblock();
|
||||
Ok(())
|
||||
};
|
||||
|
||||
biter::new(data_dir, Some(height.into()), None, rpc)
|
||||
.iter()
|
||||
.try_for_each(|(_height, block, blockhash)| -> color_eyre::Result<()> {
|
||||
println!("Processing block {_height}...");
|
||||
|
||||
height = Height::from(_height);
|
||||
|
||||
if dbs.has_different_blockhash(height, &blockhash)? {
|
||||
dbs.erase_from(height)?;
|
||||
}
|
||||
|
||||
dbs.blockhash_prefix_to_height.insert(&blockhash, height)?;
|
||||
dbs.height_to_blockhash.insert(height, &blockhash);
|
||||
|
||||
let txlen = block.txdata.len();
|
||||
|
||||
block.txdata.into_iter().enumerate().try_for_each(
|
||||
|(i, tx)| -> color_eyre::Result<()> {
|
||||
if i == txlen - 1 {
|
||||
dbs.height_to_last_txindex.insert(height, txindex);
|
||||
}
|
||||
|
||||
if !dbs.txindex_to_txid.is_safe(height)
|
||||
|| !dbs.txid_prefix_to_txindex.is_safe(height)
|
||||
{
|
||||
let txid = tx.compute_txid();
|
||||
dbs.txindex_to_txid.insert(txindex, &txid, height);
|
||||
dbs.txid_prefix_to_txindex.insert(&txid, txindex, height)?;
|
||||
}
|
||||
|
||||
txindex.increment();
|
||||
|
||||
tx.output.into_iter().enumerate().for_each(|(vout, txout)| {
|
||||
let vout = vout as u16;
|
||||
let txoutindex = Txoutindex::from((txindex, vout));
|
||||
let amount = txout.value.into();
|
||||
dbs.txoutindex_to_amount.insert(txoutindex, amount, height);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
let should_snapshot = _height % MONTHLY_BLOCK_TARGET == 0 && !exit.active();
|
||||
if should_snapshot {
|
||||
export(&mut dbs, height)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
export(&mut dbs, height)?;
|
||||
|
||||
dbg!(i.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::{
|
||||
iter::Sum,
|
||||
ops::{Add, AddAssign, Mul, Sub, SubAssign},
|
||||
};
|
||||
|
||||
use biter::bitcoin;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use super::Height;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default)]
|
||||
pub struct Amount(bitcoin::Amount);
|
||||
|
||||
impl Amount {
|
||||
pub const ZERO: Self = Self(bitcoin::Amount::ZERO);
|
||||
pub const ONE_BTC_F32: f32 = 100_000_000.0;
|
||||
pub const ONE_BTC_F64: f64 = 100_000_000.0;
|
||||
}
|
||||
|
||||
impl From<u64> for Amount {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(bitcoin::Amount::from_sat(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::Amount> for Amount {
|
||||
fn from(value: bitcoin::Amount) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Amount> for fjall::Slice {
|
||||
fn from(value: Amount) -> Self {
|
||||
value.to_sat().to_be_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Amount {
|
||||
type Output = Amount;
|
||||
fn add(self, rhs: Amount) -> Self::Output {
|
||||
Amount::from(self.to_sat() + rhs.to_sat())
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Amount {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = Amount::from(self.to_sat() + rhs.to_sat());
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Amount {
|
||||
type Output = Amount;
|
||||
fn sub(self, rhs: Amount) -> Self::Output {
|
||||
Amount::from(self.to_sat() - rhs.to_sat())
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Amount {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = Amount::from(self.to_sat() - rhs.to_sat());
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Amount> for Amount {
|
||||
type Output = Amount;
|
||||
fn mul(self, rhs: Amount) -> Self::Output {
|
||||
Amount::from(self.to_sat() * rhs.to_sat())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<u64> for Amount {
|
||||
type Output = Amount;
|
||||
fn mul(self, rhs: u64) -> Self::Output {
|
||||
Amount::from(self.to_sat() * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Height> for Amount {
|
||||
type Output = Amount;
|
||||
fn mul(self, rhs: Height) -> Self::Output {
|
||||
Amount::from(self.to_sat() * *rhs as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum for Amount {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
let sats: u64 = iter.map(|amt| amt.to_sat()).sum();
|
||||
Amount::from(sats)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
use std::{mem, ops::RangeBounds};
|
||||
|
||||
use fjall::{
|
||||
Batch, Keyspace, KvPair, PartitionCreateOptions, PartitionHandle, PersistMode, Result, Slice,
|
||||
};
|
||||
|
||||
use super::{Height, Version};
|
||||
|
||||
pub struct Database {
|
||||
keyspace: Keyspace,
|
||||
data: PartitionHandle,
|
||||
meta: PartitionHandle,
|
||||
batch: Batch,
|
||||
height: Option<Height>,
|
||||
}
|
||||
|
||||
const VERSION: &str = "version";
|
||||
const HEIGHT: &str = "height";
|
||||
|
||||
impl Database {
|
||||
pub fn import(name: &str, version: Version) -> Result<Self> {
|
||||
let keyspace = fjall::Config::new(format!("./databases/{name}")).open()?;
|
||||
|
||||
let data = Self::open_data(&keyspace)?;
|
||||
let meta = Self::open_meta(&keyspace)?;
|
||||
|
||||
let batch = keyspace.batch();
|
||||
|
||||
let mut this = Self {
|
||||
height: meta.get(HEIGHT)?.map(Height::from),
|
||||
keyspace,
|
||||
data,
|
||||
meta,
|
||||
batch,
|
||||
};
|
||||
|
||||
if let Some(slice) = this.meta.get(VERSION)? {
|
||||
if version != Version::from(slice) {
|
||||
this = this.reset()?;
|
||||
}
|
||||
}
|
||||
|
||||
this.batch
|
||||
.insert(&this.meta, VERSION, version.to_be_bytes());
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
fn open_data(keyspace: &Keyspace) -> Result<PartitionHandle> {
|
||||
keyspace.open_partition("data", Self::create_options())
|
||||
}
|
||||
|
||||
fn open_meta(keyspace: &Keyspace) -> Result<PartitionHandle> {
|
||||
keyspace.open_partition("meta", Self::create_options())
|
||||
}
|
||||
|
||||
fn create_options() -> PartitionCreateOptions {
|
||||
PartitionCreateOptions::default().manual_journal_persist(true)
|
||||
}
|
||||
|
||||
pub fn get(&self, key: Slice) -> Result<Option<Slice>> {
|
||||
self.data.get(key)
|
||||
}
|
||||
|
||||
pub fn range<'a, K: AsRef<[u8]> + 'a, R: RangeBounds<K> + 'a>(
|
||||
&'a self,
|
||||
range: R,
|
||||
) -> impl DoubleEndedIterator<Item = Result<KvPair>> + 'static {
|
||||
self.data.range(range)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: Slice, value: Slice, height: Height) {
|
||||
if self.is_safe(height) {
|
||||
return;
|
||||
}
|
||||
self.batch.insert(&self.data, key, value);
|
||||
}
|
||||
|
||||
pub fn fetch_update(
|
||||
&mut self,
|
||||
key: Slice,
|
||||
value: Slice,
|
||||
height: Height,
|
||||
) -> Result<Option<Slice>> {
|
||||
if self.is_safe(height) {
|
||||
return Ok(None);
|
||||
}
|
||||
let prev = self.get(key.clone());
|
||||
self.batch.insert(&self.data, key, value);
|
||||
prev
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: Slice) {
|
||||
self.batch.remove(&self.data, key);
|
||||
}
|
||||
|
||||
pub fn is_safe(&self, height: Height) -> bool {
|
||||
self.height.is_some_and(|self_height| self_height >= height)
|
||||
}
|
||||
|
||||
fn persist(&self) -> Result<()> {
|
||||
self.keyspace.persist(PersistMode::SyncAll)
|
||||
}
|
||||
|
||||
pub fn export(&mut self, height: Height) -> Result<()> {
|
||||
let mut batch = self.keyspace.batch();
|
||||
mem::swap(&mut batch, &mut self.batch);
|
||||
|
||||
batch.insert(&self.meta, HEIGHT, height.to_be_bytes());
|
||||
|
||||
batch.commit()?;
|
||||
|
||||
self.persist()
|
||||
}
|
||||
|
||||
fn reset(mut self) -> Result<Self> {
|
||||
self.keyspace.delete_partition(self.data)?;
|
||||
self.keyspace.delete_partition(self.meta)?;
|
||||
|
||||
self.keyspace.persist(PersistMode::SyncAll)?;
|
||||
|
||||
self.data = Self::open_data(&self.keyspace)?;
|
||||
self.meta = Self::open_meta(&self.keyspace)?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn height(&self) -> &Option<Height> {
|
||||
&self.height
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DatabaseTrait
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn version() -> Version;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::{
|
||||
process::exit,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Exit {
|
||||
blocked: Arc<AtomicBool>,
|
||||
active: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Exit {
|
||||
pub fn new() -> Self {
|
||||
let s = Self {
|
||||
active: Arc::new(AtomicBool::new(false)),
|
||||
blocked: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
|
||||
let active = s.active.clone();
|
||||
|
||||
let _blocked = s.blocked.clone();
|
||||
let blocked = move || _blocked.load(Ordering::SeqCst);
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
println!("Exitting...");
|
||||
|
||||
active.store(true, Ordering::SeqCst);
|
||||
|
||||
if blocked() {
|
||||
println!("Waiting to exit safely");
|
||||
|
||||
while blocked() {
|
||||
sleep(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn block(&self) {
|
||||
self.blocked.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn unblock(&self) {
|
||||
self.blocked.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn active(&self) -> bool {
|
||||
self.active.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
use std::{
|
||||
fmt,
|
||||
ops::{Add, AddAssign, Sub},
|
||||
};
|
||||
|
||||
use biter::bitcoincore_rpc::{self, RpcApi};
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use super::SliceExtended;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub struct Height(u32);
|
||||
|
||||
impl From<fjall::Slice> for Height {
|
||||
fn from(slice: fjall::Slice) -> Self {
|
||||
Self(slice.read_u32())
|
||||
}
|
||||
}
|
||||
impl From<Height> for fjall::Slice {
|
||||
fn from(value: Height) -> Self {
|
||||
value.to_be_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Height {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Height {
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as u32)
|
||||
}
|
||||
}
|
||||
impl From<Height> for usize {
|
||||
fn from(value: Height) -> Self {
|
||||
value.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&bitcoincore_rpc::Client> for Height {
|
||||
type Error = bitcoincore_rpc::Error;
|
||||
fn try_from(value: &bitcoincore_rpc::Client) -> Result<Self, Self::Error> {
|
||||
Ok((value.get_blockchain_info()?.blocks as usize - 1).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u64> for Height {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
**self == *other as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<u32> for Height {
|
||||
type Output = Height;
|
||||
|
||||
fn add(self, rhs: u32) -> Self::Output {
|
||||
Self::from(*self + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for Height {
|
||||
type Output = Height;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self::from(*self + rhs as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Height> for Height {
|
||||
type Output = Height;
|
||||
|
||||
fn sub(self, rhs: Height) -> Self::Output {
|
||||
Self::from(*self - *rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<i32> for Height {
|
||||
type Output = Height;
|
||||
fn sub(self, rhs: i32) -> Self::Output {
|
||||
Self::from(*self - rhs as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<u32> for Height {
|
||||
type Output = Height;
|
||||
fn sub(self, rhs: u32) -> Self::Output {
|
||||
Self::from(*self - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<usize> for Height {
|
||||
type Output = Height;
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
Self::from(*self - rhs as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<usize> for Height {
|
||||
fn add_assign(&mut self, rhs: usize) {
|
||||
*self = self.add(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Height {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", **self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
mod amount;
|
||||
mod database;
|
||||
mod exit;
|
||||
mod height;
|
||||
mod slice;
|
||||
mod txindex;
|
||||
mod txoutindex;
|
||||
mod version;
|
||||
|
||||
pub use amount::*;
|
||||
pub use database::*;
|
||||
pub use exit::*;
|
||||
pub use height::*;
|
||||
pub use slice::*;
|
||||
pub use txindex::*;
|
||||
pub use txoutindex::*;
|
||||
pub use version::*;
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::io::Read;
|
||||
|
||||
pub trait SliceExtended {
|
||||
fn read_u8(&self) -> u8;
|
||||
fn read_u32(&self) -> u32;
|
||||
fn read_u64(&self) -> u64;
|
||||
fn read_exact(&self, buf: &mut [u8]);
|
||||
}
|
||||
|
||||
impl SliceExtended for fjall::Slice {
|
||||
fn read_u8(&self) -> u8 {
|
||||
let mut buf: [u8; 1] = [0; 1];
|
||||
self.read_exact(&mut buf);
|
||||
u8::from_be_bytes(buf)
|
||||
}
|
||||
|
||||
fn read_u32(&self) -> u32 {
|
||||
let mut buf: [u8; 4] = [0; 4];
|
||||
self.read_exact(&mut buf);
|
||||
u32::from_be_bytes(buf)
|
||||
}
|
||||
|
||||
fn read_u64(&self) -> u64 {
|
||||
let mut buf: [u8; 8] = [0; 8];
|
||||
self.read_exact(&mut buf);
|
||||
u64::from_be_bytes(buf)
|
||||
}
|
||||
|
||||
fn read_exact(&self, buf: &mut [u8]) {
|
||||
self.bytes().take(buf.len()).enumerate().for_each(|(i, r)| {
|
||||
buf[i] = r.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use super::SliceExtended;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deref, DerefMut, Default)]
|
||||
pub struct Txindex(u32);
|
||||
|
||||
impl Txindex {
|
||||
pub fn increment(&mut self) {
|
||||
self.0 += 1;
|
||||
}
|
||||
|
||||
pub fn incremented(self) -> Self {
|
||||
Self(*self + 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Txindex {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Txindex {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(value as u32)
|
||||
}
|
||||
}
|
||||
impl From<Txindex> for u64 {
|
||||
fn from(value: Txindex) -> Self {
|
||||
value.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fjall::Slice> for Txindex {
|
||||
fn from(slice: fjall::Slice) -> Self {
|
||||
Self(slice.read_u32())
|
||||
}
|
||||
}
|
||||
impl From<Txindex> for fjall::Slice {
|
||||
fn from(value: Txindex) -> Self {
|
||||
value.to_be_bytes().into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use super::{SliceExtended, Txindex};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
||||
pub struct Txoutindex {
|
||||
pub txindex: Txindex,
|
||||
pub vout: u16,
|
||||
}
|
||||
|
||||
const SHIFT: u64 = 16;
|
||||
const AND: u64 = (1 << SHIFT) - 1;
|
||||
|
||||
impl From<Txindex> for Txoutindex {
|
||||
fn from(value: Txindex) -> Self {
|
||||
Self {
|
||||
txindex: value,
|
||||
vout: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Txindex, u16)> for Txoutindex {
|
||||
fn from(value: (Txindex, u16)) -> Self {
|
||||
Self {
|
||||
txindex: value.0,
|
||||
vout: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Txoutindex {
|
||||
fn from(value: u64) -> Self {
|
||||
Self {
|
||||
txindex: (value >> SHIFT).into(),
|
||||
vout: (value & AND) as u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Txoutindex> for u64 {
|
||||
fn from(value: Txoutindex) -> Self {
|
||||
(u64::from(value.txindex) << SHIFT) + value.vout as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Txoutindex> for fjall::Slice {
|
||||
fn from(value: Txoutindex) -> Self {
|
||||
u64::from(value).to_be_bytes().into()
|
||||
}
|
||||
}
|
||||
impl From<fjall::Slice> for Txoutindex {
|
||||
fn from(value: fjall::Slice) -> Self {
|
||||
fjall::Slice::read_u64(&value).into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
|
||||
use super::SliceExtended;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Version(u8);
|
||||
|
||||
impl From<u8> for Version {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fjall::Slice> for Version {
|
||||
fn from(slice: fjall::Slice) -> Self {
|
||||
Self(slice.read_u8())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user