bitbase: move to transactional

This commit is contained in:
nym21
2025-01-06 10:45:04 +01:00
parent 1380b42c1d
commit bf31ee5fd6
24 changed files with 714 additions and 954 deletions

View File

@@ -1,41 +0,0 @@
use color_eyre::eyre::eyre;
use derive_deref::{Deref, DerefMut};
use crate::structs::{Addressbytes, Addressindex, Database, DatabaseTrait, Height, Version};
#[derive(Deref, DerefMut)]
pub struct AddressbytesPrefixToAddressindex(Database);
impl AddressbytesPrefixToAddressindex {
pub fn import() -> color_eyre::Result<Self> {
Ok(Self(Database::import(
"address_prefix_to_addressindex",
Self::version(),
)?))
}
pub fn insert(
&mut self,
addressbytes: &Addressbytes,
addressindex: Addressindex,
height: Height,
) -> color_eyre::Result<()> {
if let Some(_height) =
self.fetch_update(addressbytes.to_prefix_slice(), addressindex.into(), height)?
{
dbg!(addressbytes, addressindex);
return Err(eyre!("AddressPrefixToAddressindex: key collision"));
}
Ok(())
}
pub fn remove(&mut self, addressbytes: &Addressbytes) {
self.0.remove(addressbytes.to_prefix_slice())
}
}
impl DatabaseTrait for AddressbytesPrefixToAddressindex {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,43 +0,0 @@
use derive_deref::{Deref, DerefMut};
use crate::structs::{Addressbytes, Addressindex, Database, DatabaseTrait, Height, Version};
#[derive(Deref, DerefMut)]
pub struct AddressindexToAddressbytes(Database);
impl AddressindexToAddressbytes {
pub fn import() -> color_eyre::Result<Self> {
Ok(Self(Database::import(
"addressindex_to_addressbytes",
Self::version(),
)?))
}
pub fn get(&self, addressindex: Addressindex) -> color_eyre::Result<Option<Addressbytes>> {
if let Some(address) = self.0.get(addressindex.into())?.map(Addressbytes::try_from) {
Ok(Some(address?))
} else {
Ok(None)
}
}
pub fn insert(
&mut self,
addressindex: Addressindex,
addressbytes: &Addressbytes,
height: Height,
) {
self.0
.insert(addressindex.into(), addressbytes.into(), height)
}
pub fn remove(&mut self, addressindex: Addressindex) {
self.0.remove(addressindex.into())
}
}
impl DatabaseTrait for AddressindexToAddressbytes {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,38 +0,0 @@
use derive_deref::{Deref, DerefMut};
use crate::structs::{Addressindex, Addresstype, Database, DatabaseTrait, Height, Version};
#[derive(Deref, DerefMut)]
pub struct AddressindexToAddresstype(Database);
impl AddressindexToAddresstype {
pub fn import() -> color_eyre::Result<Self> {
Ok(Self(Database::import(
"addressindex_to_addresstype",
Self::version(),
)?))
}
// pub fn get(&self, addressindex: Addressindex) -> color_eyre::Result<Option<Addresstype>> {
// if let Some(addresstype) = self.0.get(addressindex.into())?.map(Addresstype::try_from) {
// Ok(Some(addresstype?))
// } else {
// Ok(None)
// }
// }
pub fn insert(&mut self, addressindex: Addressindex, addresstype: Addresstype, height: Height) {
self.0
.insert(addressindex.into(), addresstype.into(), height)
}
pub fn remove(&mut self, addressindex: Addressindex) {
self.0.remove(addressindex.into())
}
}
impl DatabaseTrait for AddressindexToAddresstype {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,42 +0,0 @@
use derive_deref::{Deref, DerefMut};
use fjall::Slice;
use crate::structs::{
Addressindex, Addresstxoutindex, Database, DatabaseTrait, Height, SliceExtended, Txoutindex,
Version,
};
#[derive(Deref, DerefMut)]
pub struct AddressindexToTxoutindexes(Database);
impl AddressindexToTxoutindexes {
pub fn import() -> color_eyre::Result<Self> {
Ok(Self(Database::import(
"addressindex_to_txoutindexes",
Self::version(),
)?))
}
pub fn insert(&mut self, addressindex: Addressindex, txoutindex: Txoutindex, height: Height) {
self.0.insert(
Addresstxoutindex::from((addressindex, txoutindex)).into(),
Slice::default(),
height,
)
}
pub fn remove(&mut self, addressindex: Addressindex, txoutindex: Txoutindex) {
self.0
.remove(Addresstxoutindex::from((addressindex, txoutindex)).into());
}
pub fn is_empty(&self, addressindex: Addressindex) -> bool {
self.prefix(Slice::from(addressindex)).next().is_none()
}
}
impl DatabaseTrait for AddressindexToTxoutindexes {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,35 +0,0 @@
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)
}
}

View File

@@ -1,37 +0,0 @@
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)
}
}

View File

@@ -1,45 +0,0 @@
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)
}
}

View File

