From 416a8bc451537ff96d409f12db8f74255784139b Mon Sep 17 00:00:00 2001 From: Santiago Carmuega Date: Thu, 15 Dec 2022 10:46:15 -0300 Subject: [PATCH] feat: Introduce RollDB for chain persistence --- Cargo.lock | 52 +++++++++++ Cargo.toml | 5 +- src/lib.rs | 1 + src/rolldb/blocks.rs | 60 ++++++++++++ src/rolldb/mod.rs | 144 ++++++++++++++++++++++++++++ src/rolldb/wal.rs | 211 ++++++++++++++++++++++++++++++++++++++++++ src/storage/blocks.rs | 93 ------------------- src/storage/chain.rs | 201 ---------------------------------------- src/storage/mod.rs | 1 - 9 files changed, 472 insertions(+), 296 deletions(-) create mode 100644 src/rolldb/blocks.rs create mode 100644 src/rolldb/mod.rs create mode 100644 src/rolldb/wal.rs delete mode 100644 src/storage/blocks.rs delete mode 100644 src/storage/chain.rs diff --git a/Cargo.lock b/Cargo.lock index 7d199b90..e93071e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.60.1" @@ -284,6 +293,7 @@ name = "dolos" version = "0.1.0" dependencies = [ "bech32 0.8.1", + "bincode", "clap", "config", "env_logger", @@ -299,6 +309,7 @@ dependencies = [ "rocksdb", "serde", "serde_json", + "tempfile", "thiserror", "tracing", "tracing-subscriber", @@ -323,6 +334,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "fnv" version = "1.0.7" @@ -423,6 +443,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -930,6 +959,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rocksdb" version = "0.19.0" @@ -1027,6 +1065,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index 31900cd8..3171f0ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,10 +29,13 @@ serde_json = "1.0.79" minicbor = "0.14.1" prometheus_exporter = { version = "0.8.4", default-features = false } gasket = { path = "../../construkts/gasket-rs" } -# gasket = { git = "https://github.com/construkts/gasket-rs.git" } thiserror = "1.0.30" lazy_static = "1.4.0" rayon = "1.5.3" rocksdb = "0.19.0" tracing = "0.1.37" tracing-subscriber = "0.3.16" +bincode = "1.3.3" + +[dev-dependencies] +tempfile = "3.3.0" diff --git a/src/lib.rs b/src/lib.rs index f46fe91c..cdb17b28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod crosscut; pub mod downstream; pub mod model; pub mod prelude; +pub mod rolldb; pub mod storage; pub mod upstream; diff --git a/src/rolldb/blocks.rs b/src/rolldb/blocks.rs new file mode 100644 index 00000000..c7dbe618 --- /dev/null +++ b/src/rolldb/blocks.rs @@ -0,0 +1,60 @@ +use pallas::crypto::hash::Hash; +use rocksdb::{ColumnFamilyRef, WriteBatch, DB}; + +#[derive(Debug)] +struct Key(Hash<32>); + +impl TryFrom> for Key { + type Error = super::Error; + + fn try_from(value: Box<[u8]>) -> Result { + let inner: [u8; 32] = value[0..32].try_into().map_err(|_| super::Error::Serde)?; + let inner = Hash::<32>::from(inner); + Ok(Self(inner)) + } +} + +impl From for Box<[u8]> { + fn from(v: Key) -> Self { + v.0.as_slice().into() + } +} + +type Value = Box<[u8]>; + +#[derive(Debug)] +pub struct Entry(Key, Value); + +impl TryFrom for Entry { + type Error = super::Error; + + fn try_from((k, v): super::RawKV) -> Result { + let k = k.try_into()?; + + Ok(Entry(k, v)) + } +} +pub fn stage_upsert( + db: &DB, + hash: super::BlockHash, + body: super::BlockBody, + batch: &mut WriteBatch, +) -> Result<(), super::Error> { + let cf = blocks_cf(db); + + batch.put_cf(cf, hash, body); + + Ok(()) +} + +pub const CF_NAME: &str = "blocks"; + +pub fn blocks_cf(db: &DB) -> ColumnFamilyRef { + db.cf_handle(CF_NAME).unwrap() +} + +pub fn get_body(db: &DB, hash: Hash<32>) -> Result, super::Error> { + let cf = blocks_cf(db); + let key = Box::<[u8]>::from(Key(hash)); + db.get_cf(cf, key).map_err(|_| super::Error::IO) +} diff --git a/src/rolldb/mod.rs b/src/rolldb/mod.rs new file mode 100644 index 00000000..903f519f --- /dev/null +++ b/src/rolldb/mod.rs @@ -0,0 +1,144 @@ +mod blocks; +mod wal; +use pallas::crypto::hash::Hash; +use std::path::Path; +use thiserror::Error; + +use rocksdb::{Options, WriteBatch, DB}; + +#[derive(Error, Debug)] +pub enum Error { + #[error("IO error")] + IO, + + #[error("serde error")] + Serde, +} + +const CHAIN_CF: &str = "chain"; + +type BlockSlot = u64; +type BlockHash = Hash<32>; +type BlockBody = Vec; + +type RawKV = (Box<[u8]>, Box<[u8]>); + +pub struct RollDB { + db: DB, + wal_seq: u64, +} + +impl RollDB { + pub fn open(path: impl AsRef) -> Result { + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.create_missing_column_families(true); + + let db = DB::open_cf(&opts, path, [blocks::CF_NAME, CHAIN_CF, wal::CF_NAME]) + .map_err(|_| Error::IO)?; + + let wal_seq = wal::find_lastest_seq(&db)?.into(); + + Ok(Self { db, wal_seq }) + } + + pub fn get_block(&mut self, hash: Hash<32>) -> Result, Error> { + blocks::get_body(&self.db, hash) + } + + pub fn roll_forward( + &mut self, + slot: BlockSlot, + hash: BlockHash, + body: BlockBody, + ) -> Result<(), Error> { + let mut batch = WriteBatch::default(); + + // keep track of the new block body + blocks::stage_upsert(&self.db, hash, body, &mut batch)?; + + // advance the WAL to the new point + let new_seq = wal::stage_roll_forward(&self.db, self.wal_seq, slot, hash, &mut batch)?; + + self.db.write(batch).map_err(|_| Error::IO)?; + self.wal_seq = new_seq; + + Ok(()) + } + + pub fn roll_back(&mut self, until: BlockSlot) -> Result<(), Error> { + let mut batch = WriteBatch::default(); + + let new_seq = wal::stage_roll_back(&self.db, self.wal_seq, until, &mut batch)?; + + self.db.write(batch).map_err(|_| Error::IO)?; + self.wal_seq = new_seq; + + Ok(()) + } + + pub fn find_tip(&self) -> Result, Error> { + // TODO: tip might be either on chain or WAL, we need to query both + wal::find_tip(&self.db) + } + + pub fn crawl_wal(&self) -> wal::CrawlIterator { + wal::crawl_forward(&self.db) + } + + pub fn destroy(path: impl AsRef) -> Result<(), Error> { + DB::destroy(&Options::default(), path).map_err(|_| Error::IO) + } +} + +#[cfg(test)] +mod tests { + use super::{BlockBody, BlockHash, BlockSlot, RollDB}; + + fn with_tmp_db(op: fn(db: RollDB) -> ()) { + let path = tempfile::tempdir().unwrap().into_path(); + let db = RollDB::open(path.clone()).unwrap(); + + op(db); + + RollDB::destroy(path).unwrap(); + } + + fn dummy_block(slot: u64) -> (BlockSlot, BlockHash, BlockBody) { + let hash = pallas::crypto::hash::Hasher::<256>::hash(slot.to_be_bytes().as_slice()); + (slot, hash, slot.to_be_bytes().to_vec()) + } + + #[test] + fn test_roll_forward_blackbox() { + with_tmp_db(|mut db| { + let (slot, hash, body) = dummy_block(11); + db.roll_forward(slot, hash, body.clone()).unwrap(); + + let persisted = db.get_block(hash).unwrap().unwrap(); + assert_eq!(persisted, body); + + let (tip_slot, tip_hash) = db.find_tip().unwrap().unwrap(); + assert_eq!(tip_slot, slot); + assert_eq!(tip_hash, hash); + }); + } + + #[test] + fn test_roll_back_blackbox() { + with_tmp_db(|mut db| { + for i in 0..5 { + let (slot, hash, body) = dummy_block(i * 10); + db.roll_forward(slot, hash, body).unwrap(); + } + + db.roll_back(20).unwrap(); + + let (tip_slot, _) = db.find_tip().unwrap().unwrap(); + assert_eq!(tip_slot, 20); + }); + } + + //TODO: test rollback beyond K + //TODO: test rollback with unknown slot +} diff --git a/src/rolldb/wal.rs b/src/rolldb/wal.rs new file mode 100644 index 00000000..5d7f39cd --- /dev/null +++ b/src/rolldb/wal.rs @@ -0,0 +1,211 @@ +use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; + +use rocksdb::{ColumnFamilyRef, IteratorMode, WriteBatch, DB}; + +#[derive(Debug)] +pub struct Key(u64); + +impl TryFrom> for Key { + type Error = super::Error; + + fn try_from(value: Box<[u8]>) -> Result { + let inner: [u8; 8] = value[0..8].try_into().map_err(|_| super::Error::Serde)?; + let inner = u64::from_be_bytes(inner); + Ok(Self(inner)) + } +} + +impl From for Box<[u8]> { + fn from(v: Key) -> Self { + v.0.to_be_bytes().into() + } +} + +impl From for u64 { + fn from(v: Key) -> Self { + v.0 + } +} + +#[derive(Debug, Serialize, Deserialize)] +enum WalAction { + Apply, + Undo, + Mark, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Value(WalAction, super::BlockSlot, super::BlockHash); + +impl Value { + pub fn slot(&self) -> super::BlockSlot { + self.1 + } + + pub fn into_undo(self) -> Option { + match self.0 { + WalAction::Apply => Some(Self(WalAction::Undo, self.1, self.2)), + WalAction::Undo => None, + WalAction::Mark => None, + } + } + + pub fn into_mark(self) -> Option { + match self.0 { + WalAction::Apply => Some(Self(WalAction::Mark, self.1, self.2)), + WalAction::Undo => None, + WalAction::Mark => None, + } + } +} + +impl TryFrom for Box<[u8]> { + type Error = super::Error; + + fn try_from(v: Value) -> Result { + bincode::serialize(&v) + .map(|x| x.into_boxed_slice()) + .map_err(|_| super::Error::Serde) + } +} + +impl TryFrom> for Value { + type Error = super::Error; + + fn try_from(value: Box<[u8]>) -> Result { + bincode::deserialize(&value).map_err(|_| super::Error::Serde) + } +} + +#[derive(Debug)] +pub struct Entry(Key, Value); + +impl TryFrom for Entry { + type Error = super::Error; + + fn try_from((k, v): super::RawKV) -> Result { + let k = k.try_into()?; + let v = v.try_into()?; + + Ok(Entry(k, v)) + } +} + +pub const CF_NAME: &str = "wal"; + +pub fn wal_cf(db: &DB) -> ColumnFamilyRef { + db.cf_handle(CF_NAME).unwrap() +} + +type RocksIterator<'a> = rocksdb::DBIteratorWithThreadMode<'a, rocksdb::DB>; + +pub struct CrawlIterator<'a>(RocksIterator<'a>); + +impl<'a> Iterator for CrawlIterator<'a> { + type Item = Result; + + fn next(&mut self) -> Option> { + match self.0.next() { + Some(Ok((key, value))) => Some(Value::try_from(value)), + Some(Err(err)) => Some(Err(super::Error::IO)), + None => None, + } + } +} + +pub fn crawl_forward(db: &DB) -> CrawlIterator { + let cf = wal_cf(db); + let inner = db.iterator_cf(cf, IteratorMode::Start); + CrawlIterator(inner) +} + +pub fn crawl_backwards(db: &DB) -> CrawlIterator { + let cf = wal_cf(db); + let inner = db.iterator_cf(cf, IteratorMode::End); + CrawlIterator(inner) +} + +pub fn find_lastest_seq(db: &DB) -> Result { + let cf = wal_cf(db); + let mut iter = db.iterator_cf(cf, IteratorMode::End); + + match iter.next() { + Some(Ok((key, _))) => Ok(Key::try_from(key)?), + Some(Err(err)) => Err(super::Error::IO), + None => Ok(Key(0)), + } +} + +fn stage_append( + cf: ColumnFamilyRef, + last_seq: u64, + value: Value, + batch: &mut WriteBatch, +) -> Result { + let new_seq = last_seq + 1; + let key = Box::<[u8]>::from(Key(new_seq)); + let value = Box::<[u8]>::try_from(value)?; + + batch.put_cf(cf, key, value); + + Ok(new_seq) +} + +pub fn stage_roll_back( + db: &DB, + mut last_seq: u64, + until: super::BlockSlot, + batch: &mut WriteBatch, +) -> Result { + let iter = crawl_backwards(db); + let cf = wal_cf(db); + + for step in iter { + let value = step.map_err(|_| super::Error::IO)?; + + if value.slot() <= until { + last_seq = stage_append(cf, last_seq, value.into_mark().unwrap(), batch)?; + break; + } + + match value.into_undo() { + Some(undo) => { + last_seq = stage_append(cf, last_seq, undo, batch)?; + } + None => continue, + }; + } + + Ok(last_seq) +} + +pub fn stage_roll_forward( + db: &DB, + last_seq: u64, + slot: super::BlockSlot, + hash: super::BlockHash, + batch: &mut WriteBatch, +) -> Result { + let cf = wal_cf(db); + + let last_seq = stage_append(cf, last_seq, Value(WalAction::Apply, slot, hash), batch)?; + + Ok(last_seq) +} + +pub fn find_tip(db: &DB) -> Result, super::Error> { + let iter = crawl_backwards(db); + + for value in iter { + match value { + Ok(Value(WalAction::Apply | WalAction::Mark, slot, hash)) => { + return Ok(Some((slot, hash))) + } + Ok(_) => (), + Err(err) => return Err(err), + } + } + + Ok(None) +} diff --git a/src/storage/blocks.rs b/src/storage/blocks.rs deleted file mode 100644 index 15e27db2..00000000 --- a/src/storage/blocks.rs +++ /dev/null @@ -1,93 +0,0 @@ -use pallas::{crypto::hash::Hash, network::miniprotocols::Point}; - -use std::{ - convert::{TryFrom, TryInto}, - path::Path, -}; - -use rocksdb::{Direction, IteratorMode, Options, DB}; - -#[derive(Debug)] -struct Key(Hash<32>); - -impl TryFrom> for Key { - type Error = super::Error; - - fn try_from(value: Box<[u8]>) -> Result { - let inner: [u8; 32] = value[0..32].try_into().map_err(Self::Error::serde)?; - let inner = Hash::<32>::from(inner); - Ok(Self(inner)) - } -} - -impl Into> for Key { - fn into(self) -> Box<[u8]> { - self.0.as_slice().into() - } -} - -type Value = Box<[u8]>; - -type RawKV = (Box<[u8]>, Box<[u8]>); - -#[derive(Debug)] -pub struct Entry(Key, Value); - -impl TryFrom for Entry { - type Error = super::Error; - - fn try_from((k, v): RawKV) -> Result { - let k = k.try_into()?; - - Ok(Entry(k, v)) - } -} - -pub struct BlocksDB { - db: DB, -} - -impl BlocksDB { - pub fn open(path: impl AsRef) -> Result { - let db = DB::open_default(path).map_err(super::Error::storage)?; - Ok(Self { db }) - } - - /// Sets the content of a block for a specific hash - pub fn set(&mut self, hash: Hash<32>, body: Vec) -> Result<(), super::Error> { - let key: Box<[u8]> = Key(hash).into(); - self.db.put(key, body).map_err(super::Error::storage) - } - - pub fn get(&mut self, hash: Hash<32>) -> Result>, super::Error> { - let key: Box<[u8]> = Key(hash).into(); - self.db.get(key).map_err(super::Error::storage) - } -} - -#[cfg(test)] -mod tests { - use super::BlocksDB; - use pallas::crypto::hash::Hash; - use std::str::FromStr; - - #[test] - fn test_rocks() { - let mut db = BlocksDB::open("./tmp2").unwrap(); - - db.set( - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - vec![0u8, 1u8], - ) - .unwrap(); - - db.get( - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - //BlocksDB::destroy(&Options::default(), "./tmp2"); - } -} diff --git a/src/storage/chain.rs b/src/storage/chain.rs deleted file mode 100644 index e4961782..00000000 --- a/src/storage/chain.rs +++ /dev/null @@ -1,201 +0,0 @@ -use pallas::{crypto::hash::Hash, network::miniprotocols::Point}; - -use std::{ - convert::{TryFrom, TryInto}, - path::Path, -}; - -use rocksdb::{Direction, IteratorMode, Options, DB}; - -#[derive(Debug)] -struct Key(u64); - -impl TryFrom> for Key { - type Error = super::Error; - - fn try_from(value: Box<[u8]>) -> Result { - let inner: [u8; 8] = value[0..8].try_into().map_err(Self::Error::serde)?; - let inner = u64::from_be_bytes(inner); - Ok(Self(inner)) - } -} - -impl Into> for Key { - fn into(self) -> Box<[u8]> { - self.0.to_be_bytes().into() - } -} - -#[derive(Debug)] -struct Value(Hash<32>); - -impl TryFrom> for Value { - type Error = super::Error; - - fn try_from(value: Box<[u8]>) -> Result { - let inner: [u8; 32] = value[0..32].try_into().map_err(Self::Error::serde)?; - let inner = Hash::<32>::from(inner); - Ok(Self(inner)) - } -} - -impl Into> for Value { - fn into(self) -> Box<[u8]> { - self.0.as_slice().into() - } -} - -type RawKV = (Box<[u8]>, Box<[u8]>); -type RocksIterator<'a> = rocksdb::DBIteratorWithThreadMode<'a, rocksdb::DB>; - -pub struct Iterator<'a>(RocksIterator<'a>); - -impl<'a> Iterator<'a> { - pub fn next(&mut self) -> Option> { - match self.0.next() { - Some(Ok(kv)) => Some(Entry::try_from(kv)), - Some(Err(err)) => Some(Err(super::Error::storage(err))), - None => None, - } - } -} - -#[derive(Debug)] -pub struct Entry(Key, Value); - -impl TryFrom for Entry { - type Error = super::Error; - - fn try_from((k, v): RawKV) -> Result { - let k = k.try_into()?; - let v = v.try_into()?; - - Ok(Entry(k, v)) - } -} - -pub struct ChainDB { - db: DB, -} - -impl ChainDB { - pub fn open(path: impl AsRef) -> Result { - let db = DB::open_default(path).map_err(super::Error::storage)?; - Ok(Self { db }) - } - - /// Extends the state with a newly received block - pub fn extend(&mut self, slot: u64, hash: Hash<32>) -> Result<(), super::Error> { - let key: Box<[u8]> = Key(slot).into(); - let value: Box<[u8]> = Value(hash).into(); - - self.db.put(key, value).map_err(super::Error::storage) - } - - pub fn read_tip(&mut self) -> Result { - let mut iter = self.db.iterator(IteratorMode::End); - - match iter.next() { - Some(x) => { - let raw = x.map_err(super::Error::storage)?; - let Entry(key, value) = raw.try_into().map_err(super::Error::serde)?; - Ok(Point::Specific(key.0, Vec::from(value.0.as_slice()))) - } - None => Ok(Point::Origin), - } - } - - /// Clears entries since a certain slot - pub fn rollback(&mut self, slot: u64) -> Result<(), super::Error> { - let tip = self.read_tip()?; - - let cf = self.db.cf_handle("default").unwrap(); - let from: Box<[u8]> = Key(slot).into(); - let to: Box<[u8]> = Key(tip.slot_or_default()).into(); - - self.db - .delete_range_cf(&cf, from, to) - .map_err(super::Error::storage)?; - - Ok(()) - } - - /// Returns an entry interator from a certain slot - pub fn read_since(&self, slot: u64) -> Iterator { - let k: Box<[u8]> = Key(slot).into(); - let inner = self.db.iterator(IteratorMode::From(&k, Direction::Forward)); - Iterator(inner) - } -} - -#[cfg(test)] -mod tests { - use super::ChainDB; - use pallas::crypto::hash::Hash; - use std::str::FromStr; - - #[test] - fn test_rocks() { - let mut db = ChainDB::open("./tmp1").unwrap(); - - db.extend( - 0, - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - db.extend( - 1, - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - db.extend( - 2, - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - { - let mut iter = db.read_since(0); - - while let Some(point) = iter.next() { - dbg!(point); - } - } - - db.extend( - 3, - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - db.extend( - 4, - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - db.extend( - 5, - Hash::from_str("c5e51fb496cb215246a6c2b7354ca1078620cab8ae6f961e39a90b1291abd705") - .unwrap(), - ) - .unwrap(); - - { - let mut iter = db.read_since(3); - - while let Some(point) = iter.next() { - dbg!(point); - } - } - - //ChainDB::destroy(&Options::default(), "./tmp2"); - } -} diff --git a/src/storage/mod.rs b/src/storage/mod.rs index a962916c..8eb45376 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,4 +1,3 @@ -pub mod blocks; pub mod chain; mod cursor;