use std::marker::PhantomData;
use exonum_crypto::Hash;
use crate::{
access::{Access, AccessError, FromAccess},
views::{IndexAddress, IndexState, IndexType, RawAccess, RawAccessMut, View, ViewWithMetadata},
BinaryValue, ObjectHash,
};
#[derive(Debug)]
pub struct ProofEntry<T: RawAccess, V> {
base: View<T>,
state: IndexState<T, Hash>,
_v: PhantomData<V>,
}
impl<T, V> FromAccess<T> for ProofEntry<T::Base, V>
where
T: Access,
V: BinaryValue + ObjectHash,
{
fn from_access(access: T, addr: IndexAddress) -> Result<Self, AccessError> {
let view = access.get_or_create_view(addr, IndexType::ProofEntry)?;
Ok(Self::new(view))
}
}
impl<T, V> ProofEntry<T, V>
where
T: RawAccess,
V: BinaryValue,
{
pub(crate) fn new(view: ViewWithMetadata<T>) -> Self {
let (base, state) = view.into_parts();
Self {
base,
state,
_v: PhantomData,
}
}
pub fn get(&self) -> Option<V> {
self.base.get(&())
}
pub fn exists(&self) -> bool {
self.base.contains(&())
}
}
impl<T, V> ProofEntry<T, V>
where
T: RawAccessMut,
V: BinaryValue + ObjectHash,
{
pub fn set(&mut self, value: V) {
self.state.set(value.object_hash());
self.base.put(&(), value);
}
pub fn remove(&mut self) {
self.state.unset();
self.base.remove(&());
}
pub fn take(&mut self) -> Option<V> {
let value = self.get();
if value.is_some() {
self.remove();
}
value
}
pub fn swap(&mut self, value: V) -> Option<V> {
let previous = self.get();
self.set(value);
previous
}
}
impl<T, V> ObjectHash for ProofEntry<T, V>
where
T: RawAccess,
V: BinaryValue + ObjectHash,
{
fn object_hash(&self) -> Hash {
self.state.get().unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{access::CopyAccessExt, Database, TemporaryDB};
use std::borrow::Cow;
#[test]
fn basics() {
let db = TemporaryDB::new();
let fork = db.fork();
{
let mut entry = fork.get_proof_entry("test");
assert!(!entry.exists());
entry.set(25_u64);
assert!(entry.exists());
assert_eq!(entry.get(), Some(25));
assert_eq!(entry.swap(42), Some(25));
}
db.merge(fork.into_patch()).unwrap();
let snapshot = db.snapshot();
let entry = snapshot.get_proof_entry::<_, u64>("test");
assert_eq!(entry.get(), Some(42));
assert_eq!(entry.object_hash(), 42_u64.object_hash());
}
#[test]
fn entry_with_custom_hashing() {
#[derive(Debug, PartialEq)]
struct CustomHash(u8);
impl BinaryValue for CustomHash {
fn to_bytes(&self) -> Vec<u8> {
vec![self.0]
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> anyhow::Result<Self> {
u8::from_bytes(bytes).map(Self)
}
}
impl ObjectHash for CustomHash {
fn object_hash(&self) -> Hash {
Hash::new([self.0; exonum_crypto::HASH_SIZE])
}
}
let db = TemporaryDB::new();
let fork = db.fork();
{
let mut entry = fork.get_proof_entry("test");
entry.set(CustomHash(11));
assert!(entry.exists());
assert_eq!(entry.get(), Some(CustomHash(11)));
}
db.merge(fork.into_patch()).unwrap();
let snapshot = db.snapshot();
let entry = snapshot.get_proof_entry::<_, ()>("test");
assert_eq!(
entry.object_hash(),
Hash::new([11; exonum_crypto::HASH_SIZE])
);
}
}