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
use anyhow::Result;
use cid::Cid;
pub use crdts::{map, Orswot};
use libipld_cbor::DagCborCodec;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{fmt::Display, hash::Hash, marker::PhantomData};

use noosphere_collections::hamt::{Hamt, Hash as HamtHash, Sha256};
use noosphere_storage::BlockStore;

use super::ChangelogIpld;

#[cfg(not(target_arch = "wasm32"))]
pub trait VersionedMapSendSync: Send + Sync {}

#[cfg(not(target_arch = "wasm32"))]
impl<T> VersionedMapSendSync for T where T: Send + Sync {}

#[cfg(target_arch = "wasm32")]
pub trait VersionedMapSendSync {}

#[cfg(target_arch = "wasm32")]
impl<T> VersionedMapSendSync for T {}

#[repr(transparent)]
#[derive(Ord, Eq, PartialEq, PartialOrd, Debug, Clone, Serialize, Deserialize)]
pub struct CidKey(pub Cid);

impl HamtHash for CidKey {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.hash().hash(state);
    }
}

impl Display for CidKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

pub trait VersionedMapKey:
    Serialize + DeserializeOwned + HamtHash + Clone + Eq + Ord + VersionedMapSendSync + Display
{
}

impl<T> VersionedMapKey for T where
    T: Serialize + DeserializeOwned + HamtHash + Clone + Eq + Ord + VersionedMapSendSync + Display
{
}

pub trait VersionedMapValue:
    Serialize + DeserializeOwned + Clone + Eq + Hash + VersionedMapSendSync
{
}

impl<T> VersionedMapValue for T where
    T: Serialize + DeserializeOwned + Clone + Eq + Hash + VersionedMapSendSync
{
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub enum MapOperation<Key, Value> {
    Add { key: Key, value: Value },
    Remove { key: Key },
}

#[derive(Debug, Default, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct VersionedMapIpld<Key, Value>
where
    Key: VersionedMapKey,
    Value: VersionedMapValue,
{
    /// A pointer to a HAMT
    pub hamt: Cid,
    // TODO: The size of this vec is implicitly limited by the IPLD block size
    // limit. This is probably fine most of the time; the vec only holds the
    // delta changes, and N<10 probably holds in the majority of cases. But, it
    // will be necessary to gracefully survive the outlier cases where N>~1000.
    // pub changelog: ChangelogIpld<MapOperation<Key, Value>>,
    pub changelog: Cid,

    #[serde(skip)]
    pub signature: PhantomData<(Key, Value)>,
}

impl<Key, Value> VersionedMapIpld<Key, Value>
where
    Key: VersionedMapKey,
    Value: VersionedMapValue,
{
    pub async fn try_load_hamt<S: BlockStore>(
        &self,
        store: &S,
    ) -> Result<Hamt<S, Value, Key, Sha256>> {
        Hamt::load(&self.hamt, store.clone()).await
    }

    pub async fn try_load_changelog<S: BlockStore>(
        &self,
        store: &S,
    ) -> Result<ChangelogIpld<MapOperation<Key, Value>>> {
        store.load::<DagCborCodec, _>(&self.changelog).await
    }

    // NOTE: We currently don't have a mechanism to prepuplate the store with
    // "empty" DAGs like a HAMT. So, we do it lazily by requiring async
    // initialization of this struct even when it is empty.
    pub async fn try_empty<S: BlockStore>(store: &mut S) -> Result<Self> {
        let mut hamt = Hamt::<S, Value, Key, Sha256>::new(store.clone());
        let changelog = ChangelogIpld::<MapOperation<Key, Value>>::default();

        let changelog_cid = store.save::<DagCborCodec, _>(&changelog).await?;
        let hamt_cid = hamt.flush().await?;

        Ok(VersionedMapIpld {
            hamt: hamt_cid,
            changelog: changelog_cid,
            signature: Default::default(),
        })
    }
}