-
Notifications
You must be signed in to change notification settings - Fork 33
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
Implement an alternative to TSMT #243
Comments
Since the implementation is going to be close to Note that this design wouldn't directly support future optimizations such as compressing Merkle proofs, but is flexible enough to allow for it in the future. Theoretical perspectiveThe current Data types
FunctionsIn words, the abstract implementation is a map
In words, the Next, we'll then need to define how a Implementation perspectiveIn Rust, we would model things roughly as follows. Note that I used dummy names. Also, when the implementation is omitted, assume that today's type LeafIndex = u64;
trait SimpleSmtTrait {
type Key: Into<LeafIndex>;
type Value;
type Leaf;
// ABSTRACT METHODS
// ---------------------------------------------------------------------------------------------
/// The depth of the tree
fn depth(&self) -> u8;
/// The root of the tree
fn root(&self) -> RpoDigest;
/// Retrieves a leaf a the given index
/// NOTE: Requires a change in current `SimpleSmt` (will need to use `unwrap_or_default()`)
fn get_leaf(&self, leaf_index: LeafIndex) -> Option<&Self::Leaf>;
/// Retrieves an inner node at the given index
fn get_inner_node(&self, index: NodeIndex) -> BranchNode;
/// Inserts a leaf node, and returns the value at the key if already exists
fn insert_leaf_node(&self, key: Self::Key, value: Self::Value) -> Option<Self::Value>;
/// Returns the hash of a leaf
fn hash_leaf(v: Self::Leaf) -> RpoDigest;
// PROVIDED METHODS
// ---------------------------------------------------------------------------------------------
fn get_node(
&self,
index: NodeIndex,
) -> Result<RpoDigest, MerkleError> {
todo!()
}
fn insert(
&mut self,
key: Self::Key,
value: Self::Value,
) -> Result<Option<Self::Value>, MerkleError> {
todo!()
}
}
// SIMPLE SMT
// =================================================================================================
// Assumption: we always know the depth of our trees at compile time
struct SimpleSmt<const DEPTH: u8> {
root: RpoDigest,
leaves: BTreeMap<u64, Word>,
branches: BTreeMap<NodeIndex, BranchNode>,
}
impl<const DEPTH: u8> SimpleSmtTrait for SimpleSmt<DEPTH> {
type Key = SimpleSmtKey<DEPTH>;
type Value = Word;
type Leaf = Word;
fn depth(&self) -> u8 {
DEPTH
}
fn root(&self) -> RpoDigest {
self.root
}
fn get_leaf(&self, leaf_index: LeafIndex) -> Option<&Self::Leaf> {
self.leaves.get(&leaf_index)
}
fn get_inner_node(&self, index: NodeIndex) -> BranchNode {
// Same impl as in current `SimpleSmt`
todo!()
}
fn insert_leaf_node(&self, key: Self::Key, value: Self::Value) -> Option<Self::Value> {
todo!()
}
fn hash_value(v: Self::Value) -> RpoDigest {
// hash(v)
todo!()
}
}
/// Encodes a value with constraints 0 < value < DEPTH,
/// where DEPTH is the depth of the `SimpleSmt`
struct SimpleSmtKey<const DEPTH: u8> {
value: u64
}
impl<const DEPTH: u8> SimpleSmtKey<DEPTH> {
pub fn new(value: u64) -> Result<Self, MerkleError> {
if value < 2_u64.pow(DEPTH.into()) {
Ok(Self { value })
} else {
Err(MerkleError)
}
}
}
// We can convert infaillibly to u64 because we checked that value is consistent with DEPTH when creating the `SimpleSmtKey`
impl<const DEPTH: u8> From<SimpleSmtKey<DEPTH>> for u64 {
fn from(value: SimpleSmtKey<DEPTH>) -> Self {
value.value
}
}
// NEW SMT
// =================================================================================================
struct NewSmtKey(Word);
impl From<NewSmtKey> for LeafIndex {
fn from(key: NewSmtKey) -> Self {
// return key[0] (i.e. most significant felt)
todo!()
}
}
enum NewSmtLeaf {
Single((u64, Word)),
Multiple(Vec<(u64, Word)>)
}
// Assumption: we always know the depth of our trees at compile time
struct NewSmt<const DEPTH: u8> {
root: RpoDigest,
leaves: BTreeMap<u64, NewSmtLeaf>,
branches: BTreeMap<NodeIndex, BranchNode>,
}
impl<const DEPTH: u8> SimpleSmtTrait for NewSmt<DEPTH> {
type Key = NewSmtKey;
type Value = Word;
type Leaf = NewSmtLeaf;
fn depth(&self) -> u8 {
DEPTH
}
fn root(&self) -> RpoDigest {
self.root
}
fn get_leaf(&self, leaf_index: LeafIndex) -> Option<&Self::Leaf> {
self.leaves.get(&leaf_index)
}
fn get_inner_node(&self, index: NodeIndex) -> BranchNode {
// Same impl as in current `SimpleSmt`
todo!()
}
fn insert_leaf_node(&self, key: Self::Key, value: Self::Value) -> Option<Self::Value> {
// If value already exists, convert to `NewSmtLeaf::Multiple`, etc.
todo!()
}
fn hash_value(v: Self::Value) -> RpoDigest {
// hash(key0 || value0 || key1 || value1 || ...)
todo!()
}
} |
Closed by #254 |
As mentioned in 0xPolygonMiden/miden-vm#1136, our TieredSmt turned out to be quite complex, and in the end, the performance benefits we get from it don't justify this complexity (in my opinion, at least). In my mind, there are two alternatives worth considering:
I think for now it makes sense to go for the simpler approach, even if it is somewhat less efficient outside of the VM. So, I'll focus on the second approach.
The end goal is to provide a data structure which emulates a key-value map with 4-element keys and 4-element values (we could consider values of arbitrary size, but for the first implementation, I'd probably stick with 4-element values). So, the interface could probably remain pretty close to what we have for
TieredSmt
.The internal implementation can be just a simple SMT of depth 64 which behaves as follows:
hash(key || value)
.hash(key_0 || value_0 || key_1 || value_1)
.A few notes:
SimpleSmt
), but in the future we can optimize this quite a bit to reduce storage requirements. One example of potentially relevant optimizations is described here.The text was updated successfully, but these errors were encountered: