Skip to content

Commit

Permalink
ADD: nullity bitmap and hashes in storage
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Zamojski <[email protected]>
  • Loading branch information
thomas-quadratic committed Jun 14, 2024
1 parent 0de0eac commit 715abf4
Show file tree
Hide file tree
Showing 14 changed files with 549 additions and 402 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* @param <V> The type of values in the Verkle Trie.
*/
public class SimpleBatchedVerkleTrie<K extends Bytes, V extends Bytes>
extends SimpleVerkleTrie<K, V> implements VerkleTrie<K, V> {
extends SimpleVerkleTrie<K, V> {

private final VerkleTrieBatchHasher batchProcessor;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,25 @@
/**
* Processes batches of trie nodes for efficient hashing.
*
* <p>This class manages the batching and hashing of trie nodes to optimize performance.
* <p>
* This class manages the batching and hashing of trie nodes to optimize
* performance.
*/
public class VerkleTrieBatchHasher {

private static final Logger LOG = LogManager.getLogger(VerkleTrieBatchHasher.class);
private static final int MAX_BATCH_SIZE = 1000; // Maximum number of nodes in a batch
private static final Bytes[] EMPTY_ARRAY_TEMPLATE = new Bytes[0];
private final Hasher hasher = new PedersenHasher(); // Hasher for node hashing
private final Map<Bytes, Node<?>> updatedNodes =
new HashMap<>(); // Map to hold nodes for batching
private final Map<Bytes, Node<?>> updatedNodes = new HashMap<>(); // Map to hold nodes for batching

/**
* Adds a node for future batching. If the node is a NullNode or NullLeafNode and the location is
* Adds a node for future batching. If the node is a NullNode or NullLeafNode
* and the location is
* not empty, it removes the node from the batch.
*
* @param location The location of the node.
* @param node The node to add.
* @param node The node to add.
*/
public void addNodeToBatch(final Optional<Bytes> location, final Node<?> node) {
if ((node instanceof NullNode<?> || node instanceof NullLeafNode<?>)
Expand All @@ -81,16 +83,16 @@ public Map<Bytes, Node<?>> getNodesToBatch() {
}

/**
* Processes the nodes in batches. Sorts the nodes by their location and hashes them in batches.
* Processes the nodes in batches. Sorts the nodes by their location and hashes
* them in batches.
* Clears the batch after processing.
*/
public void calculateStateRoot() {
if (updatedNodes.isEmpty()) {
return;
}

final List<Map.Entry<Bytes, Node<?>>> sortedNodesByLocation =
new ArrayList<>(updatedNodes.entrySet());
final List<Map.Entry<Bytes, Node<?>>> sortedNodesByLocation = new ArrayList<>(updatedNodes.entrySet());
sortedNodesByLocation.sort(
(entry1, entry2) -> Integer.compare(entry2.getKey().size(), entry1.getKey().size()));

Expand All @@ -108,7 +110,8 @@ public void calculateStateRoot() {
}
if (location.isEmpty()) {
// We will end up updating the root node. Once all the batching is finished,
// we will update the previous states of the nodes by setting them to the new ones.
// we will update the previous states of the nodes by setting them to the new
// ones.
calculateRootInternalNodeHash((InternalNode<?>) node);
updatedNodes.forEach(
(__, n) -> {
Expand Down Expand Up @@ -151,8 +154,7 @@ private void processBatch(List<Node<?>> nodes) {
"Executing batch hashing for {} commitments of stem (left/right) and internal nodes.",
commitments.size());
Iterator<Bytes> commitmentsIterator = new ArrayList<>(commitments).iterator();
Iterator<Bytes32> hashesIterator =
hasher.hashMany(commitments.toArray(EMPTY_ARRAY_TEMPLATE)).iterator();
Iterator<Bytes32> hashesIterator = hasher.hashMany(commitments.toArray(EMPTY_ARRAY_TEMPLATE)).iterator();

// reset commitments list for stem
commitments.clear();
Expand Down Expand Up @@ -182,8 +184,9 @@ private void processBatch(List<Node<?>> nodes) {
}

private void calculateRootInternalNodeHash(final InternalNode<?> internalNode) {
final Bytes32 hash = Bytes32.wrap(getRootNodeCommitments(internalNode).get(0));
internalNode.replaceHash(hash, hash);
final Bytes commitment = getRootNodeCommitments(internalNode).get(0);
final Bytes32 hash = hasher.compress(commitment);
internalNode.replaceHash(hash, commitment);
}

private void calculateStemNodeHashes(
Expand Down Expand Up @@ -226,9 +229,11 @@ private List<Bytes> getStemNodeLeftRightCommitments(StemNode<?> stemNode) {
Node<?> node = stemNode.child((byte) idx);

Optional<Bytes> oldValue = node.getPrevious().map(Bytes.class::cast);
// We should not recalculate a node if it is persisted and has not undergone an update since
// We should not recalculate a node if it is persisted and has not undergone an
// update since
// its last save.
// If a child does not have a previous value, it means that it is a new node and we must
// If a child does not have a previous value, it means that it is a new node and
// we must
// therefore recalculate it.
if (!(node instanceof StoredNode<?>) && (oldValue.isEmpty() || node.isDirty())) {
if (idx < halfSize) {
Expand Down Expand Up @@ -297,9 +302,11 @@ private List<Bytes> getInternalNodeCommitments(InternalNode<?> internalNode) {
for (int i = 0; i < size; i++) {
final Node<?> node = internalNode.child((byte) i);
Optional<Bytes> oldValue = node.getPrevious().map(Bytes.class::cast);
// We should not recalculate a node if it is persisted and has not undergone an update since
// We should not recalculate a node if it is persisted and has not undergone an
// update since
// its last save.
// If a child does not have a previous value, it means that it is a new node and we must
// If a child does not have a previous value, it means that it is a new node and
// we must
// therefore recalculate it.
if (!(node instanceof StoredNode<?>) && (oldValue.isEmpty() || node.isDirty())) {
indices.add((byte) i);
Expand All @@ -315,12 +322,12 @@ private List<Bytes> getInternalNodeCommitments(InternalNode<?> internalNode) {
private List<Bytes> getRootNodeCommitments(InternalNode<?> internalNode) {
int size = InternalNode.maxChild();
final List<Bytes> commitmentsHashes = new ArrayList<>();
final List<Bytes> newValues = new ArrayList<>();
final List<Bytes32> newValues = new ArrayList<>();
for (int i = 0; i < size; i++) {
final Node<?> node = internalNode.child((byte) i);
newValues.add(node.getHash().get());
}
commitmentsHashes.add(hasher.commitRoot(newValues.toArray(new Bytes[] {})));
commitmentsHashes.add(hasher.commit(newValues.toArray(new Bytes32[] {})));
return commitmentsHashes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
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.NullLeafNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.StemNode;
import org.hyperledger.besu.ethereum.trie.verkle.node.StoredNode;

Expand All @@ -29,7 +31,7 @@

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.rlp.RLP;
import org.apache.tuweni.bytes.MutableBytes;

/** Node types that are saved to storage. */
enum NodeType {
Expand All @@ -49,9 +51,10 @@ public class StoredNodeFactory<V> implements NodeFactory<V> {
private final Function<Bytes, V> valueDeserializer;

/**
* Creates a new StoredNodeFactory with the given node loader and value deserializer.
* Creates a new StoredNodeFactory with the given node loader and value
* deserializer.
*
* @param nodeLoader The loader for retrieving stored nodes.
* @param nodeLoader The loader for retrieving stored nodes.
* @param valueDeserializer The function to deserialize values from Bytes.
*/
public StoredNodeFactory(NodeLoader nodeLoader, Function<Bytes, V> valueDeserializer) {
Expand All @@ -63,13 +66,15 @@ public StoredNodeFactory(NodeLoader nodeLoader, Function<Bytes, V> valueDeserial
* Retrieves a Verkle Trie node from stored data based on the location and hash.
*
* @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.
* @param hash Node's hash
* @return An optional containing the retrieved node, or an empty optional if
* the node is not
* found.
*/
@Override
public Optional<Node<V>> retrieve(final Bytes location, final Bytes32 hash) {
/* Currently, Root and Leaf are distinguishable by location.
/*
* Currently, Root and Leaf are distinguishable by location.
* To distinguish internal from stem, we further need values.
* Currently, they are distinguished by values length.
*/
Expand All @@ -78,78 +83,153 @@ public Optional<Node<V>> retrieve(final Bytes location, final Bytes32 hash) {
return Optional.empty();
}
Bytes encodedValues = optionalEncodedValues.get();
List<Bytes> values = RLP.decodeToList(encodedValues, reader -> reader.readValue().copy());
final int locLength = location.size();
final int nValues = values.size();
NodeType type =
(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));
case STEM -> Optional.of(createStemNode(location, values));
int indicator = (int) encodedValues.get(0);
Bytes values = encodedValues.slice(1);
return switch (indicator) {
case 0 -> Optional.of(createRootNode(values));
case 1 -> Optional.of(createInternalNode(location, values, hash));
case 2 -> Optional.of(createStemNode(location, values, hash));
default -> Optional.empty();
};
}

private List<Boolean> decodeIsNull(Bytes nullBitMap, int nChild) {
List<Boolean> isNull = new ArrayList<>(nChild);
for (int i = 0; i < nChild / 8; i++) {
int mask = 128;
for (int j = 8 * i; j < 8 * (i + 1); j++) {
isNull.add((nullBitMap.get(i) & mask) != 0);
mask = mask >> 1;
}
}
return isNull;

}

private List<Bytes32> decodeScalars(List<Boolean> isNull, Bytes values) {
int nChild = isNull.size();
List<Bytes32> scalars = new ArrayList<>(nChild);
for (int i = 0; i < nChild; i++) {
if (!isNull.get(i)) {
Bytes32 scalar = (Bytes32) values.slice(0, 32);
values = values.slice(32);
scalars.add(scalar);
} else {
scalars.add(Bytes32.ZERO);
}
}
return scalars;
}

private List<Bytes> decodeValues(List<Boolean> isNull, Bytes encoded) {
int nChild = isNull.size();
List<Bytes> values = new ArrayList<>(nChild);
for (int i = 0; i < nChild; i++) {
if (!isNull.get(i)) {
Bytes value = encoded.slice(0, 32);
encoded = encoded.slice(32);
values.add(value);
} else {
values.add(Bytes.EMPTY);
}
}
return values;
}

/**
* Creates a rootNode using the provided location, hash, and path.
*
* @param encodedValues List of Bytes values retrieved from storage.
* @return A internalNode instance.
*/
InternalNode<V> createRootNode(Bytes encodedValues) {
Bytes32 hash = (Bytes32) encodedValues.slice(0, 32);
return createInternalNode(Bytes.EMPTY, encodedValues.slice(32), hash);
}

/**
* 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.
* @param location The location of the internalNode.
* @param encodedValues List of Bytes values retrieved from storage.
* @param hash Node's hash value.
* @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);
InternalNode<V> createInternalNode(Bytes location, Bytes encodedValues, Bytes32 hash) {
int nChild = InternalNode.maxChild();
Bytes commitment = encodedValues.slice(0, 64);
Bytes nullBitMap = encodedValues.slice(64, 32);
Bytes values = encodedValues.slice(96);

List<Boolean> isNull = decodeIsNull(nullBitMap, nChild);
List<Bytes32> scalars = decodeScalars(isNull, values);

List<Node<V>> children = new ArrayList<>(nChild);
MutableBytes nextBuffer = MutableBytes.create(location.size() + 1);
Bytes.concatenate(location, Bytes.of(0)).copyTo(nextBuffer);
for (int i = 0; i < nChild; i++) {
children.add(new StoredNode<>(this, Bytes.concatenate(location, Bytes.of(i))));
nextBuffer.set(location.size(), Bytes.of(i));
Bytes nextLoc = nextBuffer.copy();
if (isNull.get(i)) {
children.add(new NullNode<V>());
} else {
children.add(new StoredNode<V>(this, nextLoc, scalars.get(i)));
}
}
final Bytes32 hash = (Bytes32) values.get(0);
final Bytes commitment = 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 values List of Bytes values retrieved from storage.
* @param location The location of the BranchNode.
* @param encodedValues List of Bytes values retrieved from storage.
* @param hash Node's hash value.
* @return A BranchNode instance.
*/
StemNode<V> createStemNode(Bytes location, List<Bytes> values) {
StemNode<V> createStemNode(Bytes location, Bytes encodedValues, Bytes32 hash) {
final int nChild = StemNode.maxChild();
final Bytes stem = values.get(0);
final Bytes32 hash = (Bytes32) values.get(1);
final Bytes commitment = values.get(2);
final Bytes32 leftHash = (Bytes32) values.get(3);
final Bytes leftCommitment = values.get(4);
final Bytes32 rightHash = (Bytes32) values.get(5);
final Bytes rightCommitment = values.get(6);
ArrayList<Node<V>> children = new ArrayList<Node<V>>(nChild);
Bytes stem = encodedValues.slice(0, 31);
Bytes commitment = encodedValues.slice(31, 64);
Bytes leftCommitment = encodedValues.slice(95, 64);
Bytes rightCommitment = encodedValues.slice(159, 64);
Bytes32 leftHash = (Bytes32) encodedValues.slice(223, 32);
Bytes32 rightHash = (Bytes32) encodedValues.slice(255, 32);

Bytes nullBitMap = encodedValues.slice(287, 32);
Bytes encoded = encodedValues.slice(319);

List<Boolean> isNull = decodeIsNull(nullBitMap, nChild);
List<Bytes> values = decodeValues(isNull, encoded);

List<Node<V>> children = new ArrayList<>(nChild);
MutableBytes nextBuffer = MutableBytes.create(stem.size() + 1);
Bytes.concatenate(stem, Bytes.of(0)).copyTo(nextBuffer);
for (int i = 0; i < nChild; i++) {
children.add(new StoredNode<>(this, Bytes.concatenate(stem, Bytes.of(i))));
nextBuffer.set(stem.size(), Bytes.of(i));
Bytes nextLoc = nextBuffer.copy();
if (isNull.get(i)) {
children.add(new NullLeafNode<V>());
} else {
children.add(createLeafNode(nextLoc, values.get(i)));
}
}
return new StemNode<V>(
location,
stem,
hash,
commitment,
leftHash,
leftCommitment,
rightHash,
rightCommitment,
location, stem,
hash, commitment,
leftHash, leftCommitment, rightHash, rightCommitment,
children);
}

/**
* Creates a LeafNode using the provided location, path, and value.
*
* @param key The key of the LeafNode.
* @param values List of Bytes values retrieved from storage.
* @param key The key of the LeafNode.
* @param encodedValue Leaf value retrieved from storage.
* @return A LeafNode instance.
*/
LeafNode<V> createLeafNode(Bytes key, List<Bytes> values) {
V value = valueDeserializer.apply(values.get(0));
LeafNode<V> createLeafNode(Bytes key, Bytes encodedValue) {
V value = valueDeserializer.apply(encodedValue);
return new LeafNode<V>(Optional.of(key), value);
}
}
Loading

0 comments on commit 715abf4

Please sign in to comment.