diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java index 49e93ec..fa6c83e 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java @@ -54,6 +54,15 @@ public SimpleVerkleTrie(Node root) { this.root = root; } + /** + * Creates a new Verkle Trie with the specified node as the root. + * + * @param root The root node of the Verkle Trie. + */ + public SimpleVerkleTrie(Optional> root) { + this.root = root.orElse(new InternalNode(Bytes.EMPTY)); + } + /** * Retrieves the root node of the Verkle Trie. * diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrie.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrie.java index 5d77b19..7fbde05 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrie.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrie.java @@ -16,11 +16,17 @@ package org.hyperledger.besu.ethereum.trie.verkle; import org.hyperledger.besu.ethereum.trie.verkle.factory.NodeFactory; -import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; import org.apache.tuweni.bytes.Bytes; +/** + * Implementation of a Verkle Trie with nodes saved in storage. + * + * @param The type of keys in the Verkle Trie. + * @param The type of values in the Verkle Trie. + */ public class StoredVerkleTrie extends SimpleVerkleTrie { + /** NodeFactory that load nodes from storage */ protected final NodeFactory nodeFactory; /** @@ -29,7 +35,7 @@ public class StoredVerkleTrie extends SimpleVe * @param nodeFactory The {@link NodeFactory} to retrieve node. */ public StoredVerkleTrie(final NodeFactory nodeFactory) { - super(nodeFactory.retrieve(Bytes.EMPTY, null).orElse(NullNode.instance())); + super(nodeFactory.retrieve(Bytes.EMPTY, null)); this.nodeFactory = nodeFactory; } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java index 5236d2e..7652994 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java @@ -38,6 +38,7 @@ public interface VerkleTrie { * * @param key The key that corresponds to the value to be updated. * @param value The value to associate the key with. + * @return Optional previous value before replacement if it exists. */ Optional put(K key, V value); diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/exceptions/VerkleTrieException.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/exceptions/VerkleTrieException.java index 6f359ed..87eaa74 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/exceptions/VerkleTrieException.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/exceptions/VerkleTrieException.java @@ -17,22 +17,46 @@ import org.apache.tuweni.bytes.Bytes; +/** This exception is thrown when there is an issue retrieving or decoding values from Storage */ public class VerkleTrieException extends RuntimeException { + /** Location at which the Exception occurs */ private Bytes location; + /** + * Constructs a VerkleTrieException. + * + * @param message Exception's messasge. + */ public VerkleTrieException(final String message) { super(message); } + /** + * Constructs a VerkleTrieException at location. + * + * @param message Exception's messasge. + * @param location Exception occured at location. + */ public VerkleTrieException(final String message, final Bytes location) { super(message); this.location = location; } + /** + * Constructs a VerkleTrieException throwned from another exception. + * + * @param message Exception's messasge. + * @param cause Exception from which this exception was throwned. + */ public VerkleTrieException(final String message, final Exception cause) { super(message, cause); } + /** + * Location at which the exception occured + * + * @return the location at which the exception occured + */ public Bytes getLocation() { return location; } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java index d00ed46..ab7b358 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java @@ -31,10 +31,12 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.rlp.RLP; +/** Node types that are saved to storage. */ enum NodeType { - LEAF, + ROOT, INTERNAL, - STEM + STEM, + LEAF } /** @@ -67,15 +69,20 @@ public StoredNodeFactory(NodeLoader nodeLoader, Function valueDeserial */ @Override public Optional> retrieve(final Bytes location, final Bytes32 hash) { + /* Currently, Root and Leaf are distinguishable by location. + * To distinguish internal from stem, we further need values. + * Currently, they are distinguished by values length. + */ Optional optionalEncodedValues = nodeLoader.getNode(location, hash); if (optionalEncodedValues.isEmpty()) { return Optional.empty(); } Bytes encodedValues = optionalEncodedValues.get(); List values = RLP.decodeToList(encodedValues, reader -> reader.readValue().copy()); + final int locLength = location.size(); final int nValues = values.size(); NodeType type = - (nValues == 1 ? NodeType.LEAF : (nValues == 2 ? NodeType.INTERNAL : NodeType.STEM)); + (locLength == 32 ? 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)); diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/InternalNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/InternalNode.java index 37e59bb..c9a3656 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/InternalNode.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/InternalNode.java @@ -27,6 +27,11 @@ import org.apache.tuweni.rlp.RLP; import org.apache.tuweni.rlp.RLPWriter; +/** + * Represents an internal node in the Verkle Trie. + * + * @param The type of the node's value. + */ public class InternalNode extends BranchNode { private Optional encodedValue = Optional.empty(); // Encoded value @@ -109,8 +114,9 @@ public Node accept(final NodeVisitor visitor) { /** * Replace the vector commitment with a new one. * - * @param hash The new vector commitment to set. - * @return A new BranchNode with the updated vector commitment. + * @param hash The new vector commitment's hash to set. + * @param commitment The new vector commitment to set. + * @return A new InternalNode with the updated vector commitment. */ public Node replaceHash(Bytes32 hash, Bytes32 commitment) { return new InternalNode( @@ -143,10 +149,10 @@ public String print() { final StringBuilder builder = new StringBuilder(); builder.append("Internal:"); for (int i = 0; i < maxChild(); i++) { - final Node child = child((byte) i); - if (child == NullNode.instance()) { - final String label = "[" + Integer.toHexString(i) + "] "; - final String childRep = child.print().replaceAll("\n\t", "\n\t\t"); + final Node childNode = child((byte) i); + if (childNode != NullNode.instance()) { + final String label = String.format("[%02x] ", i); + final String childRep = childNode.print().replaceAll("\n\t", "\n\t\t"); builder.append("\n\t").append(label).append(childRep); } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java index 73e16a8..e6a744d 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java @@ -34,7 +34,7 @@ */ public class LeafNode implements Node { private final Optional location; // Location in the tree, or the key - protected final V value; // Value associated with the node + private final V value; // Value associated with the node private Optional encodedValue = Optional.empty(); // Encoded value private final Function valueSerializer; // Serializer function for the value private boolean dirty = true; // not persisted diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java index 9dd97de..b6abba6 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java @@ -32,9 +32,10 @@ */ public interface Node { - /** A constant representing a commitment to NullNodes */ + /** A constant representing a commitment's hash to NullNodes */ Bytes32 EMPTY_HASH = Bytes32.ZERO; + /** A constant representing a commitment to NullNodes */ Bytes32 EMPTY_COMMITMENT = Bytes32.ZERO; /** diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java index b40433c..2a16fbd 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StemNode.java @@ -27,6 +27,13 @@ import org.apache.tuweni.rlp.RLP; import org.apache.tuweni.rlp.RLPWriter; +/** + * Represents a stem node in the Verkle Trie. + * + *

StemNodes are nodes storing the stem of the key, and is the root of the suffix to value trie. + * + * @param The type of the node's value. + */ public class StemNode extends BranchNode { private final Node NULL_LEAF_NODE = NullLeafNode.instance(); @@ -149,6 +156,15 @@ public Bytes getStem() { return stem; } + /** + * Get Node's extension. + * + * @return the extension path. + */ + public Optional getPathExtension() { + return getLocation().map((loc) -> stem.slice(loc.size())); + } + /** * Get the leftHash. * @@ -189,6 +205,7 @@ public Optional getRightCommitment() { * Creates a new node by replacing its location * * @param location The location in the tree. + * @return StemNode with new location. */ public StemNode replaceLocation(final Bytes location) { return new StemNode( @@ -212,6 +229,7 @@ public StemNode replaceLocation(final Bytes location) { * @param leftCommitment Node's left vector commitment * @param rightHash Node's right vector commitment hash * @param rightCommitment Node's right vector commitment + * @return StemNode with new commitments. */ public StemNode replaceHash( final Bytes32 hash, @@ -264,11 +282,11 @@ public Bytes getEncodedValue() { @Override public String print() { final StringBuilder builder = new StringBuilder(); - builder.append("Stem:"); + builder.append(String.format("Stem: %s", stem)); for (int i = 0; i < maxChild(); i++) { final Node child = child((byte) i); - if (child == NullLeafNode.instance()) { - final String label = "[" + Integer.toHexString(i) + "] "; + if (child != NullLeafNode.instance()) { + final String label = String.format("[%02x] ", i); final String childRep = child.print().replaceAll("\n\t", "\n\t\t"); builder.append("\n\t").append(label).append(childRep); } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StoredNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StoredNode.java index 1733dff..53bd6a9 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StoredNode.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/StoredNode.java @@ -25,6 +25,13 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +/** + * Represents a regular node that can possibly be stored in storage. + * + *

StoredNodes wrap regular nodes and loads them lazily from storage as needed. + * + * @param The type of the node's value. + */ public class StoredNode implements Node { private final Bytes location; private final NodeFactory nodeFactory; @@ -32,6 +39,12 @@ public class StoredNode implements Node { private boolean dirty = true; // not persisted + /** + * Constructs a new StoredNode at location. + * + * @param nodeFactory The node factory for creating nodes from storage. + * @param location The location in the tree. + */ public StoredNode(final NodeFactory nodeFactory, final Bytes location) { this.location = location; this.nodeFactory = nodeFactory; @@ -152,13 +165,19 @@ public boolean isDirty() { @Override public String print() { final Node node = load(); - return node.print(); + return String.format("(stored) %s", node.print()); } private Node load() { - if (!loadedNode.isPresent()) { + if (loadedNode.isEmpty()) { loadedNode = nodeFactory.retrieve(location, null); } - return loadedNode.orElse(NullNode.instance()); + if (loadedNode.isPresent()) { + return loadedNode.get(); + } else if (location.size() == 32) { + return NullLeafNode.instance(); + } else { + return NullNode.instance(); + } } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/FlattenVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/FlattenVisitor.java index c5f34cf..a194732 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/FlattenVisitor.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/FlattenVisitor.java @@ -22,16 +22,25 @@ import org.apache.tuweni.bytes.Bytes; -public class FlattenVisitor implements PathNodeVisitor { +/** + * Class representing a visitor for flattening a node in a Trie tree. + * + *

Flattening a node means that it is merged with its parent, adding one level to the extension + * path. Per current specs, only StemNodes can have extensions, so only StemNodes can potentially be + * flattened. + * + * @param The type of node values. + */ +public class FlattenVisitor implements NodeVisitor { private final Node NULL_NODE = NullNode.instance(); @Override - public Node visit(InternalNode internalNode, Bytes path) { + public Node visit(InternalNode internalNode) { return NULL_NODE; } @Override - public Node visit(StemNode stemNode, Bytes path) { + public Node visit(StemNode stemNode) { final Bytes location = stemNode.getLocation().get(); final Bytes newLocation = location.slice(0, location.size() - 1); // Should not flatten root node diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java index d9c3ef5..753a99e 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java @@ -53,8 +53,13 @@ public Node visit(final InternalNode internalNode, final Bytes path) { */ @Override public Node visit(final StemNode stemNode, final Bytes path) { - final byte childIndex = path.get(path.size() - 1); // extract suffix - return stemNode.child(childIndex).accept(this, path.slice(1)); + final Bytes extension = stemNode.getPathExtension().get(); + final int prefix = path.commonPrefixLength(extension); + if (prefix < extension.size()) { + return NULL_NODE_RESULT; + } + final byte childIndex = path.get(prefix); // extract suffix + return stemNode.child(childIndex).accept(this, path.slice(prefix + 1)); } /** diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java index 8375599..3e37612 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java @@ -37,8 +37,9 @@ */ public class RemoveVisitor implements PathNodeVisitor { private final Node NULL_NODE = NullNode.instance(); - private final Node NULL_LEAF_NODE = NullNode.instance(); + private final Node NULL_LEAF_NODE = NullLeafNode.instance(); private final FlattenVisitor flatten = new FlattenVisitor<>(); + private final GetVisitor getter = new GetVisitor<>(); /** * Visits a internal node to remove a node associated with the provided path and maintain the @@ -51,17 +52,18 @@ public class RemoveVisitor implements PathNodeVisitor { @Override public Node visit(InternalNode internalNode, Bytes path) { final byte index = path.get(0); - final Node updatedChild = internalNode.child(index).accept(this, path.slice(1)); + final Node childNode = internalNode.child(index); + final Node updatedChild = childNode.accept(this, path.slice(1)); internalNode.replaceChild(index, updatedChild); - if (updatedChild.isDirty()) { + final boolean wasChildNullified = (childNode != NULL_NODE && updatedChild == NULL_NODE); + if (updatedChild.isDirty() || wasChildNullified) { internalNode.markDirty(); } final Optional onlyChildIndex = findOnlyChild(internalNode); if (onlyChildIndex.isEmpty()) { return internalNode; } - final Node childNode = internalNode.child(onlyChildIndex.get()); - final Node newNode = childNode.accept(flatten, Bytes.of(index)); + final Node newNode = internalNode.child(onlyChildIndex.get()).accept(flatten); if (newNode != NULL_NODE) { // Flatten StemNode one-level up newNode.markDirty(); return newNode; @@ -153,7 +155,10 @@ Optional findOnlyChild(final InternalNode branchNode) { boolean allLeavesAreNull(final StemNode stemNode) { final List> children = stemNode.getChildren(); for (int i = 0; i < children.size(); ++i) { - if (children.get(i) != NullLeafNode.instance()) { + Node child = + children.get(i).accept(getter, Bytes.EMPTY); // forces to load node if StoredNode; + stemNode.replaceChild((byte) i, child); + if (child != NULL_LEAF_NODE) { return false; } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java index 5599182..430b0bd 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java @@ -19,6 +19,7 @@ import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; @@ -214,12 +215,58 @@ public void testDeleteThreeValuesWithFlattening() throws Exception { trie.put(key2, value2); trie.put(key3, value3); trie.remove(key1); - assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); + assertThat(trie.get(key1)).as("First value has been deleted").isEqualTo(Optional.empty()); + assertThat(trie.get(key2)).as("Second value").isEqualTo(Optional.of(value2)); trie.remove(key2); - assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.get(key3)).as("Retrieve first value").isEqualTo(Optional.of(value3)); + assertThat(trie.get(key2)).as("Second value has been deleted").isEqualTo(Optional.empty()); + assertThat(trie.get(key3)).as("Third value").isEqualTo(Optional.of(value3)); trie.remove(key3); - assertThat(trie.get(key3)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.get(key3)).as("Third value has been deleted").isEqualTo(Optional.empty()); + } + + @Test + public void testDeleteManyValuesWithDivergentStemsAtDepth2() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie<>(); + assertThat(trie.getRootHash()).isEqualTo(Bytes32.ZERO); + Bytes32 key0 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45641"); + Bytes32 value0 = + Bytes32.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); + Bytes32 key1 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45601"); + Bytes32 value1 = + Bytes32.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); + Bytes32 key2 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45602"); + Bytes32 value2 = Bytes32.fromHexString("0x01"); + Bytes32 key3 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45600"); + Bytes32 value3 = Bytes32.fromHexString("0x00"); + Bytes32 key4 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45603"); + Bytes32 value4 = + Bytes32.fromHexString("0xf84a97f1f0a956e738abd85c2e0a5026f8874e3ec09c8f012159dfeeaab2b156"); + Bytes32 key5 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45604"); + Bytes32 value5 = Bytes32.fromHexString("0x03"); + Bytes32 key6 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45680"); + Bytes32 value6 = + Bytes32.fromHexString("0x0000010200000000000000000000000000000000000000000000000000000000"); + trie.put(key0, value0); + trie.put(key1, value1); + trie.put(key2, value2); + trie.put(key3, value3); + trie.put(key4, value4); + trie.put(key5, value5); + trie.put(key6, value6); + trie.remove(key0); + trie.remove(key4); + trie.remove(key5); + trie.remove(key6); + trie.remove(key3); + trie.remove(key1); + trie.remove(key2); + assertThat(trie.getRootHash()).isEqualTo(Bytes32.ZERO); } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java index ee7d6f8..0ac9746 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java @@ -17,8 +17,14 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.hyperledger.besu.ethereum.trie.NodeUpdater; import org.hyperledger.besu.ethereum.trie.verkle.factory.StoredNodeFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; @@ -199,4 +205,68 @@ public void testDeleteThreeValuesWithFlattening() throws Exception { assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); assertThat(storedTrie.get(key3).orElse(null)).isEqualTo(value3); } + + @Test + public void testDeleteManyValuesWithDivergentStemsAtDepth2() throws Exception { + final Map map = new HashMap<>(); + StoredVerkleTrie trie = + new StoredVerkleTrie<>( + new StoredNodeFactory<>( + (location, hash) -> Optional.ofNullable(map.get(location)), value -> value)); + + assertThat(trie.getRootHash()).isEqualTo(Bytes32.ZERO); + Bytes32 key0 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45641"); + Bytes32 value0 = + Bytes32.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); + Bytes32 key1 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45601"); + Bytes32 value1 = + Bytes32.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); + Bytes32 key2 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45602"); + Bytes32 value2 = Bytes32.fromHexString("0x01"); + Bytes32 key3 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45600"); + Bytes32 value3 = Bytes32.fromHexString("0x00"); + Bytes32 key4 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45603"); + Bytes32 value4 = + Bytes32.fromHexString("0xf84a97f1f0a956e738abd85c2e0a5026f8874e3ec09c8f012159dfeeaab2b156"); + Bytes32 key5 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45604"); + Bytes32 value5 = Bytes32.fromHexString("0x03"); + Bytes32 key6 = + Bytes32.fromHexString("0x1e4abaeaa58259f4784e086ddbaa74a9d3975efb2e4380595f0eed5692c45680"); + Bytes32 value6 = + Bytes32.fromHexString("0x0000010200000000000000000000000000000000000000000000000000000000"); + trie.put(key0, value0); + trie.put(key1, value1); + trie.put(key2, value2); + trie.put(key3, value3); + trie.put(key4, value4); + trie.put(key5, value5); + trie.put(key6, value6); + + trie.commit( + new NodeUpdater() { + @Override + public void store(final Bytes location, final Bytes32 hash, final Bytes value) { + map.put(location, value); + } + }); + StoredVerkleTrie trie2 = + new StoredVerkleTrie<>( + new StoredNodeFactory<>( + (location, hash) -> Optional.ofNullable(map.get(location)), value -> value)); + assertThat(trie2.getRootHash()).isEqualTo(trie.getRootHash()); + trie2.remove(key0); + trie2.remove(key4); + trie2.remove(key5); + trie2.remove(key6); + trie2.remove(key3); + trie2.remove(key1); + trie2.remove(key2); + assertThat(trie2.getRootHash()).isEqualTo(Bytes32.ZERO); + } }