use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use crate::store::NodeStore;
use crate::sync::ballot::Stamp;
use crate::tree::{Hash, LeafNode, Node, TreeError, batch_mutate};
use crate::ttl::entry::{StampedEntry, TtlDecodeError};
type StoredEntry = (Vec<u8>, Vec<u8>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HandoffMergeError {
MissingNode { hash: Hash },
StoreRead { hash: Hash },
InvalidNode,
UndecodableEntry { key: Vec<u8> },
DuplicateStamp { key: Vec<u8>, stamp: Stamp },
}
impl fmt::Display for HandoffMergeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingNode { hash } => write!(formatter, "missing tree node {hash}"),
Self::StoreRead { hash } => write!(formatter, "failed to read tree node {hash}"),
Self::InvalidNode => formatter.write_str("invalid tree node"),
Self::UndecodableEntry { key } => write!(
formatter,
"committed entry for key {} is not a stamped envelope",
String::from_utf8_lossy(key)
),
Self::DuplicateStamp { key, stamp } => write!(
formatter,
"key {} has two distinct committed values at the same stamp {stamp:?} \
(stamp uniqueness invariant violated)",
String::from_utf8_lossy(key)
),
}
}
}
impl std::error::Error for HandoffMergeError {}
impl From<TreeError> for HandoffMergeError {
fn from(error: TreeError) -> Self {
match error {
TreeError::MissingNode { hash } => Self::MissingNode { hash },
TreeError::InvalidNode => Self::InvalidNode,
}
}
}
impl From<TtlDecodeError> for HandoffMergeError {
fn from(_error: TtlDecodeError) -> Self {
Self::InvalidNode
}
}
pub fn merge_committed_union<S: NodeStore + ?Sized>(
root_a: Option<Hash>,
root_b: Option<Hash>,
store: &mut S,
) -> Result<Option<Hash>, HandoffMergeError> {
if root_a.is_none() && root_b.is_none() {
return Ok(None);
}
let mut winners: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
for root in [root_a, root_b].into_iter().flatten() {
merge_root_into(store, root, &mut winners)?;
}
let empty_root = store_empty_leaf(store)?;
let mutations: Vec<(Vec<u8>, Option<Vec<u8>>)> = winners
.into_iter()
.map(|(key, bytes)| (key, Some(bytes)))
.collect();
let merged = batch_mutate(store, empty_root, mutations.as_slice())?;
Ok(Some(merged))
}
fn merge_root_into<S: NodeStore + ?Sized>(
store: &S,
root: Hash,
winners: &mut BTreeMap<Vec<u8>, Vec<u8>>,
) -> Result<(), HandoffMergeError> {
for (key, bytes) in collect_stored_entries(store, root)? {
let stamp = decode_stamp(&key, &bytes)?;
match winners.get(&key) {
None => {
winners.insert(key, bytes);
}
Some(existing) => {
let existing_stamp = decode_stamp(&key, existing)?;
if stamp > existing_stamp {
winners.insert(key, bytes);
} else if stamp == existing_stamp && bytes != *existing {
return Err(HandoffMergeError::DuplicateStamp { key, stamp });
}
}
}
}
Ok(())
}
fn decode_stamp(key: &[u8], bytes: &[u8]) -> Result<Stamp, HandoffMergeError> {
let entry = StampedEntry::decode(bytes)
.map_err(|_error| HandoffMergeError::UndecodableEntry { key: key.to_vec() })?
.ok_or_else(|| HandoffMergeError::UndecodableEntry { key: key.to_vec() })?;
Ok(entry.stamp().clone())
}
fn collect_stored_entries<S: NodeStore + ?Sized>(
store: &S,
root: Hash,
) -> Result<Vec<StoredEntry>, HandoffMergeError> {
let mut entries = Vec::new();
collect_into(store, root, &mut entries)?;
Ok(entries)
}
fn collect_into<S: NodeStore + ?Sized>(
store: &S,
hash: Hash,
out: &mut Vec<StoredEntry>,
) -> Result<(), HandoffMergeError> {
match &*load_node(store, hash)? {
Node::Leaf(leaf) => {
out.extend(leaf.entries().iter().cloned());
Ok(())
}
Node::Internal(internal) => {
if internal.children().is_empty() {
return Err(HandoffMergeError::InvalidNode);
}
for (_separator, child_hash) in internal.children() {
collect_into(store, *child_hash, out)?;
}
Ok(())
}
}
}
fn load_node<S: NodeStore + ?Sized>(store: &S, hash: Hash) -> Result<Arc<Node>, HandoffMergeError> {
store
.get(&hash)
.map_err(|_error| HandoffMergeError::StoreRead { hash })?
.ok_or(HandoffMergeError::MissingNode { hash })
}
fn store_empty_leaf<S: NodeStore + ?Sized>(store: &mut S) -> Result<Hash, HandoffMergeError> {
let leaf = LeafNode::new(Vec::new()).map_err(|_error| HandoffMergeError::InvalidNode)?;
store
.put(&Node::Leaf(leaf))
.map_err(|_error| HandoffMergeError::InvalidNode)
}
#[cfg(test)]
#[path = "handoff_merge_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "handoff_merge_proptest.rs"]
mod proptest_tests;