use std::ops::{Deref, DerefMut};
use mhinprotocol::{
store::MhinStore,
types::{Amount, UtxoKey},
};
use rollblock::types::{Operation as StoreOperation, Value as StoreValue};
use rollblock::MhinStoreBlockFacade;
pub(crate) struct UTXOStore {
facade: MhinStoreBlockFacade,
}
impl UTXOStore {
pub(crate) fn new(facade: MhinStoreBlockFacade) -> Self {
Self { facade }
}
pub(crate) fn view(&self) -> UTXOStoreView<'_> {
UTXOStoreView {
facade: &self.facade,
}
}
}
impl Deref for UTXOStore {
type Target = MhinStoreBlockFacade;
fn deref(&self) -> &Self::Target {
&self.facade
}
}
impl DerefMut for UTXOStore {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.facade
}
}
pub(crate) struct UTXOStoreView<'a> {
facade: &'a MhinStoreBlockFacade,
}
impl<'a> UTXOStoreView<'a> {
fn decode_value(&self, key: &UtxoKey, value: StoreValue) -> Amount {
if value.is_delete() {
return 0;
}
value.to_u64().unwrap_or_else(|| {
panic!(
"invalid rollblock value for key {key:?}: rollblock value exceeds 64-bit width ({} bytes)",
value.len()
)
})
}
}
impl<'a> MhinStore for UTXOStoreView<'a> {
fn get(&mut self, key: &UtxoKey) -> Amount {
let value = self.facade.get(*key).unwrap_or_else(|err| {
panic!("rollblock get failed for key {key:?}: {err}");
});
self.decode_value(key, value)
}
fn pop(&mut self, key: &UtxoKey) -> Amount {
let value = self.facade.pop(*key).unwrap_or_else(|err| {
panic!("rollblock pop failed for key {key:?}: {err}");
});
self.decode_value(key, value)
}
fn set(&mut self, key: UtxoKey, value: Amount) {
if let Err(err) = self.facade.set(StoreOperation {
key,
value: StoreValue::from(value),
}) {
panic!("rollblock set failed: {err}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rollblock::StoreConfig;
use tempfile::TempDir;
fn create_test_store(temp_dir: &TempDir) -> UTXOStore {
let config = StoreConfig::new(temp_dir.path(), 2, 1000, 2, false).expect("store config");
let facade = MhinStoreBlockFacade::new(config).expect("create facade");
UTXOStore::new(facade)
}
#[test]
fn utxo_store_deref_returns_facade() {
let temp = TempDir::new().expect("temp dir");
let store = create_test_store(&temp);
let facade_ref: &MhinStoreBlockFacade = &store;
assert!(facade_ref.current_block().is_ok());
}
#[test]
fn utxo_store_view_returns_zero_for_empty_value() {
let temp = TempDir::new().expect("temp dir");
let store = create_test_store(&temp);
let view = store.view();
let empty_value = StoreValue::from_vec(Vec::new());
let key: UtxoKey = [0u8; 8];
let amount = view.decode_value(&key, empty_value);
assert_eq!(amount, 0);
}
#[test]
fn utxo_store_view_decodes_u64_value() {
let temp = TempDir::new().expect("temp dir");
let store = create_test_store(&temp);
let view = store.view();
let key: UtxoKey = [1u8; 8];
let value = StoreValue::from(42u64);
let amount = view.decode_value(&key, value);
assert_eq!(amount, 42);
}
#[test]
fn utxo_store_set_get_roundtrip() {
let temp = TempDir::new().expect("temp dir");
let store = create_test_store(&temp);
store.start_block(0).expect("start block");
let key: UtxoKey = [2u8; 8];
{
let mut view = store.view();
view.set(key, 100);
}
store.end_block().expect("end block");
store.start_block(1).expect("start next block");
{
let mut view = store.view();
let amount = view.get(&key);
assert_eq!(amount, 100);
}
store.end_block().expect("end next block");
}
#[test]
fn utxo_store_pop_returns_and_removes_value() {
let temp = TempDir::new().expect("temp dir");
let store = create_test_store(&temp);
store.start_block(0).expect("start block");
let key: UtxoKey = [3u8; 8];
{
let mut view = store.view();
view.set(key, 200);
}
store.end_block().expect("end block");
store.start_block(1).expect("start pop block");
{
let mut view = store.view();
let amount = view.pop(&key);
assert_eq!(amount, 200);
}
store.end_block().expect("end pop block");
store.start_block(2).expect("start verify block");
{
let mut view = store.view();
let amount = view.get(&key);
assert_eq!(amount, 0);
}
store.end_block().expect("end verify block");
}
#[test]
fn utxo_store_deref_mut_allows_mutation() {
let temp = TempDir::new().expect("temp dir");
let mut store = create_test_store(&temp);
let facade_mut: &mut MhinStoreBlockFacade = &mut store;
facade_mut
.start_block(0)
.expect("start block via deref_mut");
facade_mut.end_block().expect("end block via deref_mut");
}
}