Skip to content

Commit 4c31640

Browse files
author
Ole Krüger
committed
feat(durable-storage): support persisting node without its key value
1 parent 6b8df3c commit 4c31640

8 files changed

Lines changed: 664 additions & 446 deletions

File tree

durable-storage/src/avl/node.rs

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
use std::borrow::Borrow;
88
use std::cmp::Ordering;
9+
use std::ops::Deref;
910
use std::sync::OnceLock;
1011

1112
use bincode::Decode;
@@ -22,6 +23,8 @@ use octez_riscv_data::hash::HashFold;
2223
use octez_riscv_data::mode::Mode;
2324
use octez_riscv_data::mode::Normal;
2425
use octez_riscv_data::mode::Prove;
26+
use octez_riscv_data::serialisation::deserialise;
27+
use octez_riscv_data::serialisation::serialise;
2528
use perfect_derive::perfect_derive;
2629

2730
use super::resolver::LazyTreeId;
@@ -32,6 +35,10 @@ use crate::avl::resolver::AvlResolver;
3235
use crate::errors::Error;
3336
use crate::errors::OperationalError;
3437
use crate::key::Key;
38+
use crate::storage::KeyValueStore;
39+
use crate::storage::Loadable;
40+
use crate::storage::Storable;
41+
use crate::storage::StoreOptions;
3542

3643
/// Metadata of a [`Node`] needed for accesses.
3744
#[derive(Clone, Default, Debug, Encode, Decode)]
@@ -42,22 +49,12 @@ pub(crate) struct Meta {
4249
balance_factor: i64,
4350
}
4451

45-
/// A serialisable representation of [`Meta`].
52+
/// This type is a compact serialised form of a [`Node`] with metadata and child subtree hashes.
4653
#[derive(Encode, Decode)]
47-
pub(super) struct MetaHashRepresentation<K: Borrow<self::Key>> {
48-
key: K,
49-
balance_factor: i64,
50-
}
51-
52-
/// A serialisable representation of [`Node`].
53-
#[derive(Encode, Decode)]
54-
pub(super) struct NodeHashRepresentation<Data, K: Borrow<self::Key>, H: Borrow<self::Hash>> {
55-
meta: MetaHashRepresentation<K>,
56-
data: Data,
57-
// The hash of the left subtree.
58-
left: H,
59-
// The hash of the right subtree.
60-
right: H,
54+
struct StoredNode {
55+
meta: Meta,
56+
left: Hash,
57+
right: Hash,
6158
}
6259

6360
/// A node that supports rebalancing and Merklisation.
@@ -75,25 +72,6 @@ pub struct Node<TreeId, M: Mode> {
7572
hash: OnceLock<Hash>,
7673
}
7774

78-
impl<TreeId, M: BytesMode + AtomMode> From<NodeHashRepresentation<Bytes<M>, Key, Hash>>
79-
for Node<TreeId, M>
80-
where
81-
TreeId: From<Hash>,
82-
{
83-
fn from(node_repr: NodeHashRepresentation<Bytes<M>, Key, Hash>) -> Self {
84-
Node {
85-
meta: Atom::new(Meta {
86-
key: node_repr.meta.key,
87-
balance_factor: node_repr.meta.balance_factor,
88-
}),
89-
data: node_repr.data,
90-
left: TreeId::from(node_repr.left),
91-
right: TreeId::from(node_repr.right),
92-
hash: OnceLock::new(),
93-
}
94-
}
95-
}
96-
9775
impl Node<LazyTreeId, Normal> {
9876
/// Converts the [`Node`] to [`Prove`] mode.
9977
pub fn into_proof(self) -> Node<ProveTreeId, Prove<'static>> {
@@ -170,31 +148,12 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
170148
}
171149
}
172150

