1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
use super::{KeyValueChange, Node, HAMT_VERSION};
use crate::{serializable::HamtSerializable, Hasher};
use anyhow::Result;
use async_trait::async_trait;
use libipld::Cid;
use semver::Version;
use serde::{de::DeserializeOwned, Serialize};
use std::hash::Hash;
use wnfs_common::{
utils::{Arc, CondSync},
BlockStore, Link, Storable,
};
//--------------------------------------------------------------------------------------------------
// Type Definitions
//--------------------------------------------------------------------------------------------------
/// Hash Array Mapped Trie (HAMT) is an implementation of an associative array that combines the characteristics
/// of a hash table and an array mapped trie.
///
/// This type wraps the actual implementation which can be found in the [`Node`](crate::Node).
///
/// # Examples
///
/// ```
/// use wnfs_hamt::Hamt;
///
/// let hamt = Hamt::<String, usize>::new();
/// println!("HAMT: {:?}", hamt);
/// ```
#[derive(Debug, Clone)]
pub struct Hamt<K: CondSync, V: CondSync, H = blake3::Hasher>
where
H: Hasher + CondSync,
{
pub root: Arc<Node<K, V, H>>,
pub version: Version,
}
//--------------------------------------------------------------------------------------------------
// Implementations
//--------------------------------------------------------------------------------------------------
impl<K: CondSync, V: CondSync, H: Hasher + CondSync> Hamt<K, V, H> {
/// Creates a new empty HAMT.
///
/// # Examples
///
/// ```
/// use wnfs_hamt::Hamt;
///
/// let hamt = Hamt::<String, usize>::new();
/// println!("HAMT: {:?}", hamt);
/// ```
pub fn new() -> Self {
Self {
root: Arc::new(Node::default()),
version: HAMT_VERSION,
}
}
/// Creates a new `HAMT` with the given root node.
///
/// # Examples
///
/// ```
/// use std::sync::Arc;
/// use wnfs_hamt::{Hamt, Node};
///
/// let hamt = Hamt::<String, usize>::with_root(Arc::new(Node::default()));
///
/// println!("HAMT: {:?}", hamt);
/// ```
pub fn with_root(root: Arc<Node<K, V, H>>) -> Self {
Self {
root,
version: HAMT_VERSION,
}
}
/// Gets the difference between two HAMTs at the key-value level.
///
/// # Examples
///
/// ```
/// use std::sync::Arc;
/// use wnfs_hamt::{Hamt, Node};
/// use wnfs_common::MemoryBlockStore;
///
/// #[async_std::main]
/// async fn main() {
/// let store = &MemoryBlockStore::default();
///
/// let main_hamt = Hamt::<String, usize>::with_root({
/// let mut node = Arc::new(Node::default());
/// node.set("foo".into(), 400, store).await.unwrap();
/// node.set("bar".into(), 500, store).await.unwrap();
/// node
/// });
///
/// let other_hamt = Hamt::<String, usize>::with_root({
/// let mut node = Arc::new(Node::default());
/// node.set("foo".into(), 200, store).await.unwrap();
/// node.set("qux".into(), 600, store).await.unwrap();
/// node
/// });
///
/// let diff = main_hamt.diff(&other_hamt, store).await.unwrap();
///
/// println!("diff: {:#?}", diff);
/// }
pub async fn diff(
&self,
other: &Self,
store: &impl BlockStore,
) -> Result<Vec<KeyValueChange<K, V>>>
where
K: Storable + Clone + Eq + Hash + AsRef<[u8]>,
V: Storable + Clone + Eq,
K::Serializable: Serialize + DeserializeOwned,
V::Serializable: Serialize + DeserializeOwned,
{
super::diff(
Link::from(Arc::clone(&self.root)),
Link::from(Arc::clone(&other.root)),
store,
)
.await
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl<K, V, H> Storable for Hamt<K, V, H>
where
K: Storable + CondSync,
V: Storable + CondSync,
K::Serializable: Serialize + DeserializeOwned,
V::Serializable: Serialize + DeserializeOwned,
H: Hasher + CondSync,
{
type Serializable = HamtSerializable<K::Serializable, V::Serializable>;
async fn to_serializable(&self, store: &impl BlockStore) -> Result<Self::Serializable> {
Ok(HamtSerializable {
root: self.root.to_serializable(store).await?,
version: self.version.clone(),
structure: "hamt".to_string(),
})
}
async fn from_serializable(
_cid: Option<&Cid>,
serializable: Self::Serializable,
) -> Result<Self> {
Ok(Self {
root: Arc::new(Node::from_serializable(None, serializable.root).await?),
version: serializable.version,
})
}
}
impl<K: CondSync, V: CondSync, H: Hasher + CondSync> Default for Hamt<K, V, H> {
fn default() -> Self {
Self::new()
}
}
impl<K: CondSync, V: CondSync, H> PartialEq for Hamt<K, V, H>
where
K: Storable + PartialEq + CondSync,
V: Storable + PartialEq + CondSync,
K::Serializable: Serialize + DeserializeOwned,
V::Serializable: Serialize + DeserializeOwned,
H: Hasher + CondSync,
{
fn eq(&self, other: &Self) -> bool {
self.root == other.root && self.version == other.version
}
}
//--------------------------------------------------------------------------------------------------
// Tests
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use wnfs_common::MemoryBlockStore;
#[async_std::test]
async fn hamt_can_encode_decode_as_cbor() {
let store = &MemoryBlockStore::default();
let root = Arc::new(Node::default());
let hamt: Hamt<String, i32> = Hamt::with_root(root);
let hamt_cid = hamt.store(store).await.unwrap();
let decoded_hamt = Hamt::load(&hamt_cid, store).await.unwrap();
assert_eq!(hamt, decoded_hamt);
}
}
#[cfg(test)]
mod snapshot_tests {
use super::*;
use wnfs_common::utils::SnapshotBlockStore;
#[async_std::test]
async fn test_hamt() {
let store = &SnapshotBlockStore::default();
let node = &mut Arc::new(Node::<[u8; 4], String>::default());
for i in 0..99_u32 {
node.set(i.to_le_bytes(), i.to_string(), store)
.await
.unwrap();
}
let hamt = Hamt::with_root(Arc::clone(node));
let cid = hamt.store(store).await.unwrap();
let hamt = store.get_block_snapshot(&cid).await.unwrap();
insta::assert_json_snapshot!(hamt);
}
}