Skip to content
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 toDot Method and File Storing Mechanism for VerkleTrie #17

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ dependencies {
implementation 'io.tmio:tuweni-rlp'
implementation 'org.hyperledger.besu:ipa-multipoint'
implementation 'org.hyperledger.besu.internal:trie:23.1.3-SNAPSHOT'
implementation 'org.apache.logging.log4j:log4j-api'
implementation 'org.apache.logging.log4j:log4j-core'

testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.junit.jupiter:junit-jupiter-params'
Expand Down
5 changes: 5 additions & 0 deletions gradle/versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ dependencyManagement {

dependency 'org.assertj:assertj-core:3.24.2'

dependencySet(group: 'org.apache.logging.log4j', version: '2.20.0') {
entry 'log4j-api'
entry 'log4j-core'
}

dependencySet(group: 'io.tmio', version: '2.4.2') {
entry 'tuweni-bytes'
entry 'tuweni-rlp'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static com.google.common.base.Preconditions.checkNotNull;

import org.hyperledger.besu.ethereum.trie.NodeUpdater;
import org.hyperledger.besu.ethereum.trie.verkle.exporter.DotExporter;
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.visitor.CommitVisitor;
Expand All @@ -26,6 +27,7 @@
import org.hyperledger.besu.ethereum.trie.verkle.visitor.PutVisitor;
import org.hyperledger.besu.ethereum.trie.verkle.visitor.RemoveVisitor;

import java.io.IOException;
import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
Expand Down Expand Up @@ -141,4 +143,50 @@ public void commit(final NodeUpdater nodeUpdater) {
root = root.accept(new HashVisitor<V>(), Bytes.EMPTY);
root = root.accept(new CommitVisitor<V>(nodeUpdater), Bytes.EMPTY);
}

/**
* Returns the DOT representation of the entire Verkle Trie.
*
* @param showRepeatingEdges if true displays repeating edges; if false does not.
* @return The DOT representation of the Verkle Trie.
*/
public String toDotTree(Boolean showRepeatingEdges) {
return String.format(
"digraph VerkleTrie {\n%s\n}",
getRoot().toDot(showRepeatingEdges).replaceAll("^\\n+|\\n+$", ""));
}

/**
* Returns the DOT representation of the entire Verkle Trie.
*
* <p>The representation does not contain repeating edges.
*
* @return The DOT representation of the Verkle Trie.
*/
public String toDotTree() {
StringBuilder result = new StringBuilder("digraph VerkleTrie {\n");
Node<V> root = getRoot();
result.append(root.toDot());
return result.append("}").toString();
}

/**
* Exports the Verkle Trie DOT representation to a '.gv' file located in the current directory.
* The default file name is "VerkleTree.gv".
*
* @throws IOException if an I/O error occurs.
*/
public void dotTreeToFile() throws IOException {
DotExporter.exportToDotFile(toDotTree());
}

/**
* /** Exports the Verkle Trie DOT representation to a '.gv' file located at the specified path.
*
* @param path The location where the DOT file will be saved.
* @throws IOException if ann I/O error occurs.
*/
public void dotTreeToFile(String path) throws IOException {
DotExporter.exportToDotFile(toDotTree(), path);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright Besu Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.trie.verkle.exporter;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/** Utility class for exporting Verkle Trie representations to DOT files. */
public class DotExporter {

private static final Logger LOG = LogManager.getLogger(DotExporter.class);
private static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile("\\.(dot|gv)$");
private static final String DEFAULT_FILE_NAME = "./VerkleTrie.gv";

/**
* Exports the Verkle Trie DOT representation to a '.gv' file located in the current directory.
* The default file name is "VerkleTrie.gv".
*
* @param verkleTrieDotString The DOT representation of the Verkle Trie.
* @throws IOException If an I/O error occurs during the export process.
*/
public static void exportToDotFile(String verkleTrieDotString) throws IOException {
exportToDotFile(verkleTrieDotString, DEFAULT_FILE_NAME);
}

/**
* Exports the Verkle Trie DOT representation to a '.gv' file located at the specified path.
*
* @param verkleTrieDotString The DOT representation of the Verkle Trie.
* @param filePath The location where the DOT file will be saved.
* @throws IOException If an I/O error occurs during the export process.
*/
public static void exportToDotFile(String verkleTrieDotString, String filePath)
throws IOException {
try {
if (filePath == null || filePath.isEmpty()) {
filePath = DEFAULT_FILE_NAME;
} else {
Matcher matcher = FILE_EXTENSION_PATTERN.matcher(filePath);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid file extension. Use .dot or .gv extension.");
}
}

Path path = Paths.get(filePath);

Files.createDirectories(path.getParent());

try (BufferedWriter writer =
new BufferedWriter(new FileWriter(path.toString(), StandardCharsets.UTF_8))) {
writer.write(verkleTrieDotString);
}

} catch (AccessDeniedException e) {
LOG.error(
"Access denied. Check write permissions for the file. Details: {}", e.getMessage(), e);
throw e;
} catch (FileSystemException e) {
LOG.error(
"File system issue. Check disk space and file system restrictions. Details: {}",
e.getMessage(),
e);
throw e;
} catch (IOException e) {
LOG.error("Error writing DOT file: {}. Details: {}", e.getMessage(), e);
throw e;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,39 @@ public String print() {
}
return builder.toString();
}

/**
* Generates DOT representation for the BranchNode.
*
* @return DOT representation of the BranchNode.
*/
@Override
public String toDot(Boolean showRepeatingEdges) {
StringBuilder result =
new StringBuilder()
.append(getClass().getSimpleName())
.append(getLocation().orElse(Bytes.EMPTY))
.append("\", location=\"")
.append(getLocation().orElse(Bytes.EMPTY))
.append("\", commitment=\"")
.append(getHash().orElse(Bytes32.ZERO))
.append("\"]\n");

for (Node<V> child : getChildren()) {
String edgeString =
getClass().getSimpleName()
+ getLocation().orElse(Bytes.EMPTY)
+ " -> "
+ child.getClass().getSimpleName()
+ child.getLocation().orElse(Bytes.EMPTY)
+ "\n";

if (showRepeatingEdges || !result.toString().contains(edgeString)) {
result.append(edgeString);
}
result.append(child.toDot(showRepeatingEdges));
}

return result.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,39 @@ public String print() {
}
return builder.toString();
}

/**
* Generates DOT representation for the InternalNode.
*
* @return DOT representation of the InternalNode.
*/
@Override
public String toDot(Boolean showRepeatingEdges) {
StringBuilder result =
new StringBuilder()
.append(getClass().getSimpleName())
.append(getLocation().orElse(Bytes.EMPTY))
.append("[location=\"")
.append(getLocation().orElse(Bytes.EMPTY))
.append("\", commitment=\"")
.append(getHash().orElse(Bytes32.ZERO))
.append("\"]\n");

for (Node<V> child : getChildren()) {
String edgeString =
getClass().getSimpleName()
+ getLocation().orElse(Bytes.EMPTY)
+ " -> "
+ child.getClass().getSimpleName()
+ child.getLocation().orElse(Bytes.EMPTY)
+ "\n";

if (showRepeatingEdges || !result.toString().contains(edgeString)) {
result.append(edgeString);
}
result.append(child.toDot(showRepeatingEdges));
}

return result.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public LeafNode(final Bytes location, final V value) {
this.value = value;
this.valueSerializer = val -> (Bytes) val;
}

/**
* Constructs a new LeafNode with optional location, value.
*
Expand Down Expand Up @@ -104,6 +105,7 @@ public Optional<V> getValue() {
public Optional<Bytes> getLocation() {
return location;
}

/**
* Get the children of the node. A leaf node does not have children, so this method throws an
* UnsupportedOperationException.
Expand Down Expand Up @@ -159,4 +161,26 @@ public boolean isDirty() {
public String print() {
return "Leaf:" + getValue().map(Object::toString).orElse("empty");
}

/**
* Generates DOT representation for the LeafNode.
*
* @return DOT representation of the LeafNode.
*/
@Override
public String toDot(Boolean showRepeatingEdges) {
Bytes locationBytes = getLocation().orElse(Bytes.EMPTY);

return new StringBuilder()
.append(getClass().getSimpleName())
.append(locationBytes)
.append("[location=\"")
.append(locationBytes)
.append("\", suffix=\"")
.append(locationBytes.get(locationBytes.size() - 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doing some tests currently

should display hex representation in order to avoid conversion issues

.append(Bytes.of(locationBytes.get(locationBytes.size() - 1)))

because I have something like that with the current code

LeafNode0xc43a5184f61b02b1e21512facb6deb3c01c1e6909158cccda8267b6618c44f8c[location="0xc43a5184f61b02b1e21512facb6deb3c01c1e6909158cccda8267b6618c44f8c", suffix="-116", value="0x0057600080fd5b82018360208201111561018b57600080fd5b80359060200191"]

.append("\", value=\"")
.append(getValue().orElse(null))
.append("\"]\n")
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,23 @@ default List<Node<V>> getChildren() {
* @return A string representation of the node.
*/
String print();

/**
* Generates DOT representation for the Node.
*
* @param showRepeatingEdges If true, prints all edges; if false, prints only unique edges.
* @return DOT representation of the Node.
*/
String toDot(Boolean showRepeatingEdges);

/**
* Generates DOT representation for the Node.
*
* <p>Representation does not contain repeating edges.
*
* @return DOT representation of the Node.
*/
default String toDot() {
return toDot(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ public String print() {
return "[NULL-LEAF]";
}

/**
* Generates DOT representation for the NullLeafNode.
*
* @return DOT representation of the NullLeafNode.
*/
@Override
public String toDot(Boolean showRepeatingEdges) {
String result =
getClass().getSimpleName()
+ getLocation().orElse(Bytes.EMPTY)
+ " [location=\""
+ getLocation().orElse(Bytes.EMPTY)
+ "\"]\n";
return result;
}

/**
* Check if the `NullNode` is marked as dirty (needing to be persisted).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ public String print() {
return "[NULL]";
}

/**
* Generates DOT representation for the NullNode.
*
* @return DOT representation of the NullNode.
*/
@Override
public String toDot(Boolean showRepeatingEdges) {
String result =
getClass().getSimpleName()
+ getLocation().orElse(Bytes.EMPTY)
+ "[location=\""
+ getLocation().orElse(Bytes.EMPTY)
+ "\"]\n";
return result;
}

/**
* Check if the `NullNode` is marked as dirty (needing to be persisted).
*
Expand Down
Loading
Loading