@@ -1,256 +0,0 @@
use std::{collections::BTreeSet, ops::Sub, thread};
use biter::bitcoin::{hashes::Hash, BlockHash, Txid};
use color_eyre::eyre::ContextCompat;
use fjall::Slice;
mod addressbytes_prefix_to_addressindex;
mod addressindex_to_addressbytes;
mod addressindex_to_addresstype;
mod addressindex_to_txoutindexes;
mod blockhash_prefix_to_height;
mod height_to_blockhash;
mod height_to_txindex;
mod txid_prefix_to_txindex;
mod txindex_to_height;
mod txindex_to_txid;
mod txoutindex_to_addressindex;
mod txoutindex_to_amount;
pub use addressbytes_prefix_to_addressindex::*;
pub use addressindex_to_addressbytes::*;
pub use addressindex_to_addresstype::*;
pub use addressindex_to_txoutindexes::*;
pub use blockhash_prefix_to_height::*;
pub use height_to_blockhash::*;
pub use height_to_txindex::*;
pub use txid_prefix_to_txindex::*;
pub use txindex_to_height::*;
pub use txindex_to_txid::*;
pub use txoutindex_to_addressindex::*;
pub use txoutindex_to_amount::*;
use crate::structs::{Addressindex, Exit, Height, Txindex, Txoutindex};
pub struct Database {
pub addressbytes_prefix_to_addressindex: AddressbytesPrefixToAddressindex,
pub addressindex_to_addressbytes: AddressindexToAddressbytes,
pub addressindex_to_addresstype: AddressindexToAddresstype,
pub addressindex_to_txoutindexes: AddressindexToTxoutindexes,
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 txindex_to_height: TxindexToHeight,
pub txoutindex_to_addressindex: TxoutindexToAddressindex,
pub txoutindex_to_amount: TxoutindexToAmount,
}
const UNSAFE_BLOCKS: usize = 100;
impl Database {
pub fn import() -> color_eyre::Result<Self> {
thread::scope(|scope| {
let addressbytes_prefix_to_addressindex_handle =
scope.spawn(AddressbytesPrefixToAddressindex::import);
let addressindex_to_addressbytes_handle =
scope.spawn(AddressindexToAddressbytes::import);
let addressindex_to_addresstype_handle = scope.spawn(AddressindexToAddresstype::import);
let addressindex_to_txoutindexes_handle =
scope.spawn(AddressindexToTxoutindexes::import);
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_height_handle = scope.spawn(TxindexToHeight::import);
let txindex_to_txid_handle = scope.spawn(TxindexToTxid::import);
let txoutindex_to_addressindex_handle = scope.spawn(TxoutindexToAddressindex::import);
let txoutindex_to_amount_handle = scope.spawn(TxoutindexToAmount::import);
Ok(Self {
addressbytes_prefix_to_addressindex: addressbytes_prefix_to_addressindex_handle
.join()
.unwrap()?,
addressindex_to_addressbytes: addressindex_to_addressbytes_handle
.join()
.unwrap()?,
addressindex_to_addresstype: addressindex_to_addresstype_handle.join().unwrap()?,
addressindex_to_txoutindexes: addressindex_to_txoutindexes_handle
.join()
.unwrap()?,
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_height: txindex_to_height_handle.join().unwrap()?,
txindex_to_txid: txindex_to_txid_handle.join().unwrap()?,
txoutindex_to_addressindex: txoutindex_to_addressindex_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.addressbytes_prefix_to_addressindex
.export(height)
.unwrap()
});
scope.spawn(|| self.addressindex_to_addressbytes.export(height).unwrap());
scope.spawn(|| self.addressindex_to_addresstype.export(height).unwrap());
scope.spawn(|| self.addressindex_to_txoutindexes.export(height).unwrap());
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_height.export(height).unwrap());
scope.spawn(|| self.txindex_to_txid.export(height).unwrap());
scope.spawn(|| self.txoutindex_to_addressindex.export(height).unwrap());
scope.spawn(|| self.txoutindex_to_amount.export(height).unwrap());
});
Ok(())
}
pub fn start_height(&self) -> Height {
self.min_height()
.map(|h| h.sub(UNSAFE_BLOCKS))
.unwrap_or_default()
}
fn min_height(&self) -> Option<Height> {
[
self.addressbytes_prefix_to_addressindex.height(),
self.addressindex_to_addressbytes.height(),
self.addressindex_to_addresstype.height(),
self.addressindex_to_txoutindexes.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_height.height(),
self.txindex_to_txid.height(),
self.txoutindex_to_addressindex.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 rollback_from(&mut self, height: Height, exit: &Exit) -> color_eyre::Result<()> {
exit.block();
self.export(height)?;
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.txindex_to_height.remove(txindex);
self.txid_prefix_to_txindex.remove(&txid);
Ok(())
})?;
let txoutindex = Txoutindex::from(txindex);
let mut addressindexes = BTreeSet::new();
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);
if let Some(addressindex_slice) =
self.txoutindex_to_addressindex.get(txoutindex.into())?
{
self.txoutindex_to_addressindex.remove(txoutindex);
let addressindex = Addressindex::from(addressindex_slice);
addressindexes.insert(addressindex);
self.addressindex_to_txoutindexes
.remove(addressindex, txoutindex);
}
Ok(())
})?;
self.export(height)?;
addressindexes
.into_iter()
.filter(|addressindex| self.addressindex_to_txoutindexes.is_empty(*addressindex))
.try_for_each(|addressindex| -> color_eyre::Result<()> {
let addressbytes = self
.addressindex_to_addressbytes
.get(addressindex)?
.context("addressindex_to_address to have value")?;
self.addressbytes_prefix_to_addressindex
.remove(&addressbytes);
self.addressindex_to_addressbytes.remove(addressindex);
self.addressindex_to_addresstype.remove(addressindex);
Ok(())
})?;
self.export(height)?;
exit.unblock();
Ok(())
}
}

