-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feature/jmt 1/4] Jmt: Integration of Storage traits with crate jmt #891
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/// Integration of jmt::JellyfishMerkleTree with the Storage traits. | ||
pub mod jmt_integration; | ||
|
||
// Re-export dependencies from the jmt crate necessary for defining implementations | ||
// of the Mappable trait required by the JellyfishMerkleTree integration. | ||
pub use jmt; |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,245 @@ | ||||||||||
use core::marker::PhantomData; | ||||||||||
|
||||||||||
use crate::storage::{ | ||||||||||
Mappable, | ||||||||||
StorageInspect, | ||||||||||
StorageMutate, | ||||||||||
}; | ||||||||||
|
||||||||||
use alloc::{ | ||||||||||
sync::Arc, | ||||||||||
vec::Vec, | ||||||||||
}; | ||||||||||
|
||||||||||
use jmt::storage::{ | ||||||||||
HasPreimage, | ||||||||||
LeafNode as JmtLeafNode, | ||||||||||
Node as JmtNode, | ||||||||||
NodeBatch as JmtNodeBatch, | ||||||||||
NodeKey as JmtNodeKey, | ||||||||||
TreeReader, | ||||||||||
TreeWriter, | ||||||||||
}; | ||||||||||
use spin::{ | ||||||||||
RwLock, | ||||||||||
RwLockReadGuard, | ||||||||||
RwLockWriteGuard, | ||||||||||
}; | ||||||||||
|
||||||||||
#[derive(Debug, Clone)] | ||||||||||
pub struct JellyfishMerkleTreeStorage< | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps it will be more obvious when I look at the following PRs, but why do we need to declare a specific storage type for the JMTs? I don't find anything directly equivalent in our SMT implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe
and at a later point fuel-vm/fuel-merkle/src/sparse/merkle_tree.rs Lines 193 to 196 in c329ea7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right yeah, perhaps we should just omit |
||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> { | ||||||||||
inner: Arc<RwLock<StorageType>>, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels weird to dictate the lock type over the Storage. Shouldn't we just use traits to dictate the storage behavior, and let any potential locking mechanism be an implementation detail of the particular storage implementation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed. In practice I don't think we'll need a Lock, and we can use RefCell instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a cell even? Can't this just hold the #[derive(Debug)]
pub struct MerkleTree<TableType, StorageType> {
root_node: Node,
storage: StorageType,
phantom_table: PhantomData<TableType>,
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should, but again it's because of a constraint on the Jmt trait signatures. If we decide to fork jmt at some point, we can amend the trait signatures to avoid having to use cells or locks |
||||||||||
phantom_table: PhantomData<( | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageError, | ||||||||||
)>, | ||||||||||
} | ||||||||||
|
||||||||||
impl< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> | ||||||||||
JellyfishMerkleTreeStorage< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> | ||||||||||
{ | ||||||||||
pub fn storage_read(&self) -> RwLockReadGuard<StorageType> { | ||||||||||
self.inner.read() | ||||||||||
} | ||||||||||
|
||||||||||
pub fn storage_write(&self) -> RwLockWriteGuard<StorageType> { | ||||||||||
self.inner.write() | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
impl< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> TreeWriter | ||||||||||
for JellyfishMerkleTreeStorage< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> | ||||||||||
where | ||||||||||
NodeTableType: Mappable<Key = JmtNodeKey, Value = JmtNode, OwnedValue = JmtNode>, | ||||||||||
ValueTableType: Mappable< | ||||||||||
Key = jmt::KeyHash, | ||||||||||
Value = (jmt::Version, jmt::OwnedValue), | ||||||||||
OwnedValue = (jmt::Version, jmt::OwnedValue), | ||||||||||
>, | ||||||||||
LatestRootVersionTableType: Mappable<Key = (), Value = u64, OwnedValue = u64>, | ||||||||||
StorageType: StorageMutate<NodeTableType, Error = StorageError> | ||||||||||
+ StorageMutate<ValueTableType, Error = StorageError> | ||||||||||
+ StorageMutate<LatestRootVersionTableType, Error = StorageError>, | ||||||||||
{ | ||||||||||
fn write_node_batch(&self, node_batch: &JmtNodeBatch) -> anyhow::Result<()> { | ||||||||||
for (key, node) in node_batch.nodes() { | ||||||||||
// TODO: Do we really need locks here? | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah commented above. I don't think we should have to care about locks in this context. |
||||||||||
let mut storage = self.storage_write(); | ||||||||||
<StorageType as StorageMutate<NodeTableType>>::insert( | ||||||||||
&mut *storage, | ||||||||||
key, | ||||||||||
node, | ||||||||||
) | ||||||||||
.map_err(|_err| anyhow::anyhow!("Node table write Storage Error"))?; | ||||||||||
if key.nibble_path().is_empty() { | ||||||||||
// If the nibble path is empty, we are updating the root node. | ||||||||||
// We must also update the latest root version | ||||||||||
let newer_version = <StorageType as StorageInspect< | ||||||||||
LatestRootVersionTableType, | ||||||||||
>>::get(&*storage, &()) | ||||||||||
.map_err(|_e| anyhow::anyhow!("Latest root version read storage error"))? | ||||||||||
.map(|v| *v) | ||||||||||
.filter(|v| *v >= key.version()); | ||||||||||
// To check: it should never be the case that this check fails | ||||||||||
if newer_version.is_none() { | ||||||||||
StorageMutate::<LatestRootVersionTableType>::insert( | ||||||||||
&mut *storage, | ||||||||||
&(), | ||||||||||
&key.version(), | ||||||||||
) | ||||||||||
.map_err(|_e| { | ||||||||||
anyhow::anyhow!("Latest root version write storage error") | ||||||||||
})?; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
for ((version, key_hash), value) in node_batch.values() { | ||||||||||
match value { | ||||||||||
None => { | ||||||||||
let _old = <StorageType as StorageMutate<ValueTableType>>::take( | ||||||||||
&mut *storage, | ||||||||||
key_hash, | ||||||||||
) | ||||||||||
.map_err(|_e| anyhow::anyhow!("Version Storage Error"))?; | ||||||||||
} | ||||||||||
Some(value) => { | ||||||||||
let _old = | ||||||||||
<StorageType as StorageMutate<ValueTableType>>::replace( | ||||||||||
&mut *storage, | ||||||||||
key_hash, | ||||||||||
&(*version, value.clone()), | ||||||||||
) | ||||||||||
.map_err(|_e| anyhow::anyhow!("Version Storage Error"))?; | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
Ok(()) | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
impl< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> TreeReader | ||||||||||
for JellyfishMerkleTreeStorage< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> | ||||||||||
where | ||||||||||
NodeTableType: Mappable<Key = JmtNodeKey, Value = JmtNode, OwnedValue = JmtNode>, | ||||||||||
ValueTableType: Mappable< | ||||||||||
Key = jmt::KeyHash, | ||||||||||
Value = (jmt::Version, jmt::OwnedValue), | ||||||||||
OwnedValue = (jmt::Version, jmt::OwnedValue), | ||||||||||
>, | ||||||||||
StorageType: StorageInspect<NodeTableType, Error = StorageError> | ||||||||||
+ StorageInspect<ValueTableType, Error = StorageError>, | ||||||||||
{ | ||||||||||
fn get_node_option(&self, node_key: &JmtNodeKey) -> anyhow::Result<Option<JmtNode>> { | ||||||||||
let storage = self.storage_read(); | ||||||||||
let get_result = | ||||||||||
<StorageType as StorageInspect<NodeTableType>>::get(&*storage, node_key) | ||||||||||
.map_err(|_e| anyhow::anyhow!("Storage Error"))?; | ||||||||||
let node = get_result.map(|node| node.into_owned()); | ||||||||||
|
||||||||||
Ok(node) | ||||||||||
} | ||||||||||
|
||||||||||
fn get_value_option( | ||||||||||
&self, | ||||||||||
max_version: jmt::Version, | ||||||||||
key_hash: jmt::KeyHash, | ||||||||||
) -> anyhow::Result<Option<jmt::OwnedValue>> { | ||||||||||
let storage = self.storage_read(); | ||||||||||
let Some(value) = | ||||||||||
<StorageType as StorageInspect<ValueTableType>>::get(&*storage, &key_hash) | ||||||||||
.map_err(|_e| anyhow::anyhow!("Version Storage Error"))? | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps
Suggested change
|
||||||||||
.filter(|v| v.0 <= max_version) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there any way we can optimize this? |
||||||||||
.map(|v| v.into_owned().1) | ||||||||||
else { | ||||||||||
return Ok(None) | ||||||||||
}; | ||||||||||
// Retrieve current version of key | ||||||||||
|
||||||||||
return Ok(Some(value)) | ||||||||||
} | ||||||||||
|
||||||||||
fn get_rightmost_leaf(&self) -> anyhow::Result<Option<(JmtNodeKey, JmtLeafNode)>> { | ||||||||||
unimplemented!( | ||||||||||
"Righmost leaf is used only when restoring the tree, which we do not support" | ||||||||||
) | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
impl< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> HasPreimage | ||||||||||
for JellyfishMerkleTreeStorage< | ||||||||||
NodeTableType, | ||||||||||
ValueTableType, | ||||||||||
LatestRootVersionTableType, | ||||||||||
StorageType, | ||||||||||
StorageError, | ||||||||||
> | ||||||||||
where | ||||||||||
ValueTableType: Mappable< | ||||||||||
Key = jmt::KeyHash, | ||||||||||
Value = (jmt::Version, jmt::OwnedValue), | ||||||||||
OwnedValue = (jmt::Version, jmt::OwnedValue), | ||||||||||
>, | ||||||||||
StorageType: StorageInspect<ValueTableType, Error = StorageError>, | ||||||||||
{ | ||||||||||
fn preimage(&self, key_hash: jmt::KeyHash) -> anyhow::Result<Option<Vec<u8>>> { | ||||||||||
let storage = self.storage_read(); | ||||||||||
let preimage = | ||||||||||
<StorageType as StorageInspect<ValueTableType>>::get(&*storage, &key_hash) | ||||||||||
.map_err(|_e| anyhow::anyhow!("Preimage storage error"))? | ||||||||||
.map(|v| v.into_owned().1); | ||||||||||
|
||||||||||
Ok(preimage) | ||||||||||
} | ||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While benchmarking I think this is fine, but I'd advocate for using dedicated error types before merging to master (or as a follow-up)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the jmt crate that exposes traits whose signature return a
anyhow::Result
value.See
fuel-vm/fuel-merkle/src/jellyfish/jmt_integration.rs
Line 47 in 68bbb4c
Otherwise I'd be very happy to remove it :)