wnfs_common/
blockstore.rs

1use crate::{
2    BlockStoreError, MAX_BLOCK_SIZE,
3    utils::{Arc, CondSend, CondSync},
4};
5use bytes::Bytes;
6use cid::Cid;
7use futures::Future;
8use ipld_core::cid::Version;
9use multihash::Multihash;
10use parking_lot::Mutex;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14//--------------------------------------------------------------------------------------------------
15// Constants
16//--------------------------------------------------------------------------------------------------
17
18/// The value representing the DAG-JSON codec.
19///
20/// - <https://ipld.io/docs/codecs/#known-codecs>
21/// - <https://github.com/multiformats/multicodec/blob/master/table.csv>
22pub const CODEC_DAG_JSON: u64 = 0x0129;
23
24/// The value representing the DAG-CBOR codec.
25///
26/// - <https://ipld.io/docs/codecs/#known-codecs>
27/// - <https://github.com/multiformats/multicodec/blob/master/table.csv>
28pub const CODEC_DAG_CBOR: u64 = 0x71;
29
30/// The value representing the DAG-Protobuf codec.
31///
32/// - <https://ipld.io/docs/codecs/#known-codecs>
33/// - <https://github.com/multiformats/multicodec/blob/master/table.csv>
34pub const CODEC_DAG_PB: u64 = 0x70;
35
36/// The value representing the raw codec.
37///
38/// - <https://ipld.io/docs/codecs/#known-codecs>
39/// - <https://github.com/multiformats/multicodec/blob/master/table.csv>
40pub const CODEC_RAW: u64 = 0x55;
41
42/// The multihash marker for blake3 hashes.
43///
44/// <https://github.com/multiformats/multicodec/blob/master/table.csv#L21>
45pub const MULTIHASH_BLAKE3: u64 = 0x1e;
46
47//--------------------------------------------------------------------------------------------------
48// Traits
49//--------------------------------------------------------------------------------------------------
50
51/// For types that implement block store operations like adding, getting content from the store.
52pub trait BlockStore: CondSync {
53    /// Retrieve a block from this store via its hash (`Cid`).
54    ///
55    /// If this store can't find the block, it may raise an error like `BlockNotFound`.
56    fn get_block(
57        &self,
58        cid: &Cid,
59    ) -> impl Future<Output = Result<Bytes, BlockStoreError>> + CondSend;
60
61    /// Put some bytes into the blockstore. These bytes should be encoded with the given codec.
62    ///
63    /// E.g. `CODEC_RAW` for raw bytes blocks, `CODEC_DAG_CBOR` for dag-cbor, etc.
64    ///
65    /// This codec will determine the codec encoded in the final `Cid` that's returned.
66    ///
67    /// If the codec is incorrect, this function won't fail, but any tools that depend on the
68    /// correctness of the codec may fail. (E.g. tools that follow the links of blocks).
69    ///
70    /// This funciton allows the blockstore to choose the hashing function itself.
71    /// The hashing function that was chosen will be readable from the `Cid` metadata.
72    ///
73    /// If you need control over the concrete hashing function that's used, see `put_block_keyed`.
74    fn put_block(
75        &self,
76        bytes: impl Into<Bytes> + CondSend,
77        codec: u64,
78    ) -> impl Future<Output = Result<Cid, BlockStoreError>> + CondSend {
79        let bytes = bytes.into();
80        async move {
81            let cid = self.create_cid(&bytes, codec)?;
82            self.put_block_keyed(cid, bytes).await?;
83            Ok(cid)
84        }
85    }
86
87    /// Put a block of data into this blockstore. The block's CID needs to match the CID given.
88    ///
89    /// It's up to the blockstore whether to check this fact or assume it when this function is called.
90    ///
91    /// The default implementation of `put_block` will use this function under the hood and use
92    /// the correct CID provided by the `create_cid` function.
93    ///
94    /// This is useful to be able to add blocks that were generated from other
95    /// clients with differently configured hashing functions to this blockstore.
96    fn put_block_keyed(
97        &self,
98        cid: Cid,
99        bytes: impl Into<Bytes> + CondSend,
100    ) -> impl Future<Output = Result<(), BlockStoreError>> + CondSend;
101
102    /// Find out whether a call to `get_block` would return with a result or not.
103    ///
104    /// This is useful for data exchange protocols to find out what needs to be fetched
105    /// externally and what doesn't.
106    fn has_block(
107        &self,
108        cid: &Cid,
109    ) -> impl Future<Output = Result<bool, BlockStoreError>> + CondSend;
110
111    // This should be the same in all implementations of BlockStore
112    fn create_cid(&self, bytes: &[u8], codec: u64) -> Result<Cid, BlockStoreError> {
113        // If there are too many bytes, abandon this task
114        if bytes.len() > MAX_BLOCK_SIZE {
115            return Err(BlockStoreError::MaximumBlockSizeExceeded(bytes.len()));
116        }
117
118        // Compute the Blake3 hash of the bytes
119        let hash = Multihash::wrap(MULTIHASH_BLAKE3, blake3::hash(bytes).as_bytes()).unwrap();
120
121        // Represent the hash as a V1 CID
122        let cid = Cid::new(Version::V1, codec, hash)?;
123
124        Ok(cid)
125    }
126}
127
128//--------------------------------------------------------------------------------------------------
129// Implementations
130//--------------------------------------------------------------------------------------------------
131
132impl<B: BlockStore> BlockStore for &B {
133    async fn get_block(&self, cid: &Cid) -> Result<Bytes, BlockStoreError> {
134        (**self).get_block(cid).await
135    }
136
137    async fn put_block(
138        &self,
139        bytes: impl Into<Bytes> + CondSend,
140        codec: u64,
141    ) -> Result<Cid, BlockStoreError> {
142        (**self).put_block(bytes, codec).await
143    }
144
145    async fn put_block_keyed(
146        &self,
147        cid: Cid,
148        bytes: impl Into<Bytes> + CondSend,
149    ) -> Result<(), BlockStoreError> {
150        (**self).put_block_keyed(cid, bytes).await
151    }
152
153    async fn has_block(&self, cid: &Cid) -> Result<bool, BlockStoreError> {
154        (**self).has_block(cid).await
155    }
156
157    fn create_cid(&self, bytes: &[u8], codec: u64) -> Result<Cid, BlockStoreError> {
158        (**self).create_cid(bytes, codec)
159    }
160}
161
162impl<B: BlockStore> BlockStore for Box<B> {
163    async fn get_block(&self, cid: &Cid) -> Result<Bytes, BlockStoreError> {
164        (**self).get_block(cid).await
165    }
166
167    async fn put_block(
168        &self,
169        bytes: impl Into<Bytes> + CondSend,
170        codec: u64,
171    ) -> Result<Cid, BlockStoreError> {
172        (**self).put_block(bytes, codec).await
173    }
174
175    async fn put_block_keyed(
176        &self,
177        cid: Cid,
178        bytes: impl Into<Bytes> + CondSend,
179    ) -> Result<(), BlockStoreError> {
180        (**self).put_block_keyed(cid, bytes).await
181    }
182
183    async fn has_block(&self, cid: &Cid) -> Result<bool, BlockStoreError> {
184        (**self).has_block(cid).await
185    }
186
187    fn create_cid(&self, bytes: &[u8], codec: u64) -> Result<Cid, BlockStoreError> {
188        (**self).create_cid(bytes, codec)
189    }
190}
191
192/// An in-memory block store to simulate IPFS.
193///
194/// IPFS is basically a glorified HashMap.
195#[derive(Debug, Default, Clone, Serialize, Deserialize)]
196pub struct MemoryBlockStore(
197    #[serde(serialize_with = "crate::utils::serialize_cid_map")]
198    #[serde(deserialize_with = "crate::utils::deserialize_cid_map")]
199    pub(crate) Arc<Mutex<HashMap<Cid, Bytes>>>,
200);
201
202impl MemoryBlockStore {
203    /// Creates a new in-memory block store.
204    pub fn new() -> Self {
205        Self::default()
206    }
207}
208
209impl BlockStore for MemoryBlockStore {
210    async fn get_block(&self, cid: &Cid) -> Result<Bytes, BlockStoreError> {
211        let bytes = self
212            .0
213            .lock()
214            .get(cid)
215            .ok_or(BlockStoreError::CIDNotFound(*cid))?
216            .clone();
217
218        Ok(bytes)
219    }
220
221    async fn put_block_keyed(
222        &self,
223        cid: Cid,
224        bytes: impl Into<Bytes> + CondSend,
225    ) -> Result<(), BlockStoreError> {
226        self.0.lock().insert(cid, bytes.into());
227
228        Ok(())
229    }
230
231    async fn has_block(&self, cid: &Cid) -> Result<bool, BlockStoreError> {
232        Ok(self.0.lock().contains_key(cid))
233    }
234}