View File

@@ -1,46 +0,0 @@
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)
}
}

View File

@@ -1,29 +0,0 @@
use derive_deref::{Deref, DerefMut};
use crate::structs::{Database, DatabaseTrait, Height, Txindex, Version};
#[derive(Deref, DerefMut)]
pub struct TxindexToHeight(Database);
impl TxindexToHeight {
pub fn import() -> color_eyre::Result<Self> {
Ok(Self(Database::import(
"txindex_to_height",
Self::version(),
)?))
}
pub fn insert(&mut self, txindex: Txindex, height: Height) {
self.0.insert(txindex.into(), height.into(), height)
}
pub fn remove(&mut self, txindex: Txindex) {
self.0.remove(txindex.into())
}
}
impl DatabaseTrait for TxindexToHeight {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,27 +0,0 @@
use biter::bitcoin::Txid;
use derive_deref::{Deref, DerefMut};
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(txindex.into())
}
}
impl DatabaseTrait for TxindexToTxid {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,30 +0,0 @@
use derive_deref::{Deref, DerefMut};
use crate::structs::{Addressindex, Database, DatabaseTrait, Height, Txoutindex, Version};
#[derive(Deref, DerefMut)]
pub struct TxoutindexToAddressindex(Database);
impl TxoutindexToAddressindex {
pub fn import() -> color_eyre::Result<Self> {
Ok(Self(Database::import(
"txoutindex_to_addressindex",
Self::version(),
)?))
}
pub fn insert(&mut self, txoutindex: Txoutindex, addressindex: Addressindex, height: Height) {
self.0
.insert(txoutindex.into(), addressindex.into(), height)
}
pub fn remove(&mut self, txoutindex: Txoutindex) {
self.0.remove(txoutindex.into())
}
}
impl DatabaseTrait for TxoutindexToAddressindex {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,29 +0,0 @@
use derive_deref::{Deref, DerefMut};
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(txoutindex.into())
}
}
impl DatabaseTrait for TxoutindexToAmount {
fn version() -> Version {
Version::from(1)
}
}

View File