173-
/// Converts the [`Node`] to an encoded, serialisable representation,
174-
/// [`NodeHashRepresentation`], potentially re-hashing uncached [`Node`]s.
175-
pub(crate) fn to_encode<'a>(&'a self) -> impl Encode + 'a
176-
where
177-
Bytes<M>: Encode,
178-
TreeId: Foldable<HashFold>,
179-
{
180-
NodeHashRepresentation {
181-
meta: MetaHashRepresentation {
182-
key: &self.meta.key,
183-
balance_factor: self.meta.balance_factor,
184-
},
185-
data: &self.data,
186-
left: Hash::from_foldable(&self.left),
187-
right: Hash::from_foldable(&self.right),
188-
}
189-
}
190-
191151
/// Returns the hash of this node.
192152
///
193153
/// If the hash has been cached, the memo is returned. Otherwise, the hash is calculated and
194154
/// cached.
195155
pub(crate) fn hash(&self) -> &Hash
196156
where
197-
TreeId: Foldable<HashFold>,
198157
Atom<Meta, M>: Foldable<HashFold>,
199158
Bytes<M>: Foldable<HashFold>,
200159
TreeId: Foldable<HashFold>,
@@ -204,7 +163,7 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
204163

205164
#[inline]
206165
/// The difference in heights between child branches.
207-
pub(super) fn balance_factor(&self) -> i64 {
166+
pub(crate) fn balance_factor(&self) -> i64 {
208167
self.meta.balance_factor
209168
}
210169

@@ -216,10 +175,16 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
216175

217176
#[inline]
218177
/// The [`Key`] used for determining the [`Node`].
219-
pub(super) fn key(&self) -> &Key {
178+
pub(crate) fn key(&self) -> &Key {
220179
&self.meta.key
221180
}
222181

182+
/// Retrieve the value associated with this node.
183+
#[cfg(all(test, feature = "rocksdb"))]
184+
pub(crate) fn value(&self) -> &Bytes<M> {
185+
&self.data
186+
}
187+
223188
/// Rebalance the subtree of the [`Node`] so that the difference in height between child
224189
/// branches is in the range of -1..=1.
225190
///
@@ -832,6 +797,80 @@ impl<TreeId, M: BytesMode + AtomMode> Node<TreeId, M> {
832797
}
833798
}
834799

800+
impl<TreeId: Storable> Storable for Node<TreeId, Normal> {
801+
fn store(
802+
&self,
803+
store: &impl KeyValueStore,
804+
options: &StoreOptions,
805+
) -> Result<(), OperationalError> {
806+
// The stored representation is more compact. We don't include the `data` field, as that
807+
// should be written to the KV store separately.
808+
let repr = StoredNode {
809+
meta: self.meta.deref().clone(),
810+
left: Hash::from_foldable(&self.left),
811+
right: Hash::from_foldable(&self.right),
812+
};
813+
814+
let &id = self.hash();
815+
let bytes = serialise(repr)?;
816+
store.blob_set(id, bytes)?;
817+
818+
// Are we in charge of writing the value data to the KV store?
819+
if options.node_data() {
820+
let key: &[u8] = self.meta.key.as_ref();
821+
let value: &[u8] = self.data.borrow();
822+
store.set(key, value)?;
823+
}
824+
825+
if options.deep() {
826+
self.left.store(store, options)?;
827+
self.right.store(store, options)?;
828+
}
829+
830+
Ok(())
831+
}
832+
}
833+
834+
impl<TreeId: Loadable> Loadable for Node<TreeId, Normal> {
835+
fn load(id: Hash, store: &impl KeyValueStore) -> Result<Self, OperationalError> {
836+
let StoredNode { meta, left, right } = {
837+
let bytes =
838+
store
839+
.blob_get(id)
840+
.map_err(|error| OperationalError::CommitDataMissing {
841+
root: id,
842+
source: Box::new(error),
843+
})?;
844+
deserialise(bytes.as_ref())?
845+
};
846+
847+
let meta = Atom::new(meta);
848+
849+
// The stored representation does not include the `data` field, so we need to load it
850+
// separately from the KV store.
851+
let data = {
852+
let bytes = store.get(meta.key.as_ref()).map_err(|error| {
853+
OperationalError::CommitValueMissing {
854+
key: meta.key.clone(),
855+
source: Box::new(error),
856+
}
857+
})?;
858+
Bytes::from(bytes.as_ref())
859+
};
860+
861+
let left = TreeId::load(left, store)?;
862+
let right = TreeId::load(right, store)?;
863+
864+
Ok(Self {
865+
meta,
866+
data,
867+
left,
868+
right,
869+
hash: OnceLock::new(),
870+
})
871+
}
872+
}
873+
835874
impl<F, TreeId, M> Foldable<F> for Node<TreeId, M>
836875
where
837876
F: Fold,

0 commit comments

Comments
 (0)