Skip to content

Commit

Permalink
REFACTOR: Addition of StemNodes to capture SuffixExtensions (#8)
Browse files Browse the repository at this point in the history
Currently, there was branch or leaf nodes (and null). The logic of suffix and extensions was all included in hash visitor.
This includes commiting to 1, stem, left values commitment and right values commitment.
However, the subcommitments will be useful in proofs.

Another reason to refactor is that path-extensions are only allowed at StemNodes, like in the current specifications.
Before, path-extensions could be done anywhere.

Another feature is support for StoredVerkleTrie. StoredNodes are now behaving correctly and can be retrieved recursively.

Finally, minor feature is that put returns the old value, which is helpful for trie-logs

Signed-off-by: Thomas Zamojski <[email protected]>
  • Loading branch information
thomas-quadratic authored Nov 10, 2023
1 parent d4ea40c commit b2ce2f2
Show file tree
Hide file tree
Showing 21 changed files with 1,098 additions and 647 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import static com.google.common.base.Preconditions.checkNotNull;

import org.hyperledger.besu.ethereum.trie.NodeUpdater;
import org.hyperledger.besu.ethereum.trie.verkle.node.InternalNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.Node;
import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode;
import org.hyperledger.besu.ethereum.trie.verkle.visitor.CommitVisitor;
import org.hyperledger.besu.ethereum.trie.verkle.visitor.GetVisitor;
import org.hyperledger.besu.ethereum.trie.verkle.visitor.HashVisitor;
Expand All @@ -42,7 +42,7 @@ public class SimpleVerkleTrie<K extends Bytes, V extends Bytes> implements Verkl

/** Creates a new Verkle Trie with a null node as the root. */
public SimpleVerkleTrie() {
this.root = NullNode.instance();
this.root = new InternalNode<V>(Bytes.EMPTY);
}

/**
Expand Down Expand Up @@ -82,10 +82,12 @@ public Optional<V> get(final K key) {
* @param value The value to associate with the key.
*/
@Override
public void put(final K key, final V value) {
public Optional<V> put(final K key, final V value) {
checkNotNull(key);
checkNotNull(value);
this.root = root.accept(new PutVisitor<V>(value), key);
PutVisitor<V> visitor = new PutVisitor<V>(value);
this.root = root.accept(visitor, key);
return visitor.getOldValue();
}

/**
Expand All @@ -106,7 +108,7 @@ public void remove(final K key) {
*/
@Override
public Bytes32 getRootHash() {
root = root.accept(new HashVisitor<V>(), root.getPath());
root = root.accept(new HashVisitor<V>(), Bytes.EMPTY);
return root.getHash().get();
}

Expand All @@ -127,7 +129,7 @@ public String toString() {
*/
@Override
public void commit(final NodeUpdater nodeUpdater) {
root = root.accept(new HashVisitor<V>(), root.getPath());
root = root.accept(new HashVisitor<V>(), Bytes.EMPTY);
root = root.accept(new CommitVisitor<V>(nodeUpdater), Bytes.EMPTY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public interface VerkleTrie<K, V> {
* @param key The key that corresponds to the value to be updated.
* @param value The value to associate the key with.
*/
void put(K key, V value);
Optional<V> put(K key, V value);

/**
* Deletes the value mapped to the specified key, if such a value exists (Optional operation).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
package org.hyperledger.besu.ethereum.trie.verkle.factory;

import org.hyperledger.besu.ethereum.trie.NodeLoader;
import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.InternalNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.Node;
import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.StemNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.StoredNode;

import java.util.ArrayList;
Expand All @@ -31,6 +31,12 @@
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.rlp.RLP;

enum NodeType {
LEAF,
INTERNAL,
STEM
}

/**
* A factory for creating Verkle Trie nodes based on stored data.
*
Expand All @@ -54,8 +60,8 @@ public StoredNodeFactory(NodeLoader nodeLoader, Function<Bytes, V> valueDeserial
/**
* Retrieves a Verkle Trie node from stored data based on the location and hash.
*
* @param location The location of the node.
* @param hash The hash of the node.
* @param location Node's location
* @param hash Node's hash
* @return An optional containing the retrieved node, or an empty optional if the node is not
* found.
*/
Expand All @@ -67,48 +73,76 @@ public Optional<Node<V>> retrieve(final Bytes location, final Bytes32 hash) {
}
Bytes encodedValues = optionalEncodedValues.get();
List<Bytes> values = RLP.decodeToList(encodedValues, reader -> reader.readValue().copy());
Bytes hashOrEmpty = values.get(0);
if (hashOrEmpty.isEmpty() && values.size() == 1) { // NullNode
return Optional.of(NullNode.instance());
}
Bytes path = values.get(1);
if (hashOrEmpty.isEmpty() && values.size() > 1) { // LeafNode
V value = valueDeserializer.apply(values.get(2));
return Optional.of(createLeafNode(location, path, value));
}
if (!hashOrEmpty.isEmpty()) { // BranchNode
Bytes32 savedHash = (Bytes32) hashOrEmpty;
return Optional.of(createBranchNode(location, savedHash, path));
final int nValues = values.size();
NodeType type =
(nValues == 1 ? NodeType.LEAF : (nValues == 2 ? NodeType.INTERNAL : NodeType.STEM));
return switch (type) {
case LEAF -> Optional.of(createLeafNode(location, values));
case INTERNAL -> Optional.of(createInternalNode(location, values));
case STEM -> Optional.of(createStemNode(location, values));
default -> Optional.empty();
};
}

/**
* Creates a internalNode using the provided location, hash, and path.
*
* @param location The location of the internalNode.
* @param values List of Bytes values retrieved from storage.
* @return A internalNode instance.
*/
InternalNode<V> createInternalNode(Bytes location, List<Bytes> values) {
final int nChild = InternalNode.maxChild();
ArrayList<Node<V>> children = new ArrayList<Node<V>>(nChild);
for (int i = 0; i < nChild; i++) {
children.add(new StoredNode<>(this, Bytes.concatenate(location, Bytes.of(i))));
}
return Optional.empty(); // should not be here.
final Bytes32 hash = (Bytes32) values.get(0);
final Bytes32 commitment = (Bytes32) values.get(1);
return new InternalNode<V>(location, hash, commitment, children);
}

/**
* Creates a BranchNode using the provided location, hash, and path.
*
* @param location The location of the BranchNode.
* @param hash The hash of the BranchNode.
* @param path The path associated with the BranchNode.
* @param values List of Bytes values retrieved from storage.
* @return A BranchNode instance.
*/
protected BranchNode<V> createBranchNode(Bytes location, Bytes32 hash, Bytes path) {
int nChild = BranchNode.maxChild();
StemNode<V> createStemNode(Bytes location, List<Bytes> values) {
final int nChild = StemNode.maxChild();
final Bytes stem = values.get(0);
final Bytes32 hash = (Bytes32) values.get(1);
final Bytes32 commitment = (Bytes32) values.get(2);
final Bytes32 leftHash = (Bytes32) values.get(3);
final Bytes32 leftCommitment = (Bytes32) values.get(4);
final Bytes32 rightHash = (Bytes32) values.get(5);
final Bytes32 rightCommitment = (Bytes32) values.get(6);
ArrayList<Node<V>> children = new ArrayList<Node<V>>(nChild);
for (int i = 0; i < nChild; i++) {
children.add(new StoredNode<>(this, Bytes.concatenate(location, Bytes.of(i))));
children.add(new StoredNode<>(this, Bytes.concatenate(stem, Bytes.of(i))));
}
return new BranchNode<V>(location, hash, path, children);
return new StemNode<V>(
location,
stem,
hash,
commitment,
leftHash,
leftCommitment,
rightHash,
rightCommitment,
children);
}

/**
* Creates a LeafNode using the provided location, path, and value.
*
* @param location The location of the LeafNode.
* @param path The path associated with the LeafNode.
* @param value The value stored in the LeafNode.
* @param key The key of the LeafNode.
* @param values List of Bytes values retrieved from storage.
* @return A LeafNode instance.
*/
protected LeafNode<V> createLeafNode(Bytes location, Bytes path, V value) {
return new LeafNode<V>(Optional.of(location), value, path);
LeafNode<V> createLeafNode(Bytes key, List<Bytes> values) {
V value = valueDeserializer.apply(values.get(0));
return new LeafNode<V>(Optional.of(key), value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ public class IPAHasher implements Hasher<Bytes32> {
*/
@Override
public Bytes32 commit(Bytes32[] inputs) {
Bytes input_serialized = Bytes.concatenate(inputs);
return Bytes32.wrap(LibIpaMultipoint.commit(input_serialized.toArray()));
Bytes32[] rev = new Bytes32[inputs.length];
for (int i = 0; i < inputs.length; ++i) {
rev[i] = (Bytes32) inputs[i].reverse();
}
Bytes input_serialized = Bytes.concatenate(rev);
return (Bytes32) Bytes32.wrap(LibIpaMultipoint.commit(input_serialized.toArray())).reverse();
}
}
Loading

0 comments on commit b2ce2f2

Please sign in to comment.