@@ -1,13 +1,18 @@
use std::path::Path;
use std::{path::Path, str::FromStr};
use biter::bitcoincore_rpc::{Auth, Client};
use biter::{
bitcoin::{hashes::Hash, Txid},
bitcoincore_rpc::{Auth, Client},
};
mod database;
mod structs;
use database::Database;
use fjall::Slice;
use structs::{Addressbytes, Addressindex, Addresstype, Exit, Height, Txindex, Txoutindex};
use color_eyre::eyre::{eyre, ContextCompat};
use fjall::{PersistMode, Slice, TransactionalKeyspace, WriteTransaction};
use structs::{
Addressbytes, Addressindex, Addresstxoutindex, Addresstype, Amount, Exit, Height, Partitions,
Prefix, SliceExtended, Txindex, Txoutindex,
};
// https://github.com/fjall-rs/fjall/discussions/72
// https://github.com/romanz/electrs/blob/master/doc/schema.md
@@ -24,124 +29,241 @@ fn main() -> color_eyre::Result<()> {
let exit = Exit::new();
let mut db = Database::import()?;
let keyspace = fjall::Config::new("./database").open_transactional()?;
let mut height = db.start_height();
let mut parts = Partitions::import(&keyspace, &exit)?;
let mut txindex = db
.height_to_last_txindex
.get(height)?
let wtx = keyspace.write_tx();
let mut height = parts.start_height();
let mut txindex = wtx
.get(parts.height_to_last_txindex.data(), Slice::from(height))?
.map(Txindex::from)
.map(Txindex::incremented)
.unwrap_or(Txindex::default());
let mut addressindex = db
.txoutindex_to_addressindex
.prefix(Slice::from(txindex))
.last()
.map(|res| -> color_eyre::Result<Addressindex> {
Ok(Addressindex::from(res?.1).incremented())
})
.unwrap_or(Ok(Addressindex::default()))?;
let mut addressindex = wtx
.get(
parts.height_to_last_addressindex.data(),
Slice::from(height),
)?
.map(Addressindex::from)
.map(Addressindex::incremented)
.unwrap_or(Addressindex::default());
let export = |db: &mut Database, height: Height| -> color_eyre::Result<()> {
let export = |keyspace: &TransactionalKeyspace,
mut wtx: WriteTransaction,
parts: &Partitions,
height: Height|
-> color_eyre::Result<()> {
parts.udpate_meta(&mut wtx, height);
exit.block();
println!("Exporting...");
db.export(height)?;
wtx.commit()?;
keyspace.persist(PersistMode::SyncAll)?;
println!("Export done");
exit.unblock();
Ok(())
};
let mut wtx_opt = Some(wtx);
biter::new(data_dir, Some(height.into()), None, rpc)
.iter()
.try_for_each(|(_height, block, blockhash)| -> color_eyre::Result<()> {
let mut wtx = wtx_opt.take().context("option should've wtx")?;
println!("Processing block {_height}...");
height = Height::from(_height);
if db.has_different_blockhash(height, &blockhash)? {
db.rollback_from(height, &exit)?;
let has_different_blockhash = wtx
.get(parts.height_to_blockhash.data(), Slice::from(height))?
.is_some_and(|saved_blockhash_slice| blockhash[..] != saved_blockhash_slice[..]);
if has_different_blockhash {
parts.rollback_from(&mut wtx, height, &exit)?;
}
db.blockhash_prefix_to_height.insert(&blockhash, height)?;
db.height_to_blockhash.insert(height, &blockhash);
if parts.blockhash_prefix_to_height.needs(height) {
if let Some(prev) = wtx.fetch_update(
parts.blockhash_prefix_to_height.data(),
blockhash.prefix(),
|_| Some(Slice::from(height)),
)? {
dbg!(prev);
return Err(eyre!("Expect none"));
}
}
if parts.height_to_blockhash.needs(height) {
wtx.insert(
parts.height_to_blockhash.data(),
Slice::from(height),
blockhash,
);
}
if parts.height_to_first_addressindex.needs(height) {
wtx.insert(
parts.height_to_first_addressindex.data(),
Slice::from(addressindex),
blockhash,
);
}
let txlen = block.txdata.len();
let last_txindex = txlen - 1;
let last_txi = txlen - 1;
block.txdata.into_iter().enumerate().try_for_each(
|(txi, tx)| -> color_eyre::Result<()> {
if txi == 0 {
db.height_to_first_txindex.insert(height, txindex);
if txi == 0 && parts.height_to_first_txindex.needs(height) {
wtx.insert(
parts.height_to_first_txindex.data(),
Slice::from(height),
Slice::from(txindex),
);
}
if txi == last_txindex {
db.height_to_last_txindex.insert(height, txindex);
if txi == last_txi && parts.height_to_last_txindex.needs(height) {
wtx.insert(
parts.height_to_last_txindex.data(),
Slice::from(height),
Slice::from(txindex),
);
}
if !db.txindex_to_txid.is_safe(height)
|| !db.txid_prefix_to_txindex.is_safe(height)
if parts.txindex_to_txid.needs(height)
|| parts.txid_prefix_to_txindex.needs(height)
{
let txid = tx.compute_txid();
db.txindex_to_txid.insert(txindex, &txid, height);
db.txid_prefix_to_txindex.insert(&txid, txindex, height)?;
if parts.txindex_to_txid.needs(height) {
wtx.insert(parts.txindex_to_txid.data(), Slice::from(txindex), txid);
}
if parts.txid_prefix_to_txindex.needs(height) {
if let Some(prev) = wtx.fetch_update(
parts.txid_prefix_to_txindex.data(),
txid.prefix(),
|_| Some(Slice::from(txindex)),
)? {
let prev_txid = Txid::from_slice(&wtx
.get(parts.txindex_to_txid.data(), &prev)?.expect("To have txid for txindex"))?;
let only_known_dup_txids = [Txid::from_str("d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599")?, Txid::from_str("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468")?];
// TODO
// link txindex to txid
// txid_prefix should point to the first vout so no override
// do not add vout as they're invalid
if !only_known_dup_txids.contains(&prev_txid) {
let prev_height = Height::from(wtx.get(parts.txindex_to_height.data(), &prev)?.expect("To have height"));
let prev_txindex = Txindex::from(prev);
dbg!(
height,
txid,
txindex,
prev_height,
prev_txid,
prev_txindex,
);
return Err(eyre!("Expect none"));
}
}
}
}
db.txindex_to_height.insert(txindex, height);
if parts.txindex_to_height.needs(height) {
wtx.insert(
parts.txindex_to_height.data(),
Slice::from(txindex),
Slice::from(height),
);
}
txindex.increment();
tx.output.into_iter().enumerate().try_for_each(
tx.output.iter().enumerate().try_for_each(
|(vout, txout)| -> color_eyre::Result<()> {
let vout = vout as u16;
let txoutindex = Txoutindex::from((txindex, vout));
let amount = txout.value.into();
db.txoutindex_to_amount.insert(txoutindex, amount, height);
let amount = Amount::from(txout.value);
if parts.txoutindex_to_amount.needs(height) {
wtx.insert(
parts.txoutindex_to_amount.data(),
Slice::from(txoutindex),
Slice::from(amount),
);
}
let script = &txout.script_pubkey;
let addresstype = Addresstype::from(script);
let addressbytes =
Addressbytes::try_from(script).inspect_err(|_| {
dbg!(&txout, height, txi);
})?;
let addresstype = Addresstype::try_from(script)?;
Addressbytes::try_from((script, addresstype, addressindex))
.inspect_err(|_| {
dbg!(&txout, height, txi, &tx.compute_txid());
})?;
let mut addressindex_local = addressindex;
if let Some(addressindex_slice) = db
.addressbytes_prefix_to_addressindex
.get((&addressbytes).into())?
{
if let Some(addressindex_slice) = wtx.get(
parts.addressbytes_prefix_to_addressindex.data(),
Slice::from(&addressbytes),
)? {
addressindex_local = addressindex_slice.into()
} else {
db.addressbytes_prefix_to_addressindex
.insert(&addressbytes, addressindex_local, height)
.inspect_err(|_| {
dbg!(addresstype);
})?;
db.addressindex_to_addressbytes.insert(
addressindex_local,
&addressbytes,
height,
);
db.addressindex_to_addresstype.insert(
addressindex_local,
addresstype,
height,
);
if parts.addressbytes_prefix_to_addressindex.needs(height) {
if let Some(prev) = wtx.fetch_update(
parts.addressbytes_prefix_to_addressindex.data(),
Slice::from(&addressbytes),
|_| Some(Slice::from(addressindex_local)),
)? {
dbg!(prev);
return Err(eyre!("Expect none"));
}
}
if parts.addressindex_to_addressbytes.needs(height) {
wtx.insert(
parts.addressindex_to_addressbytes.data(),
Slice::from(addressindex_local),
Slice::from(&addressbytes),
);
}
if parts.addressindex_to_addresstype.needs(height) {
wtx.insert(
parts.addressindex_to_addresstype.data(),
Slice::from(addressindex_local),
Slice::from(addresstype),
);
}
addressindex.increment();
}
db.txoutindex_to_addressindex.insert(
txoutindex,
addressindex_local,
height,
);
if parts.txoutindex_to_addressindex.needs(height) {
wtx.insert(
parts.txoutindex_to_addressindex.data(),
Slice::from(txoutindex),
Slice::from(addressindex_local),
);
}
db.addressindex_to_txoutindexes.insert(
addressindex_local,
txoutindex,
height,
);
if parts.addresstxoutindexes.needs(height) {
wtx.insert(
parts.addresstxoutindexes.data(),
Slice::from(Addresstxoutindex::from((
addressindex_local,
txoutindex,
))),
Slice::default(),
);
}
Ok(())
},
@@ -151,15 +273,29 @@ fn main() -> color_eyre::Result<()> {
},
)?;
if parts.height_to_last_addressindex.needs(height) {
wtx.insert(
parts.height_to_last_addressindex.data(),
Slice::from(addressindex.decremented()),
blockhash,
);
}
let should_snapshot = _height % MONTHLY_BLOCK_TARGET == 0 && !exit.active();
if should_snapshot {
export(&mut db, height)?;
export(&keyspace, wtx, &parts, height)?;
wtx_opt.replace(keyspace.write_tx());
} else {
wtx_opt.replace(wtx);
}
Ok(())
})?;
export(&mut db, height)?;
let wtx = wtx_opt
.take()
.context("option should have WriteTransaction")?;
export(&keyspace, wtx, &parts, height)?;
dbg!(i.elapsed());

View File

@@ -1,42 +1,62 @@
use biter::bitcoin::ScriptBuf;
use color_eyre::eyre::eyre;
use derive_deref::{Deref, DerefMut};
use fjall::Slice;
#[derive(Debug)]
use super::{Addressindex, Addresstype};
#[derive(Debug, Deref, DerefMut)]
pub struct Addressbytes(Slice);
impl Addressbytes {
pub fn to_prefix_slice(&self) -> Slice {
self.0[..8].into()
}
}
impl TryFrom<&ScriptBuf> for Addressbytes {
impl TryFrom<(&ScriptBuf, Addresstype, Addressindex)> for Addressbytes {
type Error = color_eyre::Report;
fn try_from(script: &ScriptBuf) -> Result<Self, Self::Error> {
if script.is_p2pk() {
let bytes = script.as_bytes();
let bytes = match bytes.len() {
67 => &script.as_bytes()[1..66],
35 => &script.as_bytes()[1..34],
_ => {
fn try_from(tuple: (&ScriptBuf, Addresstype, Addressindex)) -> Result<Self, Self::Error> {
let (script, addresstype, addressindex) = tuple;
match addresstype {
Addresstype::P2PK => {
let bytes = script.as_bytes();
let bytes = match bytes.len() {
67 => &script.as_bytes()[1..66],
35 => &script.as_bytes()[1..34],
_ => {
dbg!(bytes);
return Err(eyre!("Wrong len"));
}
};
if bytes[0] != 4 {
dbg!(bytes);
return Err(eyre!("Wrong len"));
return Err(eyre!("Doesn't start with a 4"));
}
};
if bytes[0] != 4 {
dbg!(bytes);
return Err(eyre!("Doesn't start with a 4"));
Ok(Self(bytes.into()))
}
Addresstype::P2PKH => {
let bytes = &script.as_bytes()[3..23];
Ok(Self(bytes.into()))
}
_ => {
if script.is_p2sh() {
Err(eyre!("p2sh address type"))
} else if script.is_p2wpkh() {
Err(eyre!("p2wpkh address type"))
} else if script.is_p2wsh() {
Err(eyre!("p2wsh address type"))
} else if script.is_p2tr() {
Err(eyre!("p2tr address type"))
} else if script.is_empty() {
Err(eyre!("empty address type"))
} else if script.is_op_return() {
Err(eyre!("op_return address type"))
} else if script.is_multisig() {
Err(eyre!("multisig address type"))
} else if script.is_push_only() {
Err(eyre!("push only address type"))
} else {
Ok(Self(addressindex.into()))
}
}
Ok(Self(bytes.into()))
} else if script.is_p2pkh() {
let bytes = &script.as_bytes()[3..23];
Ok(Self(bytes.into()))
} else {
Err(eyre!("Unsupported address type"))
}
}
}

View File

@@ -9,6 +9,10 @@ pub struct Addressindex(u32);
impl Addressindex {
pub const BYTES: usize = size_of::<Self>();
pub fn decremented(self) -> Self {
Self(*self - 1)
}
pub fn increment(&mut self) {
self.0 += 1;
}

View File

@@ -4,21 +4,45 @@ use fjall::Slice;
use super::SliceExtended;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Addresstype {
P2PK,
P2PKH,
P2SH,
P2WPKH,
P2WSH,
P2TR,
Multisig = 251,
PushOnly = 252,
OpReturn = 253,
Empty = 254,
Unknown = 255,
}
impl TryFrom<&ScriptBuf> for Addresstype {
type Error = color_eyre::Report;
fn try_from(value: &ScriptBuf) -> Result<Self, Self::Error> {
if value.is_p2pk() {
Ok(Self::P2PK)
} else if value.is_p2pkh() {
Ok(Self::P2PKH)
impl From<&ScriptBuf> for Addresstype {
fn from(script: &ScriptBuf) -> Self {
if script.is_p2pk() {
Self::P2PK
} else if script.is_p2pkh() {
Self::P2PKH
} else if script.is_p2sh() {
Self::P2SH
} else if script.is_p2wpkh() {
Self::P2WPKH
} else if script.is_p2wsh() {
Self::P2WSH
} else if script.is_p2tr() {
Self::P2TR
} else if script.is_empty() {
Self::Empty
} else if script.is_op_return() {
Self::OpReturn
} else if script.is_push_only() {
Self::PushOnly
} else if script.is_multisig() {
Self::Multisig
} else {
Err(eyre!("Not compatible script"))
Self::Unknown
}
}
}

View File

@@ -1,143 +0,0 @@
use std::{mem, ops::RangeBounds};
pub use fjall::*;
use crate::structs::{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!("./database/{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 prefix<'a, K: AsRef<[u8]> + 'a>(
&'a self,
prefix: K,
) -> impl DoubleEndedIterator<Item = Result<KvPair>> + 'static {
self.data.prefix(prefix)
}
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;
}

View File

@@ -4,8 +4,10 @@ mod addresstxoutindex;
mod addresstype;
mod amount;
mod exit;
mod fjall;
mod height;
mod partition;
mod partitions;
mod prefix;
mod slice;
mod txindex;
mod txoutindex;
@@ -17,8 +19,10 @@ pub use addresstxoutindex::*;
pub use addresstype::*;
pub use amount::*;
pub use exit::*;
pub use fjall::*;
pub use height::*;
pub use partition::*;
pub use partitions::*;
pub use prefix::*;
pub use slice::*;
pub use txindex::*;
pub use txoutindex::*;

View File

@@ -0,0 +1,104 @@
pub use fjall::{
PartitionCreateOptions, PersistMode, Result, TransactionalKeyspace,
TransactionalPartitionHandle,
};
use crate::structs::{Height, Version};
use super::Exit;
pub struct Partition {
version: Version,
data: TransactionalPartitionHandle,
meta: TransactionalPartitionHandle,
height: Option<Height>,
}
impl Partition {
pub const VERSION: &str = "version";
pub const HEIGHT: &str = "height";
pub fn import(
keyspace: &TransactionalKeyspace,
name: &str,
version: Version,
exit: &Exit,
) -> Result<Self> {
let data = Self::open_data(keyspace, name)?;
let meta = Self::open_meta(keyspace, name)?;
let mut this = Self {
version,
height: meta.get(Self::HEIGHT)?.map(Height::from),
data,
meta,
};
if let Some(slice) = this.meta.get(Self::VERSION)? {
if version != Version::from(slice) {
this = this.reset(keyspace, name, exit)?;
}
}
Ok(this)
}
fn open_data(
keyspace: &TransactionalKeyspace,
name: &str,
) -> Result<TransactionalPartitionHandle> {
keyspace.open_partition(&format!("{name}-data"), Self::create_options())
}
fn open_meta(
keyspace: &TransactionalKeyspace,
name: &str,
) -> Result<TransactionalPartitionHandle> {
keyspace.open_partition(&format!("{name}-meta"), Self::create_options())
}
fn create_options() -> PartitionCreateOptions {
PartitionCreateOptions::default().manual_journal_persist(true)
}
pub fn is_safe(&self, height: Height) -> bool {
self.height.is_some_and(|self_height| self_height >= height)
}
pub fn needs(&self, height: Height) -> bool {
!self.is_safe(height)
}
pub fn version(&self) -> Version {
self.version
}
pub fn data(&self) -> &TransactionalPartitionHandle {
&self.data
}
pub fn meta(&self) -> &TransactionalPartitionHandle {
&self.meta
}
fn reset(mut self, keyspace: &TransactionalKeyspace, name: &str, exit: &Exit) -> Result<Self> {
exit.block();
keyspace.delete_partition(self.data)?;
keyspace.delete_partition(self.meta)?;
keyspace.persist(PersistMode::SyncAll)?;
self.data = Self::open_data(keyspace, name)?;
self.meta = Self::open_meta(keyspace, name)?;
self.height = None;
exit.unblock();
Ok(self)
}
pub fn height(&self) -> &Option<Height> {
&self.height
}
}

View File

@@ -0,0 +1,278 @@
use std::ops::Sub;
use fjall::{Slice, TransactionalKeyspace, WriteTransaction};
use crate::structs::{Exit, Height, Partition, Version};
pub struct Partitions {
pub addressbytes_prefix_to_addressindex: Partition,
pub addressindex_to_addressbytes: Partition,
pub addressindex_to_addresstype: Partition,
pub addresstxoutindexes: Partition,
pub blockhash_prefix_to_height: Partition,
pub height_to_blockhash: Partition,
pub height_to_first_addressindex: Partition,
pub height_to_first_txindex: Partition,
pub height_to_last_addressindex: Partition,
pub height_to_last_txindex: Partition,
pub txid_prefix_to_txindex: Partition,
pub txindex_to_height: Partition,
pub txindex_to_txid: Partition,
pub txoutindex_to_addressindex: Partition,
pub txoutindex_to_amount: Partition,
}
const UNSAFE_BLOCKS: usize = 100;
impl Partitions {
pub fn import(keyspace: &TransactionalKeyspace, exit: &Exit) -> color_eyre::Result<Self> {
Ok(Self {
addressbytes_prefix_to_addressindex: Partition::import(
keyspace,
"addressbytes_prefix_to_addressindex",
Version::from(1),
exit,
)?,
addressindex_to_addressbytes: Partition::import(
keyspace,
"addressindex_to_addressbytes",
Version::from(1),
exit,
)?,
addressindex_to_addresstype: Partition::import(
keyspace,
"addressindex_to_addresstype",
Version::from(1),
exit,
)?,
addresstxoutindexes: Partition::import(
keyspace,
"addresstxoutindexes",
Version::from(1),
exit,
)?,
blockhash_prefix_to_height: Partition::import(
keyspace,
"blockhash_prefix_to_height",
Version::from(1),
exit,
)?,
height_to_blockhash: Partition::import(
keyspace,
"height_to_blockhash",
Version::from(1),
exit,
)?,
height_to_first_addressindex: Partition::import(
keyspace,
"height_to_first_addressindex",
Version::from(1),
exit,
)?,
height_to_first_txindex: Partition::import(
keyspace,
"height_to_first_txindex",
Version::from(1),
exit,
)?,
height_to_last_addressindex: Partition::import(
keyspace,
"height_to_last_addressindex",
Version::from(1),
exit,
)?,
height_to_last_txindex: Partition::import(
keyspace,
"height_to_last_txindex",
Version::from(1),
exit,
)?,
txid_prefix_to_txindex: Partition::import(
keyspace,
"txid_prefix_to_txindex",
Version::from(1),
exit,
)?,
txindex_to_height: Partition::import(
keyspace,
"txindex_to_height",
Version::from(1),
exit,
)?,
txindex_to_txid: Partition::import(
keyspace,
"txindex_to_txid",
Version::from(1),
exit,
)?,
txoutindex_to_addressindex: Partition::import(
keyspace,
"txoutindex_to_addressindex",
Version::from(1),
exit,
)?,
txoutindex_to_amount: Partition::import(
keyspace,
"txoutindex_to_amount",
Version::from(1),
exit,
)?,
})
}
pub fn udpate_meta(&self, wtx: &mut WriteTransaction, height: Height) {
self.to_vec().into_iter().for_each(|part| {
let meta = part.meta();
wtx.insert(meta, Partition::VERSION, Slice::from(part.version()));
wtx.insert(meta, Partition::HEIGHT, height.to_be_bytes());
});
}
pub fn start_height(&self) -> Height {
self.min_height()
.map(|height| height.sub(UNSAFE_BLOCKS))
.unwrap_or_default()
}
fn min_height(&self) -> Option<Height> {
self.to_vec()
.into_iter()
.map(|part| part.height())
.map(ToOwned::to_owned)
.min()
.flatten()
}
pub fn rollback_from(
&mut self,
_wtx: &mut WriteTransaction,
_height: Height,
_exit: &Exit,
) -> color_eyre::Result<()> {
panic!();
// let mut txindex = None;
// wtx.range(self.height_to_blockhash.data(), Slice::from(height)..)
// .try_for_each(|slice| -> color_eyre::Result<()> {
// let (height_slice, slice_blockhash) = slice?;
// let blockhash = BlockHash::from_slice(&slice_blockhash)?;
// wtx.remove(self.height_to_blockhash.data(), height_slice);
// wtx.remove(self.blockhash_prefix_to_height.data(), blockhash.prefix());
// if txindex.is_none() {
// txindex.replace(
// wtx.get(self.height_to_first_txindex.data(), height_slice)?
// .context("for height to have first txindex")?,
// );
// }
// wtx.remove(self.height_to_first_txindex.data(), height_slice);
// wtx.remove(self.height_to_last_txindex.data(), height_slice);
// Ok(())
// })?;
// let txindex = txindex.context("txindex to not be none by now")?;
// wtx.range(self.txindex_to_txid.data(), 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)?;
// wtx.remove(self.txindex_to_txid.data(), Slice::from(txindex));
// wtx.remove(self.txindex_to_height.data(), Slice::from(txindex));
// wtx.remove(self.txid_prefix_to_txindex.data(), txid.prefix());
// Ok(())
// })?;
// let txoutindex = Txoutindex::from(txindex);
// let mut addressindexes = BTreeSet::new();
// wtx.range(self.txoutindex_to_amount.data(), Slice::from(txoutindex)..)
// .try_for_each(|slice| -> color_eyre::Result<()> {
// let (txoutindex_slice, _) = slice?;
// wtx.remove(self.txoutindex_to_amount.data(), txoutindex_slice);
// if let Some(addressindex_slice) =
// wtx.get(self.txoutindex_to_addressindex.data(), txoutindex_slice)?
// {
// wtx.remove(self.txoutindex_to_addressindex.data(), txoutindex_slice);
// let addressindex = Addressindex::from(addressindex_slice);
// addressindexes.insert(addressindex);
// let txoutindex = Txoutindex::from(txoutindex_slice);
// let addresstxoutindex = Addresstxoutindex::from((addressindex, txoutindex));
// wtx.remove(
// self.addressindex_to_txoutindexes.data(),
// Slice::from(addresstxoutindex),
// );
// }
// Ok(())
// })?;
// addressindexes
// .into_iter()
// .filter(|addressindex| {
// let is_empty = wtx
// .prefix(
// self.addressindex_to_txoutindexes.data(),
// Slice::from(*addressindex),
// )
// .next()
// .is_none();
// is_empty
// })
// .try_for_each(|addressindex| -> color_eyre::Result<()> {
// let addressindex_slice = Slice::from(addressindex);
// let addressbytes = Addressbytes::from(
// wtx.get(
// self.addressindex_to_addressbytes.data(),
// &addressindex_slice,
// )?
// .context("addressindex_to_address to have value")?,
// );
// wtx.remove(
// self.addressbytes_prefix_to_addressindex.data(),
// addressbytes.prefix(),
// );
// wtx.remove(
// self.addressindex_to_addressbytes.data(),
// &addressindex_slice,
// );
// wtx.remove(self.addressindex_to_addresstype.data(), &addressindex_slice);
// Ok(())
// })?;
// Ok(())
}
fn to_vec(&self) -> Vec<&Partition> {
vec![
&self.addressbytes_prefix_to_addressindex,
&self.addressindex_to_addressbytes,
&self.addressindex_to_addresstype,
&self.addresstxoutindexes,
&self.blockhash_prefix_to_height,
&self.height_to_blockhash,
&self.height_to_first_addressindex,
&self.height_to_first_txindex,
&self.height_to_last_addressindex,
&self.height_to_last_txindex,
&self.txid_prefix_to_txindex,
&self.txindex_to_height,
&self.txindex_to_txid,
&self.txoutindex_to_addressindex,
&self.txoutindex_to_amount,
]
}
}

View File

@@ -0,0 +1,25 @@
use biter::bitcoin::{BlockHash, Txid};
use super::Addressbytes;
pub trait Prefix {
fn prefix(&self) -> &[u8];
}
impl Prefix for Addressbytes {
fn prefix(&self) -> &[u8] {
&self[..8]
}
}
impl Prefix for BlockHash {
fn prefix(&self) -> &[u8] {
&self[..8]
}
}
impl Prefix for Txid {
fn prefix(&self) -> &[u8] {
&self[..8]
}
}

View File

@@ -17,3 +17,9 @@ impl From<Slice> for Version {
Self(slice.read_u8())
}
}
impl From<Version> for Slice {
fn from(value: Version) -> Self {
value.to_be_bytes().into()
}
}

View File

@@ -77,7 +77,7 @@ enum BlockState {
/// });
///
/// dbg!(i.elapsed());
///}
/// }
/// ```
///
pub fn new(