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}