init: separate parser crate

This commit is contained in:
nym21
2025-01-01 10:32:39 +01:00
parent d72bf0739a
commit dea853d840
19 changed files with 2158 additions and 0 deletions
+1094
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -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)
}
}
+167
View File
@@ -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)
}
}
+100
View File
@@ -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(())
}
+90
View File
@@ -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)
}
}
+138
View File
@@ -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;
}
+60
View File
@@ -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)
}
}
+110
View File
@@ -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)
}
}
+17
View File
@@ -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::*;
+34
View File
@@ -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();
});
}
}
+44
View File
@@ -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()
}
}
+18
View File
@@ -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())
}
}