parent
eac2c26a87
commit
bf157b25fc
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@ |
||||
data |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,28 @@ |
||||
<!-- |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
--> |
||||
|
||||
<script type="ts"> |
||||
import { Button } from "flowbite-svelte"; |
||||
import { link } from "svelte-spa-router"; |
||||
import Install from "../../../../ng-app/src/lib/Install.svelte"; |
||||
import { push } from "svelte-spa-router"; |
||||
import { onMount, onDestroy } from "svelte"; |
||||
|
||||
let display_has_wallets_warning = false; |
||||
let unsubscribe; |
||||
onMount(() => {}); |
||||
|
||||
onDestroy(() => { |
||||
unsubscribe(); |
||||
}); |
||||
</script> |
||||
|
||||
<Install {display_has_wallets_warning} /> |
@ -0,0 +1,20 @@ |
||||
[package] |
||||
name = "stores-rocksdb" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
license = "MIT/Apache-2.0" |
||||
authors = ["Niko PLP <niko@nextgraph.org>"] |
||||
description = "P2P stores based on LMDB for NextGraph" |
||||
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs" |
||||
|
||||
[dependencies] |
||||
p2p-repo = { path = "../p2p-repo" } |
||||
serde = { version = "1.0.142", features = ["derive"] } |
||||
serde_bare = "0.5.0" |
||||
tempfile = "3" |
||||
hex = "0.4.3" |
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rocksdb] |
||||
git = "https://git.nextgraph.org/NextGraph/rust-rocksdb.git" |
||||
branch = "master" |
||||
features = [ ] |
@ -0,0 +1,424 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use p2p_repo::kcv_store::*; |
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
|
||||
use p2p_repo::log::*; |
||||
|
||||
use std::path::Path; |
||||
use std::path::PathBuf; |
||||
use std::sync::RwLockReadGuard; |
||||
use std::sync::{Arc, RwLock}; |
||||
|
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::error::Error; |
||||
|
||||
use rocksdb::{ |
||||
ColumnFamilyDescriptor, Direction, Env, ErrorKind, IteratorMode, Options, SingleThreaded, |
||||
TransactionDB, TransactionDBOptions, DB, |
||||
}; |
||||
|
||||
pub struct RocksdbTransaction<'a> { |
||||
store: &'a RocksdbKCVStore, |
||||
tx: Option<rocksdb::Transaction<'a, TransactionDB>>, |
||||
} |
||||
|
||||
impl<'a> RocksdbTransaction<'a> { |
||||
fn commit(&mut self) { |
||||
self.tx.take().unwrap().commit().unwrap(); |
||||
} |
||||
fn tx(&self) -> &rocksdb::Transaction<'a, TransactionDB> { |
||||
self.tx.as_ref().unwrap() |
||||
} |
||||
} |
||||
|
||||
impl<'a> ReadTransaction for RocksdbTransaction<'a> { |
||||
fn get_all_keys_and_values( |
||||
&self, |
||||
prefix: u8, |
||||
key_size: usize, |
||||
key_prefix: Vec<u8>, |
||||
suffix: Option<u8>, |
||||
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> { |
||||
self.store |
||||
.get_all_keys_and_values(prefix, key_size, key_prefix, suffix) |
||||
} |
||||
|
||||
/// Load a single value property from the store.
|
||||
fn get(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<Vec<u8>, StorageError> { |
||||
let property = RocksdbKCVStore::compute_property(prefix, key, suffix); |
||||
let mut res = self |
||||
.tx() |
||||
.get_for_update(property, true) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
match res { |
||||
Some(val) => Ok(val), |
||||
None => Err(StorageError::NotFound), |
||||
} |
||||
} |
||||
|
||||
/// Load all the values of a property from the store.
|
||||
fn get_all( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
) -> Result<Vec<Vec<u8>>, StorageError> { |
||||
unimplemented!(); |
||||
} |
||||
|
||||
/// Check if a specific value exists for a property from the store.
|
||||
fn has_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: &Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = RocksdbKCVStore::compute_property(prefix, key, suffix); |
||||
let exists = self |
||||
.tx() |
||||
.get_for_update(property, true) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
match exists { |
||||
Some(stored_value) => { |
||||
if stored_value.eq(value) { |
||||
Ok(()) |
||||
} else { |
||||
Err(StorageError::DifferentValue) |
||||
} |
||||
} |
||||
None => Err(StorageError::NotFound), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> WriteTransaction for RocksdbTransaction<'a> { |
||||
/// Save a property value to the store.
|
||||
fn put( |
||||
&mut self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: &Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = RocksdbKCVStore::compute_property(prefix, key, suffix); |
||||
self.tx() |
||||
.put(property, value) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Replace the property of a key (single value) to the store.
|
||||
fn replace( |
||||
&mut self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: &Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = RocksdbKCVStore::compute_property(prefix, key, suffix); |
||||
|
||||
self.tx() |
||||
.put(property, value) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete a property from the store.
|
||||
fn del(&mut self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<(), StorageError> { |
||||
let property = RocksdbKCVStore::compute_property(prefix, key, suffix); |
||||
let res = self.tx().delete(property); |
||||
if res.is_err() { |
||||
if let ErrorKind::NotFound = res.unwrap_err().kind() { |
||||
return Ok(()); |
||||
} |
||||
return Err(StorageError::BackendError); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete a specific value for a property from the store.
|
||||
fn del_property_value( |
||||
&mut self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: &Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = RocksdbKCVStore::compute_property(prefix, key, suffix); |
||||
let exists = self |
||||
.tx() |
||||
.get_for_update(property.clone(), true) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
match exists { |
||||
Some(val) => { |
||||
if val.eq(value) { |
||||
self.tx() |
||||
.delete(property) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
} |
||||
} |
||||
None => return Err(StorageError::DifferentValue), |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete all properties of a key from the store.
|
||||
fn del_all( |
||||
&mut self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
all_suffixes: &[u8], |
||||
) -> Result<(), StorageError> { |
||||
for suffix in all_suffixes { |
||||
self.del(prefix, key, Some(*suffix))?; |
||||
} |
||||
if all_suffixes.is_empty() { |
||||
self.del(prefix, key, None)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub struct RocksdbKCVStore { |
||||
/// the main store where all the properties of keys are stored
|
||||
main_db: TransactionDB, |
||||
/// path for the storage backend data
|
||||
path: String, |
||||
} |
||||
|
||||
fn compare<T: Ord>(a: &[T], b: &[T]) -> std::cmp::Ordering { |
||||
let mut iter_b = b.iter(); |
||||
for v in a { |
||||
match iter_b.next() { |
||||
Some(w) => match v.cmp(w) { |
||||
std::cmp::Ordering::Equal => continue, |
||||
ord => return ord, |
||||
}, |
||||
None => break, |
||||
} |
||||
} |
||||
return a.len().cmp(&b.len()); |
||||
} |
||||
|
||||
impl ReadTransaction for RocksdbKCVStore { |
||||
fn get_all_keys_and_values( |
||||
&self, |
||||
prefix: u8, |
||||
key_size: usize, |
||||
key_prefix: Vec<u8>, |
||||
suffix: Option<u8>, |
||||
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> { |
||||
if key_prefix.len() > key_size { |
||||
return Err(StorageError::InvalidValue); |
||||
} |
||||
|
||||
let mut vec_key_start = key_prefix.clone(); |
||||
let mut trailing_zeros = vec![0u8; key_size - key_prefix.len()]; |
||||
vec_key_start.append(&mut trailing_zeros); |
||||
|
||||
let mut vec_key_end = key_prefix.clone(); |
||||
let mut trailing_max = vec![255u8; key_size - key_prefix.len()]; |
||||
vec_key_end.append(&mut trailing_max); |
||||
|
||||
let property_start = Self::compute_property(prefix, &vec_key_start, suffix); |
||||
let property_end = |
||||
Self::compute_property(prefix, &vec_key_end, Some(suffix.unwrap_or(255u8))); |
||||
|
||||
let mut iter = self |
||||
.main_db |
||||
.iterator(IteratorMode::From(&property_start, Direction::Forward)); |
||||
let mut vector: Vec<(Vec<u8>, Vec<u8>)> = vec![]; |
||||
while let res = iter.next() { |
||||
match res { |
||||
Some(Ok(val)) => { |
||||
match compare(&val.0, property_end.as_slice()) { |
||||
std::cmp::Ordering::Less | std::cmp::Ordering::Equal => { |
||||
if suffix.is_some() { |
||||
if val.0.len() < (key_size + 2) |
||||
|| val.0[1 + key_size] != suffix.unwrap() |
||||
{ |
||||
continue; |
||||
} |
||||
// } else if val.0.len() > (key_size + 1) {
|
||||
// continue;
|
||||
} |
||||
vector.push((val.0.to_vec(), val.1.to_vec())); |
||||
} |
||||
_ => {} //,
|
||||
} |
||||
} |
||||
Some(Err(_e)) => return Err(StorageError::BackendError), |
||||
None => { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
Ok(vector) |
||||
} |
||||
|
||||
/// Load a single value property from the store.
|
||||
fn get(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<Vec<u8>, StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let mut res = self |
||||
.main_db |
||||
.get(property) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
match res { |
||||
Some(val) => Ok(val), |
||||
None => Err(StorageError::NotFound), |
||||
} |
||||
} |
||||
|
||||
/// Load all the values of a property from the store.
|
||||
fn get_all( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
) -> Result<Vec<Vec<u8>>, StorageError> { |
||||
unimplemented!(); |
||||
} |
||||
|
||||
/// Check if a specific value exists for a property from the store.
|
||||
fn has_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: &Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
let property = Self::compute_property(prefix, key, suffix); |
||||
let exists = self |
||||
.main_db |
||||
.get(property) |
||||
.map_err(|e| StorageError::BackendError)?; |
||||
match exists { |
||||
Some(stored_value) => { |
||||
if stored_value.eq(value) { |
||||
Ok(()) |
||||
} else { |
||||
Err(StorageError::DifferentValue) |
||||
} |
||||
} |
||||
None => Err(StorageError::NotFound), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl KCVStore for RocksdbKCVStore { |
||||
fn write_transaction( |
||||
&self, |
||||
method: &mut dyn FnMut(&mut dyn WriteTransaction) -> Result<(), StorageError>, |
||||
) -> Result<(), StorageError> { |
||||
let tx = self.main_db.transaction(); |
||||
|
||||
let mut transaction = RocksdbTransaction { |
||||
store: self, |
||||
tx: Some(tx), |
||||
}; |
||||
let res = method(&mut transaction); |
||||
if res.is_ok() { |
||||
transaction.commit(); |
||||
//lock.sync(true);
|
||||
} |
||||
res |
||||
} |
||||
|
||||
/// Save a property value to the store.
|
||||
fn put( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
self.write_transaction(&mut |tx| tx.put(prefix, key, suffix, &value)) |
||||
} |
||||
|
||||
/// Replace the property of a key (single value) to the store.
|
||||
fn replace( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
self.write_transaction(&mut |tx| tx.replace(prefix, key, suffix, &value)) |
||||
} |
||||
|
||||
/// Delete a property from the store.
|
||||
fn del(&self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<(), StorageError> { |
||||
self.write_transaction(&mut |tx| tx.del(prefix, key, suffix)) |
||||
} |
||||
|
||||
/// Delete a specific value for a property from the store.
|
||||
fn del_property_value( |
||||
&self, |
||||
prefix: u8, |
||||
key: &Vec<u8>, |
||||
suffix: Option<u8>, |
||||
value: Vec<u8>, |
||||
) -> Result<(), StorageError> { |
||||
self.write_transaction(&mut |tx| tx.del_property_value(prefix, key, suffix, &value)) |
||||
} |
||||
|
||||
/// Delete all properties of a key from the store.
|
||||
fn del_all(&self, prefix: u8, key: &Vec<u8>, all_suffixes: &[u8]) -> Result<(), StorageError> { |
||||
for suffix in all_suffixes { |
||||
self.del(prefix, key, Some(*suffix))?; |
||||
} |
||||
if all_suffixes.is_empty() { |
||||
self.del(prefix, key, None)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl RocksdbKCVStore { |
||||
pub fn path(&self) -> PathBuf { |
||||
PathBuf::from(&self.path) |
||||
} |
||||
|
||||
fn compute_property(prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Vec<u8> { |
||||
let mut new: Vec<u8> = Vec::with_capacity(key.len() + 2); |
||||
new.push(prefix); |
||||
new.extend(key); |
||||
if suffix.is_some() { |
||||
new.push(suffix.unwrap()) |
||||
} |
||||
new |
||||
} |
||||
|
||||
/// Opens the store and returns a KCVStore object that should be kept and used to manipulate the properties
|
||||
/// The key is the encryption key for the data at rest.
|
||||
pub fn open<'a>(path: &Path, key: [u8; 32]) -> Result<RocksdbKCVStore, StorageError> { |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let env = Env::enc_env(key).unwrap(); |
||||
opts.set_env(&env); |
||||
let tx_options = TransactionDBOptions::new(); |
||||
let db: TransactionDB = |
||||
TransactionDB::open_cf(&opts, &tx_options, &path, vec!["cf0", "cf1"]).unwrap(); |
||||
|
||||
log_info!("created db with Rocksdb Version: {}", Env::version()); |
||||
|
||||
Ok(RocksdbKCVStore { |
||||
main_db: db, |
||||
path: path.to_str().unwrap().to_string(), |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// pub mod repo_store;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))] |
||||
pub mod kcv_store; |
@ -0,0 +1,992 @@ |
||||
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
||||
// All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use p2p_repo::store::*; |
||||
use p2p_repo::types::*; |
||||
use p2p_repo::utils::*; |
||||
|
||||
use p2p_repo::log::*; |
||||
use std::path::Path; |
||||
use std::sync::{Arc, RwLock}; |
||||
|
||||
use serde::{Deserialize, Serialize}; |
||||
use serde_bare::error::Error; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct LmdbRepoStore { |
||||
/// the main store where all the repo blocks are stored
|
||||
main_store: SingleStore<LmdbDatabase>, |
||||
/// store for the pin boolean, recently_used timestamp, and synced boolean
|
||||
meta_store: SingleStore<LmdbDatabase>, |
||||
/// store for the expiry timestamp
|
||||
expiry_store: MultiIntegerStore<LmdbDatabase, u32>, |
||||
/// store for the LRU list
|
||||
recently_used_store: MultiIntegerStore<LmdbDatabase, u32>, |
||||
/// the opened environment so we can create new transactions
|
||||
environment: Arc<RwLock<Rkv<LmdbEnvironment>>>, |
||||
} |
||||
|
||||
// TODO: versioning V0
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
||||
struct BlockMeta { |
||||
pub pin: bool, |
||||
pub last_used: Timestamp, |
||||
pub synced: bool, |
||||
} |
||||
|
||||
impl RepoStore for LmdbRepoStore { |
||||
/// Retrieves a block from the storage backend.
|
||||
fn get(&self, block_id: &BlockId) -> Result<Block, StorageError> { |
||||
let lock = self.environment.read().unwrap(); |
||||
let reader = lock.read().unwrap(); |
||||
let block_id_ser = serde_bare::to_vec(&block_id).unwrap(); |
||||
let block_ser_res = self.main_store.get(&reader, block_id_ser.clone()); |
||||
match block_ser_res { |
||||
Err(e) => Err(StorageError::BackendError), |
||||
Ok(None) => Err(StorageError::NotFound), |
||||
Ok(Some(block_ser)) => { |
||||
// updating recently_used
|
||||
// first getting the meta for this BlockId
|
||||
let meta_ser = self.meta_store.get(&reader, block_id_ser.clone()).unwrap(); |
||||
match meta_ser { |
||||
Some(meta_value) => { |
||||
let mut meta = |
||||
serde_bare::from_slice::<BlockMeta>(&meta_value.to_bytes().unwrap()) |
||||
.unwrap(); |
||||
if meta.synced { |
||||
let mut writer = lock.write().unwrap(); |
||||
let now = now_timestamp(); |
||||
if !meta.pin { |
||||
// we remove the previous timestamp (last_used) from recently_used_store
|
||||
self.remove_from_lru(&mut writer, &block_id_ser, &meta.last_used) |
||||
.unwrap(); |
||||
// we add an entry to recently_used_store with now
|
||||
self.add_to_lru(&mut writer, &block_id_ser, &now).unwrap(); |
||||
} |
||||
// we save the new meta (with last_used:now)
|
||||
meta.last_used = now; |
||||
let new_meta_ser = serde_bare::to_vec(&meta).unwrap(); |
||||
self.meta_store |
||||
.put( |
||||
&mut writer, |
||||
block_id_ser, |
||||
&Value::Blob(new_meta_ser.as_slice()), |
||||
) |
||||
.unwrap(); |
||||
// commit
|
||||
writer.commit().unwrap(); |
||||
} |
||||
} |
||||
_ => {} // there is no meta. we do nothing since we start to record LRU only once synced == true.
|
||||
} |
||||
|
||||
match serde_bare::from_slice::<Block>(&block_ser.to_bytes().unwrap()) { |
||||
Err(_e) => Err(StorageError::InvalidValue), |
||||
Ok(o) => { |
||||
if o.id() != *block_id { |
||||
log_debug!( |
||||
"Invalid ObjectId.\nExp: {:?}\nGot: {:?}\nContent: {:?}", |
||||
block_id, |
||||
o.id(), |
||||
o |
||||
); |
||||
panic!("CORRUPTION OF DATA !"); |
||||
} |
||||
Ok(o) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Adds a block in the storage backend.
|
||||
/// The block is persisted to disk.
|
||||
/// Returns the BlockId of the Block.
|
||||
fn put(&self, block: &Block) -> Result<BlockId, StorageError> { |
||||
let block_ser = serde_bare::to_vec(&block).unwrap(); |
||||
|
||||
let block_id = block.id(); |
||||
let block_id_ser = serde_bare::to_vec(&block_id).unwrap(); |
||||
|
||||
let lock = self.environment.read().unwrap(); |
||||
let mut writer = lock.write().unwrap(); |
||||
|
||||
// TODO: check if the block is already in store? if yes, don't put it again.
|
||||
// I didnt do it yet because it is extra cost. surely a get on the store is lighter than a put
|
||||
// but doing a get in additing to a put for every call, is probably even costlier. better to deal with that at the higher level
|
||||
|
||||
self.main_store |
||||
.put( |
||||
&mut writer, |
||||
&block_id_ser, |
||||
&Value::Blob(block_ser.as_slice()), |
||||
) |
||||
.unwrap(); |
||||
|
||||
// if it has an expiry, adding the BlockId to the expiry_store
|