Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions crates/floresta-chain/src/pruned_utreexo/chainstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,35 @@ use spin::RwLock;

use crate::prelude::*;
#[derive(Debug, Clone, Copy)]
/// This enum is used to store a block header in the database.
/// It contains the header along with metadaba about the validation state of the block, and, if applicable, also its height.
pub enum DiskBlockHeader {
/// Represents a fully validated block header in the current best chain.
FullyValid(BlockHeader, u32),

/// Represents an assumed valid block header.
AssumedValid(BlockHeader, u32),

/// Represents an orphan block header.
Orphan(BlockHeader),

/// Represents a block header in the current best chain whose block is pending validation.
HeadersOnly(BlockHeader, u32),

/// Represents a block header in a fork.
InFork(BlockHeader, u32),

/// Represents an invalid chain block header.
InvalidChain(BlockHeader),
}

impl DiskBlockHeader {
/// Computes the block hash.
pub fn block_hash(&self) -> BlockHash {
self.deref().block_hash()
}

/// Gets the height of the block and returns None if the block is orphaned or on an invalid chain.
pub fn height(&self) -> Option<u32> {
match self {
DiskBlockHeader::FullyValid(_, height) => Some(*height),
Expand All @@ -37,6 +53,8 @@ impl DiskBlockHeader {
}
}
}

/// `DiskBlockHeader` dereferences to the inner header type.
impl Deref for DiskBlockHeader {
type Target = BlockHeader;
fn deref(&self) -> &Self::Target {
Expand All @@ -50,7 +68,10 @@ impl Deref for DiskBlockHeader {
}
}
}

/// Decodable is a trait from bitcoin::consensus::encode that allows decoding of a type from a reader in a consistent manner.
impl Decodable for DiskBlockHeader {
/// Decodes a `DiskBlockHeader` from a reader.
fn consensus_decode<R: bitcoin::io::Read + ?Sized>(
reader: &mut R,
) -> core::result::Result<Self, bitcoin::consensus::encode::Error> {
Expand Down Expand Up @@ -81,7 +102,9 @@ impl Decodable for DiskBlockHeader {
}
}
}

impl Encodable for DiskBlockHeader {
/// Encodes a `DiskBlockHeader` to a writer using the consensus encoding.
fn consensus_encode<W: bitcoin::io::Write + ?Sized>(
&self,
writer: &mut W,
Expand Down Expand Up @@ -131,6 +154,11 @@ use kv::Store;
use super::chain_state::BestChain;
use super::ChainStore;

/// As for now we use a KV (key/value) database to store the chain data.
/// This struct provides a way to interact with the chain data stored in the KV store.
/// It uses buckets to store and retrieve data concerning headers, indexes, and metadata.
///
/// It temporarily caches headers and indexes using a HashMap, which are later persisted.
pub struct KvChainStore<'a> {
_store: Store,
headers: Bucket<'a, Vec<u8>, Vec<u8>>,
Expand All @@ -141,6 +169,7 @@ pub struct KvChainStore<'a> {
}

impl<'a> KvChainStore<'a> {
/// Creates a new [KvChainStore] instance in the specified directory path.
pub fn new(datadir: String) -> Result<KvChainStore<'a>, kv::Error> {
// Configure the database
let cfg = Config::new(datadir + "/chain_data").cache_capacity(100_000_000);
Expand All @@ -161,15 +190,18 @@ impl<'a> KvChainStore<'a> {

impl ChainStore for KvChainStore<'_> {
type Error = kv::Error;
/// Loads the utreexo roots from the metadata bucket.
fn load_roots(&self) -> Result<Option<Vec<u8>>, Self::Error> {
self.meta.get(&"roots")
}

/// Saves the current utreexo roots to the metadata bucket.
fn save_roots(&self, roots: Vec<u8>) -> Result<(), Self::Error> {
self.meta.set(&"roots", &roots)?;
Ok(())
}

/// Loads the best chain data from the metadata bucket.
fn load_height(&self) -> Result<Option<BestChain>, Self::Error> {
let height = self.meta.get(&"height")?;
if let Some(height) = height {
Expand All @@ -179,12 +211,15 @@ impl ChainStore for KvChainStore<'_> {
Ok(None)
}

/// Saves the best chain data to the metadata bucket.
fn save_height(&self, height: &BestChain) -> Result<(), Self::Error> {
let height = serialize(height);
self.meta.set(&"height", &height)?;
Ok(())
}

/// Gets the block header using the provided block hash. If it is on cache, it returns it directly, otherwise
/// it fetches it from the database.
fn get_header(&self, block_hash: &BlockHash) -> Result<Option<DiskBlockHeader>, Self::Error> {
match self.headers_cache.read().get(block_hash) {
Some(header) => Ok(Some(*header)),
Expand All @@ -198,6 +233,7 @@ impl ChainStore for KvChainStore<'_> {
}
}

/// Flushes the cache to the database.
fn flush(&self) -> Result<(), Self::Error> {
// save all headers in batch
let mut batch = Batch::new();
Expand Down Expand Up @@ -227,13 +263,16 @@ impl ChainStore for KvChainStore<'_> {
Ok(())
}

/// Saves a header to the database.
fn save_header(&self, header: &DiskBlockHeader) -> Result<(), Self::Error> {
self.headers_cache
.write()
.insert(header.block_hash(), *header);
Ok(())
}

/// Gets a block hash from the database using the provided height.
/// If it's not found in the cache, it will be fetched from the database.
fn get_block_hash(&self, height: u32) -> Result<Option<BlockHash>, Self::Error> {
match self.index_cache.read().get(&height).cloned() {
Some(hash) => Ok(Some(hash)),
Expand All @@ -244,6 +283,7 @@ impl ChainStore for KvChainStore<'_> {
}
}

/// Updates the block index with the provided height and hash.
fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<(), Self::Error> {
self.index_cache.write().insert(height, hash);
Ok(())
Expand Down