Skip to content

Commit b062368

Browse files
committed
Various TransactionType small optimizations
Signed-off-by: Fabio Di Fabio <[email protected]>
1 parent bdcb5b4 commit b062368

File tree

11 files changed

+198
-79
lines changed

11 files changed

+198
-79
lines changed

datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
*/
1515
package org.hyperledger.besu.datatypes;
1616

17-
import java.util.Arrays;
1817
import java.util.EnumSet;
18+
import java.util.Optional;
1919
import java.util.Set;
2020

2121
/** The enum Transaction type. */
2222
public enum TransactionType {
2323
/** The Frontier. */
24-
FRONTIER(0xf8 /* this is serialized as 0x0 in TransactionCompleteResult */),
24+
FRONTIER(0xf8, 0x00),
2525
/** Access list transaction type. */
2626
ACCESS_LIST(0x01),
2727
/** Eip1559 transaction type. */
@@ -32,15 +32,65 @@ public enum TransactionType {
3232
DELEGATE_CODE(0x04);
3333

3434
private static final Set<TransactionType> ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES =
35-
Set.of(ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE);
35+
EnumSet.of(ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE);
3636

37-
private static final EnumSet<TransactionType> LEGACY_FEE_MARKET_TRANSACTION_TYPES =
38-
EnumSet.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST);
37+
private static final Set<TransactionType> LEGACY_FEE_MARKET_TRANSACTION_TYPES =
38+
EnumSet.of(FRONTIER, ACCESS_LIST);
3939

40-
private final int typeValue;
40+
// The theoretical boundaries of the first byte of the RLP of a Frontier transaction.
41+
// In practice Frontier transactions almost always start with 0xf8 or 0xf9.
42+
private static final byte MIN_LEGACY_TX_OPAQUE_BYTE = (byte) 0xc0;
43+
private static final byte MAX_LEGACY_TX_OPAQUE_BYTE = (byte) 0xfe;
44+
45+
// This array helps to translate the first opaque byte of an RLP encoded transaction
46+
// to its type.
47+
// Note that Frontier type occupies more slots, since there are different first byte values,
48+
// that represent such type.
49+
// Holes in the array represents invalid first byte values, for which there is not (yet)
50+
// a defined transaction type
51+
private static final TransactionType[] transactionTypeByOpaqueByte =
52+
new TransactionType[Byte.toUnsignedInt(MAX_LEGACY_TX_OPAQUE_BYTE) + 1];
53+
54+
private static final TransactionType[] transactionTypeByEthSerializedType =
55+
new TransactionType[values().length];
56+
57+
static {
58+
EnumSet.allOf(TransactionType.class).stream()
59+
.forEach(
60+
tt -> {
61+
tt.requireChainId = tt != FRONTIER;
62+
tt.supportAccessList = ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES.contains(tt);
63+
tt.supportBaseFeeMarket = !LEGACY_FEE_MARKET_TRANSACTION_TYPES.contains(tt);
64+
tt.supportBlob = tt == BLOB;
65+
tt.supportDelegatedCode = tt == DELEGATE_CODE;
66+
if (tt == FRONTIER) {
67+
for (int i = Byte.toUnsignedInt(MIN_LEGACY_TX_OPAQUE_BYTE);
68+
i < Byte.toUnsignedInt(MAX_LEGACY_TX_OPAQUE_BYTE);
69+
i++) {
70+
transactionTypeByOpaqueByte[i] = FRONTIER;
71+
}
72+
} else {
73+
transactionTypeByOpaqueByte[tt.getSerializedType()] = tt;
74+
}
75+
transactionTypeByEthSerializedType[tt.getEthSerializedType()] = tt;
76+
});
77+
}
78+
79+
private final byte typeValue;
80+
private final byte serializedType;
81+
boolean requireChainId;
82+
boolean supportAccessList;
83+
boolean supportBaseFeeMarket;
84+
boolean supportBlob;
85+
boolean supportDelegatedCode;
86+
87+
TransactionType(final int typeValue, final int serializedType) {
88+
this.typeValue = (byte) typeValue;
89+
this.serializedType = (byte) serializedType;
90+
}
4191

4292
TransactionType(final int typeValue) {
43-
this.typeValue = typeValue;
93+
this(typeValue, typeValue);
4494
}
4595

4696
/**
@@ -49,7 +99,7 @@ public enum TransactionType {
4999
* @return the serialized type
50100
*/
51101
public byte getSerializedType() {
52-
return (byte) this.typeValue;
102+
return typeValue;
53103
}
54104

55105
/**
@@ -60,30 +110,35 @@ public byte getSerializedType() {
60110
* @return the serialized type
61111
*/
62112
public byte getEthSerializedType() {
63-
return (this == FRONTIER ? 0x00 : this.getSerializedType());
113+
return serializedType;
64114
}
65115

66116
/**
67-
* Convert TransactionType from int serialized type value.
117+
* Convert from opaque byte to type value.
68118
*
69-
* @param serializedTypeValue the serialized type value
119+
* @param opaqueByte the opaque byte from serialized bytes
70120
* @return the transaction type
71121
*/
72-
public static TransactionType of(final int serializedTypeValue) {
73-
return Arrays.stream(
74-
new TransactionType[] {
75-
TransactionType.FRONTIER,
76-
TransactionType.ACCESS_LIST,
77-
TransactionType.EIP1559,
78-
TransactionType.BLOB,
79-
TransactionType.DELEGATE_CODE
80-
})
81-
.filter(transactionType -> transactionType.typeValue == serializedTypeValue)
82-
.findFirst()
83-
.orElseThrow(
84-
() ->
85-
new IllegalArgumentException(
86-
String.format("Unsupported transaction type %x", serializedTypeValue)));
122+
public static Optional<TransactionType> fromOpaque(final byte opaqueByte) {
123+
try {
124+
return Optional.ofNullable(transactionTypeByOpaqueByte[Byte.toUnsignedInt(opaqueByte)]);
125+
} catch (final ArrayIndexOutOfBoundsException e) {
126+
return Optional.empty();
127+
}
128+
}
129+
130+
/**
131+
* Convert from ETH serialized byte to type value.
132+
*
133+
* @param ethSerializedType the opaque byte from serialized bytes
134+
* @return the transaction type
135+
*/
136+
public static Optional<TransactionType> fromEthSerializedType(final byte ethSerializedType) {
137+
try {
138+
return Optional.ofNullable(transactionTypeByEthSerializedType[ethSerializedType]);
139+
} catch (final ArrayIndexOutOfBoundsException e) {
140+
return Optional.empty();
141+
}
87142
}
88143

89144
/**
@@ -92,7 +147,7 @@ public static TransactionType of(final int serializedTypeValue) {
92147
* @return the boolean
93148
*/
94149
public boolean supportsAccessList() {
95-
return ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES.contains(this);
150+
return supportAccessList;
96151
}
97152

98153
/**
@@ -101,7 +156,7 @@ public boolean supportsAccessList() {
101156
* @return the boolean
102157
*/
103158
public boolean supports1559FeeMarket() {
104-
return !LEGACY_FEE_MARKET_TRANSACTION_TYPES.contains(this);
159+
return supportBaseFeeMarket;
105160
}
106161

107162
/**
@@ -110,7 +165,7 @@ public boolean supports1559FeeMarket() {
110165
* @return the boolean
111166
*/
112167
public boolean requiresChainId() {
113-
return !this.equals(FRONTIER);
168+
return requireChainId;
114169
}
115170

116171
/**
@@ -119,7 +174,7 @@ public boolean requiresChainId() {
119174
* @return the boolean
120175
*/
121176
public boolean supportsBlob() {
122-
return this.equals(BLOB);
177+
return supportBlob;
123178
}
124179

125180
/**
@@ -128,15 +183,6 @@ public boolean supportsBlob() {
128183
* @return the boolean
129184
*/
130185
public boolean supportsDelegateCode() {
131-
return this.equals(DELEGATE_CODE);
132-
}
133-
134-
/**
135-
* Does transaction type require code.
136-
*
137-
* @return the boolean
138-
*/
139-
public boolean requiresCodeDelegation() {
140-
return this.equals(DELEGATE_CODE);
186+
return supportDelegatedCode;
141187
}
142188
}

ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/SimpleTestTransactionBuilder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
package org.hyperledger.besu.ethereum.api.jsonrpc;
1616

17+
import static org.hyperledger.besu.datatypes.TransactionType.FRONTIER;
1718
import static org.mockito.Mockito.mock;
1819
import static org.mockito.Mockito.when;
1920

@@ -70,7 +71,9 @@ public static Transaction transaction(
7071
when(transaction.getR()).thenReturn(bigInteger(r));
7172
when(transaction.getS()).thenReturn(bigInteger(s));
7273
when(transaction.getTo()).thenReturn(Optional.ofNullable(address(toAddress)));
73-
when(transaction.getType()).thenReturn(TransactionType.of(Integer.decode(type)));
74+
when(transaction.getType())
75+
.thenReturn(
76+
TransactionType.fromOpaque(Bytes.fromHexString(type, 1).get(0)).orElse(FRONTIER));
7477
when(transaction.getSender()).thenReturn(address(fromAddress));
7578
when(transaction.getPayload()).thenReturn(Bytes.fromHexString(input));
7679
when(transaction.getValue()).thenReturn(wei(value));
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright contributors to 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+
package org.hyperledger.besu.ethereum.core;
16+
17+
import org.hyperledger.besu.datatypes.TransactionType;
18+
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.openjdk.jmh.annotations.Benchmark;
22+
import org.openjdk.jmh.annotations.BenchmarkMode;
23+
import org.openjdk.jmh.annotations.Measurement;
24+
import org.openjdk.jmh.annotations.Mode;
25+
import org.openjdk.jmh.annotations.OutputTimeUnit;
26+
import org.openjdk.jmh.annotations.Scope;
27+
import org.openjdk.jmh.annotations.Setup;
28+
import org.openjdk.jmh.annotations.State;
29+
import org.openjdk.jmh.annotations.Warmup;
30+
import org.openjdk.jmh.infra.Blackhole;
31+
32+
@State(Scope.Thread)
33+
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS)
34+
@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
35+
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
36+
@BenchmarkMode(Mode.AverageTime)
37+
public class TransactionTypeBenchmark {
38+
39+
@Setup()
40+
public void setUp() {}
41+
42+
@Benchmark
43+
public void ofSerializedType(final Blackhole blackhole) {
44+
blackhole.consume(TransactionType.fromOpaque((byte) 0xf8));
45+
blackhole.consume(TransactionType.fromOpaque((byte) 0x01));
46+
blackhole.consume(TransactionType.fromOpaque((byte) 0x02));
47+
blackhole.consume(TransactionType.fromOpaque((byte) 0x03));
48+
blackhole.consume(TransactionType.fromOpaque((byte) 0x04));
49+
}
50+
}

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ private Transaction(
246246
maxFeePerBlobGas.isPresent(), "Must specify max fee per blob gas for blob transaction");
247247
}
248248

