From 26921d9d74dc1622bc8e22f204eced0ea6ee28f7 Mon Sep 17 00:00:00 2001 From: thomas-quadratic <116874460+thomas-quadratic@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:57:24 +0100 Subject: [PATCH] FIX: bugs in visitors and nodes to make testDeleteManyValues pass (#10) testDeleteManyValuesWithDivergentStemsAtDepth2 was added from previous MPT tests. The test allowed to detect several bugs in visitors and nodes, that were all fixed. Javadoc was also made to emit no warnings. Signed-off-by: Thomas Zamojski --- .../trie/verkle/SimpleVerkleTrie.java | 9 +++ .../trie/verkle/StoredVerkleTrie.java | 10 ++- .../besu/ethereum/trie/verkle/VerkleTrie.java | 1 + .../exceptions/VerkleTrieException.java | 24 +++++++ .../verkle/factory/StoredNodeFactory.java | 13 +++- .../trie/verkle/node/InternalNode.java | 18 +++-- .../ethereum/trie/verkle/node/LeafNode.java | 2 +- .../besu/ethereum/trie/verkle/node/Node.java | 3 +- .../ethereum/trie/verkle/node/StemNode.java | 24 ++++++- .../ethereum/trie/verkle/node/StoredNode.java | 25 ++++++- .../trie/verkle/visitor/FlattenVisitor.java | 15 +++- .../trie/verkle/visitor/GetVisitor.java | 9 ++- .../trie/verkle/visitor/RemoveVisitor.java | 17 +++-- .../trie/verkle/SimpleVerkleTrieTest.java | 57 +++++++++++++-- .../trie/verkle/StoredVerkleTrieTest.java | 70 +++++++++++++++++++ 15 files changed, 262 insertions(+), 35 deletions(-) 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); + } }