use crate::{
basic::persistent_btree::TreeDiff,
common::{
InstanceId,
ende::{KeyEnDeOrdered, ValueEnDe},
ensure_writable,
error::{Result, VsdbError},
},
versioned::{BranchId, CommitId, map::VerMap},
};
use super::{MptCalc, MptProof, SmtCalc, SmtProof, TrieCalc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofWithRoot<P> {
pub root_hash: [u8; 32],
pub proof: P,
}
impl<P> ProofWithRoot<P> {
fn new(root_hash: Vec<u8>, proof: P) -> Result<Self> {
let root_hash = root_hash.try_into().map_err(|_| VsdbError::Trie {
detail: "proof root must contain exactly 32 bytes".into(),
})?;
Ok(Self { root_hash, proof })
}
}
pub struct VerMapWithProof<K, V, T: TrieCalc> {
map: VerMap<K, V>,
trie: T,
applied_dirty: Option<Vec<TreeDiff>>,
sync_commit: Option<CommitId>,
sync_branch: Option<BranchId>,
dirty_applied: bool,
cache_instance: InstanceId,
}
impl<K, V, T> VerMapWithProof<K, V, T>
where
K: KeyEnDeOrdered,
V: ValueEnDe,
T: TrieCalc,
{
pub fn new() -> Self {
let map = VerMap::new();
let cache_instance = map.instance_id();
let mut this = Self {
map,
trie: T::default(),
applied_dirty: None,
sync_commit: None,
sync_branch: None,
dirty_applied: false,
cache_instance,
};
this.try_load_cache();
this
}
pub fn from_map(map: VerMap<K, V>) -> Self {
let cache_instance = map.instance_id();
let mut this = Self {
map,
trie: T::default(),
applied_dirty: None,
sync_commit: None,
sync_branch: None,
dirty_applied: false,
cache_instance,
};
this.try_load_cache();
this
}
pub fn map(&self) -> &VerMap<K, V> {
&self.map
}
pub fn map_mut(&mut self) -> &mut VerMap<K, V> {
&mut self.map
}
pub fn merkle_root(&mut self, branch: BranchId) -> Result<Vec<u8>> {
self.reset_if_map_replaced();
if self.sync_branch == Some(branch) {
let head = self.map.head_commit(branch)?;
let head_id = head.as_ref().map(|c| c.id);
if head_id == self.sync_commit {
let has_dirty = self.map.has_uncommitted(branch)?;
let unchanged = if has_dirty {
let diff = self.map.raw_diff_uncommitted(branch)?;
self.dirty_applied
&& self.applied_dirty.as_deref() == Some(diff.as_slice())
} else {
!self.dirty_applied
};
if unchanged {
return self.trie.root_hash();
}
}
}
self.sync_to_branch(branch)?;
self.trie.root_hash()
}
pub fn merkle_root_at_commit(&mut self, commit: CommitId) -> Result<Vec<u8>> {
self.sync_to_commit(commit)?;
self.trie.root_hash()
}
pub fn save_cache(&mut self, commit: CommitId) -> Result<()> {
ensure_writable(&self.map.namespace(), "versioned trie cache save")?;
self.sync_to_commit(commit)?;
self.trie.save_cache(
&self.map.namespace().system_dir(),
self.cache_instance.map_id,
commit.raw(),
)
}
fn sync_to_branch(&mut self, branch: BranchId) -> Result<()> {
let head = self.map.head_commit(branch)?;
let head_id = head.as_ref().map(|c| c.id);
if self.dirty_applied
&& let Err(e) = self.reverse_dirty()
{
self.poison();
return Err(e);
}
if let Some(hid) = head_id {
if self.sync_commit != Some(hid) {
self.sync_to_commit(hid)?;
}
} else if self.sync_commit.is_some() {
self.trie = T::default();
self.applied_dirty = None;
self.sync_commit = None;
}
if self.map.has_uncommitted(branch)? {
let diff = self.map.raw_diff_uncommitted(branch)?;
if let Err(e) = self.apply_diff(&diff) {
self.poison();
return Err(e);
}
self.applied_dirty = Some(diff);
self.dirty_applied = true;
} else {
self.applied_dirty = None;
self.dirty_applied = false;
}
self.sync_branch = Some(branch);
Ok(())
}
fn reverse_dirty(&mut self) -> Result<()> {
let diff = self.applied_dirty.take();
self.dirty_applied = false;
match diff {
Some(diff) => self.reverse_diff(&diff),
None => Ok(()),
}
}
fn reverse_diff(&mut self, diff: &[TreeDiff]) -> Result<()> {
let ops: Vec<(&[u8], Option<&[u8]>)> = diff
.iter()
.map(|entry| match entry {
TreeDiff::Added { key, .. } => (key.as_slice(), None),
TreeDiff::Removed { key, value } => {
(key.as_slice(), Some(value.as_slice()))
}
TreeDiff::Modified { key, old_value, .. } => {
(key.as_slice(), Some(old_value.as_slice()))
}
})
.collect();
self.trie.batch_update(&ops)
}
fn poison(&mut self) {
self.trie = T::default();
self.applied_dirty = None;
self.sync_commit = None;
self.sync_branch = None;
self.dirty_applied = false;
}
fn sync_to_commit(&mut self, target: CommitId) -> Result<()> {
self.reset_if_map_replaced();
if self.map.get_commit(target).is_none() {
return Err(VsdbError::CommitNotFound {
commit_id: target.raw(),
});
}
if self.sync_commit == Some(target) && !self.dirty_applied {
return Ok(());
}
if self.dirty_applied
&& let Err(e) = self.reverse_dirty()
{
self.poison();
return Err(e);
}
if self.sync_commit == Some(target) {
return Ok(());
}
match self.sync_commit {
Some(current) => {
match self.map.raw_diff_commits(current, target) {
Ok(diff) => {
if let Err(e) = self.apply_diff(&diff) {
self.poison();
return Err(e);
}
}
Err(_) => self.full_rebuild_commit(target)?,
}
}
None => {
self.full_rebuild_commit(target)?;
}
}
self.sync_commit = Some(target);
self.sync_branch = None;
Ok(())
}
fn full_rebuild_commit(&mut self, commit: CommitId) -> Result<()> {
let entries: Vec<_> = self.map.at(commit)?.raw_iter().collect();
self.trie = T::from_entries(entries)?;
Ok(())
}
fn apply_diff(&mut self, diff: &[TreeDiff]) -> Result<()> {
let ops: Vec<(&[u8], Option<&[u8]>)> = diff
.iter()
.map(|entry| match entry {
TreeDiff::Added { key, value } => {
(key.as_slice(), Some(value.as_slice()))
}
TreeDiff::Removed { key, .. } => (key.as_slice(), None),
TreeDiff::Modified { key, new_value, .. } => {
(key.as_slice(), Some(new_value.as_slice()))
}
})
.collect();
self.trie.batch_update(&ops)
}
}
impl<K, V> VerMapWithProof<K, V, SmtCalc>
where
K: KeyEnDeOrdered,
V: ValueEnDe,
{
pub fn prove_at(
&mut self,
branch: BranchId,
key: &K,
) -> Result<ProofWithRoot<SmtProof>> {
let root_hash = self.merkle_root(branch)?;
ProofWithRoot::new(root_hash, self.prove_key(key)?)
}
pub fn prove_at_commit(
&mut self,
commit: CommitId,
key: &K,
) -> Result<ProofWithRoot<SmtProof>> {
let root_hash = self.merkle_root_at_commit(commit)?;
ProofWithRoot::new(root_hash, self.prove_key(key)?)
}
pub fn prove(&self, key: &[u8]) -> Result<SmtProof> {
self.trie.prove(key)
}
pub fn prove_key(&self, key: &K) -> Result<SmtProof> {
self.trie.prove(&key.to_bytes())
}
pub fn verify_proof(
root_hash: &[u8; 32],
expected_key: &[u8],
proof: &SmtProof,
) -> Result<bool> {
SmtCalc::verify_proof(root_hash, expected_key, proof)
}
pub fn verify_key_proof(
root_hash: &[u8; 32],
expected_key: &K,
proof: &SmtProof,
) -> Result<bool> {
SmtCalc::verify_proof(root_hash, &expected_key.to_bytes(), proof)
}
}
impl<K, V> VerMapWithProof<K, V, MptCalc>
where
K: KeyEnDeOrdered,
V: ValueEnDe,
{
pub fn prove_at(
&mut self,
branch: BranchId,
key: &K,
) -> Result<ProofWithRoot<MptProof>> {
let root_hash = self.merkle_root(branch)?;
ProofWithRoot::new(root_hash, self.prove_mpt(key)?)
}
pub fn prove_at_commit(
&mut self,
commit: CommitId,
key: &K,
) -> Result<ProofWithRoot<MptProof>> {
let root_hash = self.merkle_root_at_commit(commit)?;
ProofWithRoot::new(root_hash, self.prove_mpt(key)?)
}
pub fn prove_mpt(&self, key: &K) -> Result<MptProof> {
self.trie.prove(&key.to_bytes())
}
pub fn verify_mpt_proof(
root_hash: &[u8; 32],
expected_key: &[u8],
proof: &MptProof,
) -> Result<bool> {
MptCalc::verify_proof(root_hash, expected_key, proof)
}
pub fn verify_key_proof(
root_hash: &[u8; 32],
expected_key: &K,
proof: &MptProof,
) -> Result<bool> {
MptCalc::verify_proof(root_hash, &expected_key.to_bytes(), proof)
}
}
impl<K, V, T> Default for VerMapWithProof<K, V, T>
where
K: KeyEnDeOrdered,
V: ValueEnDe,
T: TrieCalc,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V, T: TrieCalc> VerMapWithProof<K, V, T> {
fn reset_if_map_replaced(&mut self) {
let current = self.map.instance_id();
if current != self.cache_instance {
self.trie = T::default();
self.applied_dirty = None;
self.sync_commit = None;
self.sync_branch = None;
self.dirty_applied = false;
self.cache_instance = current;
}
}
fn try_load_cache(&mut self) {
if let Ok((mut trie, sync_tag, saved_hash)) = T::load_cache(
&self.map.namespace().system_dir(),
self.cache_instance.map_id,
) {
let ok = trie.root_hash().map(|h| h == saved_hash).unwrap_or(false);
if !ok {
return;
}
self.trie = trie;
self.applied_dirty = None;
self.sync_commit = Some(CommitId::from_raw(sync_tag));
self.sync_branch = None;
self.dirty_applied = false;
}
}
}