249-
if (transactionType.requiresCodeDelegation()) {
249+
if (transactionType.supportsDelegateCode()) {
250250
checkArgument(
251251
maybeCodeDelegationList.isPresent(),
252252
"Must specify code delegation authorizations for code delegation transaction");

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionDecoder.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,26 @@
2828
import org.hyperledger.besu.datatypes.TransactionType;
2929
import org.hyperledger.besu.datatypes.Wei;
3030
import org.hyperledger.besu.ethereum.core.Transaction;
31+
import org.hyperledger.besu.ethereum.rlp.RLP;
3132
import org.hyperledger.besu.ethereum.rlp.RLPInput;
3233

3334
import java.math.BigInteger;
3435
import java.util.Optional;
3536
import java.util.function.Supplier;
3637

3738
import com.google.common.base.Suppliers;
39+
import org.apache.tuweni.bytes.Bytes;
3840

3941
public class FrontierTransactionDecoder {
4042
// Supplier for the signature algorithm
4143
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
4244
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
4345

44-
public static Transaction decode(final RLPInput input) {
45-
RLPInput transactionRlp = input.readAsRlp();
46+
public static Transaction decode(final Bytes input) {
47+
return decode(RLP.input(input));
48+
}
49+
50+
public static Transaction decode(final RLPInput transactionRlp) {
4651
int size = transactionRlp.currentSize();
4752
transactionRlp.enterList();
4853
final Transaction.Builder builder =
@@ -51,7 +56,7 @@ public static Transaction decode(final RLPInput input) {
5156
.nonce(transactionRlp.readLongScalar())
5257
.gasPrice(Wei.of(transactionRlp.readUInt256Scalar()))
5358
.gasLimit(transactionRlp.readLongScalar())
54-
.to(transactionRlp.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
59+
.to(transactionRlp.readBytes(v -> v.isEmpty() ? null : Address.wrap(v)))
5560
.value(Wei.of(transactionRlp.readUInt256Scalar()))
5661
.payload(transactionRlp.readBytes())
5762
.rawRlp(transactionRlp.raw())

0 commit comments

Comments
 (0)