Skip to content

Commit af011d2

Browse files
shemnonmacfarla
andauthored
Use Bytes Trie to track warm addresses (#6069)
* Use Bytes Trie to track warm addresses Move from a java HashSet to a custom Trie based on bytes to store the warm addresses, creates, and self-destructs. This avoids needing to calculate java hashes or engage in using custom Comparators. Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com> * codeql scan Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com> --------- Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com> Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
1 parent 3310c41 commit af011d2

File tree

6 files changed

+499
-10
lines changed

6 files changed

+499
-10
lines changed

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION;
2020
import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH;
2121

22+
import org.hyperledger.besu.collections.trie.BytesTrieSet;
2223
import org.hyperledger.besu.datatypes.AccessListEntry;
2324
import org.hyperledger.besu.datatypes.Address;
2425
import org.hyperledger.besu.datatypes.Wei;
@@ -43,7 +44,6 @@
4344
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
4445

4546
import java.util.Deque;
46-
import java.util.HashSet;
4747
import java.util.List;
4848
import java.util.Optional;
4949
import java.util.Set;
@@ -318,7 +318,7 @@ public TransactionProcessingResult processTransaction(
318318
final List<AccessListEntry> accessListEntries = transaction.getAccessList().orElse(List.of());
319319
// we need to keep a separate hash set of addresses in case they specify no storage.
320320
// No-storage is a common pattern, especially for Externally Owned Accounts
321-
final Set<Address> addressList = new HashSet<>();
321+
final Set<Address> addressList = new BytesTrieSet<>(Address.SIZE);
322322
final Multimap<Address, Bytes32> storageList = HashMultimap.create();
323323
int accessListStorageCount = 0;
324324
for (final var entry : accessListEntries) {
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/*
2+
* Copyright contributors to Hyperledger Besu
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*
15+
*/
16+
package org.hyperledger.besu.collections.trie;
17+
18+
import java.util.AbstractSet;
19+
import java.util.ArrayDeque;
20+
import java.util.Arrays;
21+
import java.util.Deque;
22+
import java.util.Iterator;
23+
import java.util.NoSuchElementException;
24+
import java.util.Objects;
25+
26+
import org.apache.tuweni.bytes.Bytes;
27+
28+
/**
29+
* A Bytes optimized set that stores values in a trie by byte
30+
*
31+
* @param <E> Type of trie
32+
*/
33+
public class BytesTrieSet<E extends Bytes> extends AbstractSet<E> {
34+
35+
record Node<E extends Bytes>(byte[] leafArray, E leafObject, Node<E>[] children) {
36+
37+
@Override
38+
public boolean equals(final Object o) {
39+
if (this == o) return true;
40+
if (!(o instanceof Node<?> node)) return false;
41+
return Arrays.equals(leafArray, node.leafArray)
42+
&& Objects.equals(leafObject, node.leafObject)
43+
&& Arrays.equals(children, node.children);
44+
}
45+
46+
@Override
47+
public int hashCode() {
48+
int result = Objects.hash(leafObject);
49+
result = 31 * result + Arrays.hashCode(leafArray);
50+
result = 31 * result + Arrays.hashCode(children);
51+
return result;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
final StringBuilder sb = new StringBuilder("Node{");
57+
sb.append("leaf=");
58+
if (leafObject == null) sb.append("null");
59+
else {
60+
sb.append('[');
61+
System.out.println(leafObject.toHexString());
62+
sb.append(']');
63+
}
64+
sb.append(", children=");
65+
if (children == null) sb.append("null");
66+
else {
67+
sb.append('[');
68+
for (int i = 0; i < children.length; ++i) {
69+
if (children[i] == null) {
70+
continue;
71+
}
72+
sb.append(i == 0 ? "" : ", ").append(i).append("=").append(children[i]);
73+
}
74+
sb.append(']');
75+
}
76+
sb.append('}');
77+
return sb.toString();
78+
}
79+
}
80+
81+
Node<E> root;
82+
83+
int size = 0;
84+
final int byteLength;
85+
86+
/**
87+
* Create a BytesTrieSet with a fixed length
88+
*
89+
* @param byteLength length in bytes of the stored types
90+
*/
91+
public BytesTrieSet(final int byteLength) {
92+
this.byteLength = byteLength;
93+
}
94+
95+
static class NodeWalker<E extends Bytes> {
96+
final Node<E> node;
97+
int lastRead;
98+
99+
NodeWalker(final Node<E> node) {
100+
this.node = node;
101+
this.lastRead = -1;
102+
}
103+
104+
NodeWalker<E> nextNodeWalker() {
105+
if (node.children == null) {
106+
return null;
107+
}
108+
while (lastRead < 255) {
109+
lastRead++;
110+
Node<E> child = node.children[lastRead];
111+
if (child != null) {
112+
return new NodeWalker<>(child);
113+
}
114+
}
115+
return null;
116+
}
117+
118+
E thisNode() {
119+
return node.leafObject;
120+
}
121+
}
122+
123+
@Override
124+
public Iterator<E> iterator() {
125+
var result =
126+
new Iterator<E>() {
127+
final Deque<NodeWalker<E>> stack = new ArrayDeque<>();
128+
E next;
129+
E last;
130+
131+
@Override
132+
public boolean hasNext() {
133+
return next != null;
134+
}
135+
136+
@Override
137+
public E next() {
138+
if (next == null) {
139+
throw new NoSuchElementException();
140+
}
141+
last = next;
142+
advance();
143+
return last;
144+
}
145+
146+
@Override
147+
public void remove() {
148+
BytesTrieSet.this.remove(last);
149+
}
150+
151+
void advance() {
152+
while (!stack.isEmpty()) {
153+
NodeWalker<E> thisStep = stack.peek();
154+
var nextStep = thisStep.nextNodeWalker();
155+
if (nextStep == null) {
156+
stack.pop();
157+
if (thisStep.thisNode() != null) {
158+
next = thisStep.thisNode();
159+
return;
160+
}
161+
} else {
162+
stack.push(nextStep);
163+
}
164+
}
165+
next = null;
166+
}
167+
};
168+
if (root != null) {
169+
result.stack.add(new NodeWalker<>(root));
170+
}
171+
result.advance();
172+
return result;
173+
}
174+
175+
@Override
176+
public int size() {
177+
return size;
178+
}
179+
180+
@Override
181+
public boolean contains(final Object o) {
182+
if (!(o instanceof Bytes bytes)) {
183+
throw new IllegalArgumentException(
184+
"Expected Bytes, got " + (o == null ? "null" : o.getClass().getName()));
185+
}
186+
byte[] array = bytes.toArrayUnsafe();
187+
if (array.length != byteLength) {
188+
throw new IllegalArgumentException(
189+
"Byte array is size " + array.length + " but set is size " + byteLength);
190+
}
191+
if (root == null) {
192+
return false;
193+
}
194+
int level = 0;
195+
Node<E> current = root;
196+
while (current != null) {
197+
if (current.leafObject != null) {
198+
return Arrays.compare(current.leafArray, array) == 0;
199+
}
200+
current = current.children[array[level] & 0xff];
201+
level++;
202+
}
203+
return false;
204+
}
205+
206+
@Override
207+
public boolean remove(final Object o) {
208+
// Two base cases, size==0 and size==1;
209+
if (!(o instanceof Bytes bytes)) {
210+
throw new IllegalArgumentException(
211+
"Expected Bytes, got " + (o == null ? "null" : o.getClass().getName()));
212+
}
213+
byte[] array = bytes.toArrayUnsafe();
214+
if (array.length != byteLength) {
215+
throw new IllegalArgumentException(
216+
"Byte array is size " + array.length + " but set is size " + byteLength);
217+
}
218+
// Two base cases, size==0 and size==1;
219+
if (root == null) {
220+
// size==0 is easy, empty
221+
return false;
222+
}
223+
if (root.leafObject != null) {
224+
// size==1 just check and possibly remove the root
225+
if (Arrays.compare(array, root.leafArray) == 0) {
226+
root = null;
227+
size--;
228+
return true;
229+
} else {
230+
return false;
231+
}
232+
}
233+
int level = 0;
234+
Node<E> current = root;
235+
do {
236+
int index = array[level] & 0xff;
237+
Node<E> next = current.children[index];
238+
if (next == null) {
239+
return false;
240+
}
241+
if (next.leafObject != null) {
242+
if (Arrays.compare(array, next.leafArray) == 0) {
243+
// TODO there is no cleanup of empty branches
244+
current.children[index] = null;
245+
size--;
246+
return true;
247+
} else {
248+
return false;
249+
}
250+
}
251+
current = next;
252+
253+
level++;
254+
} while (true);
255+
}
256+
257+
@SuppressWarnings({"unchecked", "rawtypes"})
258+
@Override
259+
public boolean add(final E bytes) {
260+
byte[] array = bytes.toArrayUnsafe();
261+
if (array.length != byteLength) {
262+
throw new IllegalArgumentException(
263+
"Byte array is size " + array.length + " but set is size " + byteLength);
264+
}
265+
// Two base cases, size==0 and size==1;
266+
if (root == null) {
267+
// size==0 is easy, just add
268+
root = new Node<>(array, bytes, null);
269+
size++;
270+
return true;
271+
}
272+
if (root.leafObject != null) {
273+
// size==1 first check then if no match make it look like n>1
274+
if (Arrays.compare(array, root.leafArray) == 0) {
275+
return false;
276+
}
277+
Node<E> oldRoot = root;
278+
root = new Node<>(null, null, new Node[256]);
279+
root.children[oldRoot.leafArray[0] & 0xff] = oldRoot;
280+
}
281+
int level = 0;
282+
Node<E> current = root;
283+
do {
284+
int index = array[level] & 0xff;
285+
Node<E> next = current.children[index];
286+
if (next == null) {
287+
next = new Node<>(array, bytes, null);
288+
current.children[index] = next;
289+
size++;
290+
return true;
291+
}
292+
if (next.leafObject != null) {
293+
if (Arrays.compare(array, next.leafArray) == 0) {
294+
return false;
295+
}
296+
Node<E> newLeaf = new Node<>(null, null, new Node[256]);
297+
newLeaf.children[next.leafArray[level + 1] & 0xff] = next;
298+
current.children[index] = newLeaf;
299+
next = newLeaf;
300+
}
301+
level++;
302+
303+
current = next;
304+
305+
} while (true);
306+
}
307+
308+
@Override
309+
public void clear() {
310+
root = null;
311+
}
312+
}

evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.common.base.Preconditions.checkNotNull;
1818

19+
import org.hyperledger.besu.collections.trie.BytesTrieSet;
1920
import org.hyperledger.besu.datatypes.Address;
2021
import org.hyperledger.besu.datatypes.Hash;
2122
import org.hyperledger.besu.datatypes.VersionedHash;
@@ -41,7 +42,6 @@
4142
import java.math.BigInteger;
4243
import java.util.Collection;
4344
import java.util.Deque;
44-
import java.util.HashSet;
4545
import java.util.List;
4646
import java.util.Objects;
4747
import java.util.Optional;
@@ -80,7 +80,7 @@ public class EVMExecutor {
8080
List.of(MaxCodeSizeRule.of(0x6000), PrefixCodeRule.of());
8181
private long initialNonce = 1;
8282
private Collection<Address> forceCommitAddresses = List.of(Address.fromHexString("0x03"));
83-
private Set<Address> accessListWarmAddresses = new HashSet<>();
83+
private Set<Address> accessListWarmAddresses = new BytesTrieSet<>(Address.SIZE);
8484
private Multimap<Address, Bytes32> accessListWarmStorage = HashMultimap.create();
8585
private MessageCallProcessor messageCallProcessor = null;
8686
private ContractCreationProcessor contractCreationProcessor = null;

evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.base.Preconditions.checkState;
1818
import static java.util.Collections.emptySet;
1919

20+
import org.hyperledger.besu.collections.trie.BytesTrieSet;
2021
import org.hyperledger.besu.collections.undo.UndoSet;
2122
import org.hyperledger.besu.collections.undo.UndoTable;
2223
import org.hyperledger.besu.datatypes.Address;
@@ -38,7 +39,6 @@
3839
import java.util.ArrayList;
3940
import java.util.Deque;
4041
import java.util.HashMap;
41-
import java.util.HashSet;
4242
import java.util.List;
4343
import java.util.Map;
4444
import java.util.Optional;
@@ -1707,7 +1707,7 @@ public MessageFrame build() {
17071707
new TxValues(
17081708
blockHashLookup,
17091709
maxStackSize,
1710-
UndoSet.of(new HashSet<>()),
1710+
UndoSet.of(new BytesTrieSet<>(Address.SIZE)),
17111711
UndoTable.of(HashBasedTable.create()),
17121712
originator,
17131713
gasPrice,
@@ -1717,8 +1717,8 @@ public MessageFrame build() {
17171717
miningBeneficiary,
17181718
versionedHashes,
17191719
UndoTable.of(HashBasedTable.create()),
1720-
UndoSet.of(new HashSet<>()),
1721-
UndoSet.of(new HashSet<>()));
1720+
UndoSet.of(new BytesTrieSet<>(Address.SIZE)),
1721+
UndoSet.of(new BytesTrieSet<>(Address.SIZE)));
17221722
updater = worldUpdater;
17231723
newStatic = isStatic;
17241724
} else {

0 commit comments

Comments
 (0)