[4844] Add encodingContext to TransactionEncoder and TransactionDecoder (#5820)

* Add decode type to TransactionDecoder

* Refactoring TransactionDecoder

* Invert methods order

* Use Transaction encoder instead of writeTo

* Move enter and leave list to inner method as pr suggestion

* Size calculation should use opaque bytes instead of rlp

---------

Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
pull/5875/head
Gabriel-Trintinalia 1 year ago committed by GitHub
parent 40273d7d3f
commit 1fa21e2f1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java
  2. 13
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java
  3. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadBodiesResultV1.java
  4. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionPendingResult.java
  5. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java
  6. 55
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java
  7. 6
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java
  8. 21
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  9. 74
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionDecoder.java
  10. 109
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionEncoder.java
  11. 50
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionDecoder.java
  12. 47
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionEncoder.java
  13. 47
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionDecoder.java
  14. 35
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncoder.java
  15. 74
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionDecoder.java
  16. 41
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionEncoder.java
  17. 41
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EncodingContext.java
  18. 76
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionDecoder.java
  19. 36
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionEncoder.java
  20. 265
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java
  21. 206
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java
  22. 7
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java
  23. 41
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncodingTest.java
  24. 27
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoderTest.java
  25. 24
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java
  26. 1
      ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/core/encoding/blob2.txt
  27. 5
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java
  28. 14
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/PooledTransactionsMessage.java

@ -50,6 +50,7 @@ import org.hyperledger.besu.ethereum.core.Deposit;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
@ -170,7 +171,7 @@ public abstract class AbstractEngineNewPayload extends ExecutionEngineJsonRpcMet
transactions =
blockParam.getTransactions().stream()
.map(Bytes::fromHexString)
.map(TransactionDecoder::decodeOpaqueBytes)
.map(in -> TransactionDecoder.decodeOpaqueBytes(in, EncodingContext.BLOCK_BODY))
.collect(Collectors.toList());
} catch (final RLPException | IllegalArgumentException e) {
return respondWithInvalid(

@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockValueCalculator;
import org.hyperledger.besu.ethereum.core.BlockWithReceipts;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import java.util.ArrayList;
@ -95,7 +96,9 @@ public class BlockResultFactory {
public EngineGetPayloadResultV1 payloadTransactionCompleteV1(final Block block) {
final List<String> txs =
block.getBody().getTransactions().stream()
.map(TransactionEncoder::encodeOpaqueBytes)
.map(
transaction ->
TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.BLOCK_BODY))
.map(Bytes::toHexString)
.collect(Collectors.toList());
@ -106,7 +109,9 @@ public class BlockResultFactory {
final BlockWithReceipts blockWithReceipts) {
final List<String> txs =
blockWithReceipts.getBlock().getBody().getTransactions().stream()
.map(TransactionEncoder::encodeOpaqueBytes)
.map(
transaction ->
TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.BLOCK_BODY))
.map(Bytes::toHexString)
.collect(Collectors.toList());
@ -132,7 +137,9 @@ public class BlockResultFactory {
final BlockWithReceipts blockWithReceipts) {
final List<String> txs =
blockWithReceipts.getBlock().getBody().getTransactions().stream()
.map(TransactionEncoder::encodeOpaqueBytes)
.map(
transaction ->
TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.BLOCK_BODY))
.map(Bytes::toHexString)
.collect(Collectors.toList());

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import java.util.Collections;
@ -52,7 +53,9 @@ public class EngineGetPayloadBodiesResultV1 {
public PayloadBody(final BlockBody blockBody) {
this.transactions =
blockBody.getTransactions().stream()
.map(TransactionEncoder::encodeOpaqueBytes)
.map(
transaction ->
TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.BLOCK_BODY))
.map(Bytes::toHexString)
.collect(Collectors.toList());
this.withdrawals =

@ -20,6 +20,7 @@ import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import java.util.List;
@ -99,7 +100,9 @@ public class TransactionPendingResult implements TransactionResult {
this.input = transaction.getPayload().toString();
this.nonce = Quantity.create(transaction.getNonce());
this.publicKey = transaction.getPublicKey().orElse(null);
this.raw = TransactionEncoder.encodeOpaqueBytes(transaction).toString();
this.raw =
TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.POOLED_TRANSACTION)
.toString();
this.to = transaction.getTo().map(Address::toHexString).orElse(null);
this.type =
transactionType.equals(TransactionType.FRONTIER)

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.util;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;
import org.hyperledger.besu.ethereum.rlp.RLPException;
@ -27,7 +28,7 @@ public class DomainObjectDecodeUtils {
throws InvalidJsonRpcRequestException {
try {
Bytes txnBytes = Bytes.fromHexString(rawTransaction);
return TransactionDecoder.decodeOpaqueBytes(txnBytes);
return TransactionDecoder.decodeOpaqueBytes(txnBytes, EncodingContext.POOLED_TRANSACTION);
} catch (final IllegalArgumentException e) {
throw new InvalidJsonRpcRequestException("Invalid raw transaction hex", e);
} catch (final RLPException r) {

@ -23,7 +23,13 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.BlobGas;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
import org.hyperledger.besu.ethereum.BlockProcessingResult;
@ -35,18 +41,26 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult;
import org.hyperledger.besu.ethereum.core.BlobTestFixture;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Deposit;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
@ -56,6 +70,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
protected static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair();
public EngineNewPayloadV3Test() {}
@ -194,4 +211,42 @@ public class EngineNewPayloadV3Test extends EngineNewPayloadV2Test {
assertThat(jsonRpcError.getData()).isEqualTo("Missing blob gas fields");
verify(engineCallListener, times(1)).executionEngineCalled();
}
@Test
public void shouldRejectTransactionsWithFullBlobs() {
Bytes transactionWithBlobsBytes =
TransactionEncoder.encodeOpaqueBytes(
createTransactionWithBlobs(), EncodingContext.POOLED_TRANSACTION);
List<String> transactions = List.of(transactionWithBlobsBytes.toString());
BlockHeader mockHeader =
setupValidPayload(
new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))),
Optional.empty(),
Optional.empty());
var resp = resp(mockEnginePayload(mockHeader, transactions));
EnginePayloadStatusResult res = fromSuccessResp(resp);
assertThat(res.getStatusAsString()).isEqualTo(INVALID.name());
assertThat(res.getError()).isEqualTo("Failed to decode transactions from block parameter");
verify(engineCallListener, times(1)).executionEngineCalled();
}
private Transaction createTransactionWithBlobs() {
BlobTestFixture blobTestFixture = new BlobTestFixture();
BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(1);
return new TransactionTestFixture()
.to(Optional.of(Address.fromHexString("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")))
.type(TransactionType.BLOB)
.chainId(Optional.of(BigInteger.ONE))
.maxFeePerGas(Optional.of(Wei.of(15)))
.maxFeePerBlobGas(Optional.of(Wei.of(128)))
.maxPriorityFeePerGas(Optional.of(Wei.of(1)))
.blobsWithCommitments(Optional.of(bwc))
.versionedHashes(Optional.of(bwc.getVersionedHashes()))
.createTransaction(senderKeys);
}
}

@ -23,6 +23,7 @@ import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
@ -59,7 +60,7 @@ public class DomainObjectDecodeUtilsTest {
@Test
public void testAccessListRLPSerDes() {
final BytesValueRLPOutput encoded = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(accessListTxn, encoded);
TransactionEncoder.encodeRLP(accessListTxn, encoded, EncodingContext.POOLED_TRANSACTION);
Transaction decoded =
DomainObjectDecodeUtils.decodeRawTransaction(encoded.encoded().toHexString());
Assertions.assertThat(decoded.getAccessList().isPresent()).isTrue();
@ -68,7 +69,8 @@ public class DomainObjectDecodeUtilsTest {
@Test
public void testAccessList2718OpaqueSerDes() {
final Bytes encoded = TransactionEncoder.encodeOpaqueBytes(accessListTxn);
final Bytes encoded =
TransactionEncoder.encodeOpaqueBytes(accessListTxn, EncodingContext.POOLED_TRANSACTION);
Transaction decoded = DomainObjectDecodeUtils.decodeRawTransaction(encoded.toString());
Assertions.assertThat(decoded.getAccessList().isPresent()).isTrue();
Assertions.assertThat(decoded.getAccessList().map(List::size).get()).isEqualTo(1);

@ -35,7 +35,9 @@ import org.hyperledger.besu.datatypes.Sha256Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
@ -126,7 +128,7 @@ public class Transaction
}
public static Transaction readFrom(final RLPInput rlpInput) {
return TransactionDecoder.decodeForWire(rlpInput);
return TransactionDecoder.decodeRLP(rlpInput, EncodingContext.BLOCK_BODY);
}
/**
@ -613,7 +615,7 @@ public class Transaction
* @param out the output to write the transaction to
*/
public void writeTo(final RLPOutput out) {
TransactionEncoder.encodeForWire(this, out);
TransactionEncoder.encodeRLP(this, out, EncodingContext.BLOCK_BODY);
}
@Override
@ -676,18 +678,17 @@ public class Transaction
}
private void memoizeHashAndSize() {
final Bytes bytes = TransactionEncoder.encodeOpaqueBytes(this);
final Bytes bytes = TransactionEncoder.encodeOpaqueBytes(this, EncodingContext.BLOCK_BODY);
hash = Hash.hash(bytes);
if (transactionType.supportsBlob()) {
if (getBlobsWithCommitments().isPresent()) {
size = bytes.size();
final Bytes pooledBytes =
TransactionEncoder.encodeOpaqueBytes(this, EncodingContext.POOLED_TRANSACTION);
size = pooledBytes.size();
return;
}
}
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(transactionType, bytes, rlpOutput);
size = rlpOutput.encodedSize();
size = bytes.size();
}
/**
@ -968,7 +969,7 @@ public class Transaction
rlpOutput.writeBytes(to.map(Bytes::copy).orElse(Bytes.EMPTY));
rlpOutput.writeUInt256Scalar(value);
rlpOutput.writeBytes(payload);
TransactionEncoder.writeAccessList(rlpOutput, accessList);
AccessListTransactionEncoder.writeAccessList(rlpOutput, accessList);
}
private static Bytes blobPreimage(
@ -1019,7 +1020,7 @@ public class Transaction
RLP.encode(
rlpOutput -> {
rlpOutput.startList();
TransactionEncoder.encodeAccessListInner(
AccessListTransactionEncoder.encodeAccessListInner(
chainId, nonce, gasPrice, gasLimit, to, value, payload, accessList, rlpOutput);
rlpOutput.endList();
});

@ -0,0 +1,74 @@
/*
* Copyright Hyperledger 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.core.encoding;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
class AccessListTransactionDecoder {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
public static Transaction decode(final RLPInput rlpInput) {
rlpInput.enterList();
final Transaction.Builder preSignatureTransactionBuilder =
Transaction.builder()
.type(TransactionType.ACCESS_LIST)
.chainId(BigInteger.valueOf(rlpInput.readLongScalar()))
.nonce(rlpInput.readLongScalar())
.gasPrice(Wei.of(rlpInput.readUInt256Scalar()))
.gasLimit(rlpInput.readLongScalar())
.to(
rlpInput.readBytes(
addressBytes -> addressBytes.size() == 0 ? null : Address.wrap(addressBytes)))
.value(Wei.of(rlpInput.readUInt256Scalar()))
.payload(rlpInput.readBytes())
.accessList(
rlpInput.readList(
accessListEntryRLPInput -> {
accessListEntryRLPInput.enterList();
final AccessListEntry accessListEntry =
new AccessListEntry(
Address.wrap(accessListEntryRLPInput.readBytes()),
accessListEntryRLPInput.readList(RLPInput::readBytes32));
accessListEntryRLPInput.leaveList();
return accessListEntry;
}));
final byte recId = (byte) rlpInput.readIntScalar();
final Transaction transaction =
preSignatureTransactionBuilder
.signature(
SIGNATURE_ALGORITHM
.get()
.createSignature(
rlpInput.readUInt256Scalar().toUnsignedBigInteger(),
rlpInput.readUInt256Scalar().toUnsignedBigInteger(),
recId))
.build();
rlpInput.leaveList();
return transaction;
}
}

@ -0,0 +1,109 @@
/*
* Copyright Hyperledger 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.core.encoding;
import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignature;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
public class AccessListTransactionEncoder {
public static void encode(final Transaction transaction, final RLPOutput rlpOutput) {
rlpOutput.startList();
encodeAccessListInner(
transaction.getChainId(),
transaction.getNonce(),
transaction.getGasPrice().orElseThrow(),
transaction.getGasLimit(),
transaction.getTo(),
transaction.getValue(),
transaction.getPayload(),
transaction
.getAccessList()
.orElseThrow(
() ->
new IllegalStateException(
"Developer error: access list should be guaranteed to be present")),
rlpOutput);
rlpOutput.writeIntScalar(transaction.getSignature().getRecId());
writeSignature(transaction, rlpOutput);
rlpOutput.endList();
}
public static void encodeAccessListInner(
final Optional<BigInteger> chainId,
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final List<AccessListEntry> accessList,
final RLPOutput rlpOutput) {
rlpOutput.writeBigIntegerScalar(chainId.orElseThrow());
rlpOutput.writeLongScalar(nonce);
rlpOutput.writeUInt256Scalar(gasPrice);
rlpOutput.writeLongScalar(gasLimit);
rlpOutput.writeBytes(to.map(Bytes::copy).orElse(Bytes.EMPTY));
rlpOutput.writeUInt256Scalar(value);
rlpOutput.writeBytes(payload);
/*
Access List encoding should look like this
where hex strings represent raw bytes
[
[
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
[
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x0000000000000000000000000000000000000000000000000000000000000007"
]
],
[
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
[]
]
] */
writeAccessList(rlpOutput, Optional.of(accessList));
}
public static void writeAccessList(
final RLPOutput out, final Optional<List<AccessListEntry>> accessListEntries) {
if (accessListEntries.isEmpty()) {
out.writeEmptyList();
} else {
out.writeList(
accessListEntries.get(),
(accessListEntry, accessListEntryRLPOutput) -> {
accessListEntryRLPOutput.startList();
out.writeBytes(accessListEntry.address());
out.writeList(
accessListEntry.storageKeys(),
(storageKeyBytes, storageKeyBytesRLPOutput) ->
storageKeyBytesRLPOutput.writeBytes(storageKeyBytes));
accessListEntryRLPOutput.endList();
});
}
}
}

@ -0,0 +1,50 @@
/*
* Copyright Hyperledger 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.core.encoding;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.util.List;
/**
* Class responsible for decoding blob transactions from the transaction pool. Blob transactions
* have two network representations. During transaction gossip responses (PooledTransactions), the
* EIP-2718 TransactionPayload of the blob transaction is wrapped to become: rlp([tx_payload_body,
* blobs, commitments, proofs]).
*/
public class BlobPooledTransactionDecoder {
/**
* Decodes a blob transaction from the provided RLP input.
*
* @param input the RLP input to decode
* @return the decoded transaction
*/
public static Transaction decode(final RLPInput input) {
input.enterList();
final Transaction.Builder builder = Transaction.builder();
BlobTransactionDecoder.readTransactionPayloadInner(builder, input);
List<Blob> blobs = input.readList(Blob::readFrom);
List<KZGCommitment> commitments = input.readList(KZGCommitment::readFrom);
List<KZGProof> proofs = input.readList(KZGProof::readFrom);
input.leaveList();
builder.kzgBlobs(commitments, blobs, proofs);
return builder.build();
}
}

@ -0,0 +1,47 @@
/*
* Copyright Hyperledger 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.core.encoding;
import static org.slf4j.LoggerFactory.getLogger;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.security.InvalidParameterException;
import org.slf4j.Logger;
public class BlobPooledTransactionEncoder {
private static final Logger LOG = getLogger(BlobPooledTransactionEncoder.class);
static final String NO_BLOBS_ERROR =
"Transaction with no blobsWithCommitments cannot be encoded for Pooled Transaction";
public static void encode(final Transaction transaction, final RLPOutput out) {
LOG.trace("Encoding transaction with blobs {}", transaction);
var blobsWithCommitments = transaction.getBlobsWithCommitments();
if (blobsWithCommitments.isEmpty() || blobsWithCommitments.get().getBlobs().isEmpty()) {
throw new InvalidParameterException(NO_BLOBS_ERROR);
}
out.startList();
BlobTransactionEncoder.encode(transaction, out);
out.writeList(blobsWithCommitments.get().getBlobs(), Blob::writeTo);
out.writeList(blobsWithCommitments.get().getKzgCommitments(), KZGCommitment::writeTo);
out.writeList(blobsWithCommitments.get().getKzgProofs(), KZGProof::writeTo);
out.endList();
}
}

@ -18,16 +18,12 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.util.List;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
@ -36,17 +32,15 @@ public class BlobTransactionDecoder {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
static Transaction decode(final RLPInput input) {
/**
* Decodes a blob transaction from the provided RLP input.
*
* @param input the RLP input to decode
* @return the decoded transaction
*/
public static Transaction decode(final RLPInput input) {
Transaction transaction;
input.enterList();
// BlobTransactionNetworkWrapper
if (input.nextIsList()) {
transaction = readNetworkWrapperInner(input);
} else {
transaction = readTransactionPayload(input);
}
input.leaveList();
transaction = readTransactionPayload(input);
return transaction;
}
@ -56,8 +50,17 @@ public class BlobTransactionDecoder {
return builder.build();
}
private static void readTransactionPayloadInner(
final Transaction.Builder builder, final RLPInput input) {
/**
* Reads the payload of a blob transaction from the provided RLP input.
*
* <p>[chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data,
* access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s]
*
* @param builder the transaction builder
* @param input the RLP input to read from
*/
static void readTransactionPayloadInner(final Transaction.Builder builder, final RLPInput input) {
input.enterList();
builder
.type(TransactionType.BLOB)
.chainId(input.readBigIntegerScalar())
@ -91,18 +94,6 @@ public class BlobTransactionDecoder {
input.readUInt256Scalar().toUnsignedBigInteger(),
input.readUInt256Scalar().toUnsignedBigInteger(),
recId));
}
private static Transaction readNetworkWrapperInner(final RLPInput input) {
final Transaction.Builder builder = Transaction.builder();
input.enterList();
readTransactionPayloadInner(builder, input);
input.leaveList();
List<Blob> blobs = input.readList(Blob::readFrom);
List<KZGCommitment> commitments = input.readList(KZGCommitment::readFrom);
List<KZGProof> proofs = input.readList(KZGProof::readFrom);
builder.kzgBlobs(commitments, blobs, proofs);
return builder.build();
}
}

@ -14,25 +14,17 @@
*/
package org.hyperledger.besu.ethereum.core.encoding;
import static org.slf4j.LoggerFactory.getLogger;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.util.List;
import org.apache.tuweni.bytes.Bytes;
import org.slf4j.Logger;
public class BlobTransactionEncoder {
private static final Logger LOG = getLogger(BlobTransactionEncoder.class);
public static void encodeEIP4844(final Transaction transaction, final RLPOutput out) {
public static void encode(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeBigIntegerScalar(transaction.getChainId().orElseThrow());
out.writeLongScalar(transaction.getNonce());
@ -42,7 +34,7 @@ public class BlobTransactionEncoder {
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
TransactionEncoder.writeAccessList(out, transaction.getAccessList());
AccessListTransactionEncoder.writeAccessList(out, transaction.getAccessList());
out.writeUInt256Scalar(transaction.getMaxFeePerBlobGas().orElseThrow());
out.startList();
transaction
@ -57,29 +49,6 @@ public class BlobTransactionEncoder {
out.endList();
}
private static void encodeEIP4844Network(final Transaction transaction, final RLPOutput out) {
LOG.trace("Encoding transaction with blobs {}", transaction);
out.startList();
var blobsWithCommitments = transaction.getBlobsWithCommitments().orElseThrow();
encodeEIP4844(transaction, out);
out.writeList(blobsWithCommitments.getBlobs(), Blob::writeTo);
out.writeList(blobsWithCommitments.getKzgCommitments(), KZGCommitment::writeTo);
out.writeList(blobsWithCommitments.getKzgProofs(), KZGProof::writeTo);
out.endList();
}
public static void encodeForWireNetwork(
final Transaction transaction, final RLPOutput rlpOutput) {
rlpOutput.writeBytes(encodeOpaqueBytesNetwork(transaction));
}
private static Bytes encodeOpaqueBytesNetwork(final Transaction transaction) {
return Bytes.concatenate(
Bytes.of(transaction.getType().getSerializedType()),
RLP.encode(rlpOutput -> encodeEIP4844Network(transaction, rlpOutput)));
}
public static void writeBlobVersionedHashes(
final RLPOutput rlpOutput, final List<VersionedHash> versionedHashes) {
rlpOutput.writeList(versionedHashes, (h, out) -> out.writeBytes(h.toBytes()));

@ -0,0 +1,74 @@
/*
* Copyright Hyperledger 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.core.encoding;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
public class EIP1559TransactionDecoder {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
public static Transaction decode(final RLPInput input) {
input.enterList();
final BigInteger chainId = input.readBigIntegerScalar();
final Transaction.Builder builder =
Transaction.builder()
.type(TransactionType.EIP1559)
.chainId(chainId)
.nonce(input.readLongScalar())
.maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar()))
.maxFeePerGas(Wei.of(input.readUInt256Scalar()))
.gasLimit(input.readLongScalar())
.to(input.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
.value(Wei.of(input.readUInt256Scalar()))
.payload(input.readBytes())
.accessList(
input.readList(
accessListEntryRLPInput -> {
accessListEntryRLPInput.enterList();
final AccessListEntry accessListEntry =
new AccessListEntry(
Address.wrap(accessListEntryRLPInput.readBytes()),
accessListEntryRLPInput.readList(RLPInput::readBytes32));
accessListEntryRLPInput.leaveList();
return accessListEntry;
}));
final byte recId = (byte) input.readIntScalar();
final Transaction transaction =
builder
.signature(
SIGNATURE_ALGORITHM
.get()
.createSignature(
input.readUInt256Scalar().toUnsignedBigInteger(),
input.readUInt256Scalar().toUnsignedBigInteger(),
recId))
.build();
input.leaveList();
return transaction;
}
}

@ -0,0 +1,41 @@
/*
* Copyright Hyperledger 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.core.encoding;
import static org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder.writeAccessList;
import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignatureAndRecoveryId;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.apache.tuweni.bytes.Bytes;
public class EIP1559TransactionEncoder {
public static void encode(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeBigIntegerScalar(transaction.getChainId().orElseThrow());
out.writeLongScalar(transaction.getNonce());
out.writeUInt256Scalar(transaction.getMaxPriorityFeePerGas().orElseThrow());
out.writeUInt256Scalar(transaction.getMaxFeePerGas().orElseThrow());
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeAccessList(out, transaction.getAccessList());
writeSignatureAndRecoveryId(transaction, out);
out.endList();
}
}

@ -0,0 +1,41 @@
/*
* Copyright Hyperledger 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.core.encoding;
/**
* Enum representing the context in which a transaction is being encoded. This context is used to
* determine the appropriate encoding strategy for a transaction.
*
* <p>The context can be one of the following:
*
* <ul>
* <li>{@link #BLOCK_BODY}: The transaction is part of a block body. This context is used when
* encoding transactions for inclusion in a block.
* <li>{@link #POOLED_TRANSACTION}: The transaction is part of a transaction pool. This context is
* used when encoding transactions that are currently in the transaction pool, waiting to be
* included in a block. It is also used when encoding transactions for RPC calls related to
* the transaction pool.
* </ul>
*/
public enum EncodingContext {
/** Represents the context where the transaction is part of a block body. */
BLOCK_BODY,
/**
* Represents the context where the transaction is part of a transaction pool. This context is
* also used when encoding transactions for RPC calls related to the transaction pool.
*/
POOLED_TRANSACTION,
}

@ -0,0 +1,76 @@
/*
* Copyright Hyperledger 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.core.encoding;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_PROTECTED_V_BASE;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_PROTECTED_V_MIN;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_UNPROTECTED_V_BASE;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_UNPROTECTED_V_BASE_PLUS_1;
import static org.hyperledger.besu.ethereum.core.Transaction.TWO;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
public class FrontierTransactionDecoder {
// Supplier for the signature algorithm
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
public static Transaction decode(final RLPInput input) {
input.enterList();
final Transaction.Builder builder =
Transaction.builder()
.type(TransactionType.FRONTIER)
.nonce(input.readLongScalar())
.gasPrice(Wei.of(input.readUInt256Scalar()))
.gasLimit(input.readLongScalar())
.to(input.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
.value(Wei.of(input.readUInt256Scalar()))
.payload(input.readBytes());
final BigInteger v = input.readBigIntegerScalar();
final byte recId;
Optional<BigInteger> chainId = Optional.empty();
if (v.equals(REPLAY_UNPROTECTED_V_BASE) || v.equals(REPLAY_UNPROTECTED_V_BASE_PLUS_1)) {
recId = v.subtract(REPLAY_UNPROTECTED_V_BASE).byteValueExact();
} else if (v.compareTo(REPLAY_PROTECTED_V_MIN) > 0) {
chainId = Optional.of(v.subtract(REPLAY_PROTECTED_V_BASE).divide(TWO));
recId = v.subtract(TWO.multiply(chainId.get()).add(REPLAY_PROTECTED_V_BASE)).byteValueExact();
} else {
throw new RuntimeException(
String.format("An unsupported encoded `v` value of %s was found", v));
}
final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger();
final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger();
final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, recId);
input.leaveList();
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
}
}

@ -0,0 +1,36 @@
/*
* Copyright Hyperledger 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.core.encoding;
import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignatureAndV;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.apache.tuweni.bytes.Bytes;
public class FrontierTransactionEncoder {
public static void encode(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeLongScalar(transaction.getNonce());
out.writeUInt256Scalar(transaction.getGasPrice().orElseThrow());
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeSignatureAndV(transaction, out);
out.endList();
}
}

@ -15,28 +15,14 @@
package org.hyperledger.besu.ethereum.core.encoding;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_PROTECTED_V_BASE;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_PROTECTED_V_MIN;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_UNPROTECTED_V_BASE;
import static org.hyperledger.besu.ethereum.core.Transaction.REPLAY_UNPROTECTED_V_BASE_PLUS_1;
import static org.hyperledger.besu.ethereum.core.Transaction.TWO;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import org.apache.tuweni.bytes.Bytes;
@ -50,154 +36,143 @@ public class TransactionDecoder {
private static final ImmutableMap<TransactionType, Decoder> TYPED_TRANSACTION_DECODERS =
ImmutableMap.of(
TransactionType.ACCESS_LIST,
TransactionDecoder::decodeAccessList,
AccessListTransactionDecoder::decode,
TransactionType.EIP1559,
TransactionDecoder::decodeEIP1559,
EIP1559TransactionDecoder::decode,
TransactionType.BLOB,
BlobTransactionDecoder::decode);
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
public static Transaction decodeForWire(final RLPInput rlpInput) {
if (rlpInput.nextIsList()) {
return decodeFrontier(rlpInput);
private static final ImmutableMap<TransactionType, Decoder> POOLED_TRANSACTION_DECODERS =
ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionDecoder::decode);
/**
* Decodes an RLP input into a transaction. If the input represents a typed transaction, it uses
* the appropriate decoder for that type. Otherwise, it uses the frontier decoder.
*
* @param rlpInput the RLP input
* @return the decoded transaction
*/
public static Transaction decodeRLP(
final RLPInput rlpInput, final EncodingContext encodingContext) {
if (isTypedTransaction(rlpInput)) {
return decodeTypedTransaction(rlpInput, encodingContext);
} else {
final Bytes typedTransactionBytes = rlpInput.readBytes();
final TransactionType transactionType =
TransactionType.of(typedTransactionBytes.get(0) & 0xff);
return getDecoder(transactionType).decode(RLP.input(typedTransactionBytes.slice(1)));
return FrontierTransactionDecoder.decode(rlpInput);
}
}
public static Transaction decodeOpaqueBytes(final Bytes input) {
final TransactionType transactionType;
try {
transactionType = TransactionType.of(input.get(0));
} catch (final IllegalArgumentException __) {
return decodeForWire(RLP.input(input));
}
return getDecoder(transactionType).decode(RLP.input(input.slice(1)));
/**
* Decodes a typed transaction from an RLP input. It first reads the transaction type from the
* input, then uses the appropriate decoder for that type.
*
* @param rlpInput the RLP input
* @return the decoded transaction
*/
private static Transaction decodeTypedTransaction(
final RLPInput rlpInput, final EncodingContext context) {
// Read the typed transaction bytes from the RLP input
final Bytes typedTransactionBytes = rlpInput.readBytes();
// Determine the transaction type from the typed transaction bytes
TransactionType transactionType =
getTransactionType(typedTransactionBytes)
.orElseThrow((() -> new IllegalArgumentException("Unsupported transaction type")));
return decodeTypedTransaction(typedTransactionBytes, transactionType, context);
}
private static Decoder getDecoder(final TransactionType transactionType) {
return checkNotNull(
TYPED_TRANSACTION_DECODERS.get(transactionType),
"Developer Error. A supported transaction type %s has no associated decoding logic",
transactionType);
/**
* Decodes a typed transaction. The method first slices the transaction bytes to exclude the
* transaction type, then uses the appropriate decoder for the transaction type to decode the
* remaining bytes.
*
* @param transactionBytes the transaction bytes
* @param transactionType the type of the transaction
* @param context the encoding context
* @return the decoded transaction
*/
private static Transaction decodeTypedTransaction(
final Bytes transactionBytes,
final TransactionType transactionType,
final EncodingContext context) {
// Slice the transaction bytes to exclude the transaction type and prepare for decoding
final RLPInput transactionInput = RLP.input(transactionBytes.slice(1));
// Use the appropriate decoder for the transaction type to decode the remaining bytes
return getDecoder(transactionType, context).decode(transactionInput);
}
static Transaction decodeFrontier(final RLPInput input) {
input.enterList();
final Transaction.Builder builder =
Transaction.builder()
.type(TransactionType.FRONTIER)
.nonce(input.readLongScalar())
.gasPrice(Wei.of(input.readUInt256Scalar()))
.gasLimit(input.readLongScalar())
.to(input.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
.value(Wei.of(input.readUInt256Scalar()))
.payload(input.readBytes());
final BigInteger v = input.readBigIntegerScalar();
final byte recId;
Optional<BigInteger> chainId = Optional.empty();
if (v.equals(REPLAY_UNPROTECTED_V_BASE) || v.equals(REPLAY_UNPROTECTED_V_BASE_PLUS_1)) {
recId = v.subtract(REPLAY_UNPROTECTED_V_BASE).byteValueExact();
} else if (v.compareTo(REPLAY_PROTECTED_V_MIN) > 0) {
chainId = Optional.of(v.subtract(REPLAY_PROTECTED_V_BASE).divide(TWO));
recId = v.subtract(TWO.multiply(chainId.get()).add(REPLAY_PROTECTED_V_BASE)).byteValueExact();
/**
* Decodes a transaction from opaque bytes. The method first determines the transaction type from
* the bytes. If the type is present, it delegates the decoding process to the appropriate decoder
* for that type. If the type is not present, it decodes the bytes as an RLP input.
*
* @param opaqueBytes the opaque bytes
* @param context the encoding context
* @return the decoded transaction
*/
public static Transaction decodeOpaqueBytes(
final Bytes opaqueBytes, final EncodingContext context) {
var transactionType = getTransactionType(opaqueBytes);
if (transactionType.isPresent()) {
return decodeTypedTransaction(opaqueBytes, transactionType.get(), context);
} else {
throw new RuntimeException(
String.format("An unsupported encoded `v` value of %s was found", v));
// If the transaction type is not present, decode the opaque bytes as RLP
return decodeRLP(RLP.input(opaqueBytes), context);
}
final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger();
final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger();
final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, recId);
input.leaveList();
}
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
/**
* Retrieves the transaction type from the provided bytes. The method attempts to extract the
* first byte from the input bytes and interpret it as a transaction type. If the byte does not
* correspond to a valid transaction type, the method returns an empty Optional.
*
* @param opaqueBytes the bytes from which to extract the transaction type
* @return an Optional containing the TransactionType if the first byte of the input corresponds
* to a valid transaction type, or an empty Optional if it does not
*/
private static Optional<TransactionType> getTransactionType(final Bytes opaqueBytes) {
try {
byte transactionTypeByte = opaqueBytes.get(0);
return Optional.of(TransactionType.of(transactionTypeByte));
} catch (IllegalArgumentException ex) {
return Optional.empty();
}
}
private static Transaction decodeAccessList(final RLPInput rlpInput) {
rlpInput.enterList();
final Transaction.Builder preSignatureTransactionBuilder =
Transaction.builder()
.type(TransactionType.ACCESS_LIST)
.chainId(BigInteger.valueOf(rlpInput.readLongScalar()))
.nonce(rlpInput.readLongScalar())
.gasPrice(Wei.of(rlpInput.readUInt256Scalar()))
.gasLimit(rlpInput.readLongScalar())
.to(
rlpInput.readBytes(
addressBytes -> addressBytes.size() == 0 ? null : Address.wrap(addressBytes)))
.value(Wei.of(rlpInput.readUInt256Scalar()))
.payload(rlpInput.readBytes())
.accessList(
rlpInput.readList(
accessListEntryRLPInput -> {
accessListEntryRLPInput.enterList();
final AccessListEntry accessListEntry =
new AccessListEntry(
Address.wrap(accessListEntryRLPInput.readBytes()),
accessListEntryRLPInput.readList(RLPInput::readBytes32));
accessListEntryRLPInput.leaveList();
return accessListEntry;
}));
final byte recId = (byte) rlpInput.readIntScalar();
final Transaction transaction =
preSignatureTransactionBuilder
.signature(
SIGNATURE_ALGORITHM
.get()
.createSignature(
rlpInput.readUInt256Scalar().toUnsignedBigInteger(),
rlpInput.readUInt256Scalar().toUnsignedBigInteger(),
recId))
.build();
rlpInput.leaveList();
return transaction;
/**
* Checks if the given RLP input is a typed transaction.
*
* <p>See EIP-2718
*
* <p>If it starts with a value in the range [0, 0x7f] then it is a new transaction type
*
* <p>if it starts with a value in the range [0xc0, 0xfe] then it is a legacy transaction type
*
* @param rlpInput the RLP input
* @return true if the RLP input is a typed transaction, false otherwise
*/
private static boolean isTypedTransaction(final RLPInput rlpInput) {
return !rlpInput.nextIsList();
}
static Transaction decodeEIP1559(final RLPInput input) {
input.enterList();
final BigInteger chainId = input.readBigIntegerScalar();
final Transaction.Builder builder =
Transaction.builder()
.type(TransactionType.EIP1559)
.chainId(chainId)
.nonce(input.readLongScalar())
.maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar()))
.maxFeePerGas(Wei.of(input.readUInt256Scalar()))
.gasLimit(input.readLongScalar())
.to(input.readBytes(v -> v.size() == 0 ? null : Address.wrap(v)))
.value(Wei.of(input.readUInt256Scalar()))
.payload(input.readBytes())
.accessList(
input.readList(
accessListEntryRLPInput -> {
accessListEntryRLPInput.enterList();
final AccessListEntry accessListEntry =
new AccessListEntry(
Address.wrap(accessListEntryRLPInput.readBytes()),
accessListEntryRLPInput.readList(RLPInput::readBytes32));
accessListEntryRLPInput.leaveList();
return accessListEntry;
}));
final byte recId = (byte) input.readIntScalar();
final Transaction transaction =
builder
.signature(
SIGNATURE_ALGORITHM
.get()
.createSignature(
input.readUInt256Scalar().toUnsignedBigInteger(),
input.readUInt256Scalar().toUnsignedBigInteger(),
recId))
.build();
input.leaveList();
return transaction;
/**
* Gets the decoder for a given transaction type and encoding context. If the context is
* POOLED_TRANSACTION, it uses the network decoder for the type. Otherwise, it uses the typed
* decoder.
*
* @param transactionType the transaction type
* @param encodingContext the encoding context
* @return the decoder
*/
private static Decoder getDecoder(
final TransactionType transactionType, final EncodingContext encodingContext) {
if (encodingContext.equals(EncodingContext.POOLED_TRANSACTION)) {
if (POOLED_TRANSACTION_DECODERS.containsKey(transactionType)) {
return POOLED_TRANSACTION_DECODERS.get(transactionType);
}
}
return checkNotNull(
TYPED_TRANSACTION_DECODERS.get(transactionType),
"Developer Error. A supported transaction type %s has no associated decoding logic",
transactionType);
}
}

@ -16,20 +16,13 @@ package org.hyperledger.besu.ethereum.core.encoding;
import static com.google.common.base.Preconditions.checkNotNull;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
import org.apache.tuweni.bytes.Bytes;
public class TransactionEncoder {
@ -39,23 +32,42 @@ public class TransactionEncoder {
void encode(Transaction transaction, RLPOutput output);
}
private static final Map<TransactionType, Encoder> TYPED_TRANSACTION_ENCODERS =
Map.of(
private static final ImmutableMap<TransactionType, Encoder> TYPED_TRANSACTION_ENCODERS =
ImmutableMap.of(
TransactionType.ACCESS_LIST,
TransactionEncoder::encodeAccessList,
AccessListTransactionEncoder::encode,
TransactionType.EIP1559,
TransactionEncoder::encodeEIP1559,
EIP1559TransactionEncoder::encode,
TransactionType.BLOB,
BlobTransactionEncoder::encodeEIP4844);
public static void encodeForWire(final Transaction transaction, final RLPOutput rlpOutput) {
final TransactionType transactionType =
checkNotNull(
transaction.getType(), "Transaction type for %s was not specified.", transaction);
encodeForWire(transactionType, encodeOpaqueBytes(transaction), rlpOutput);
BlobTransactionEncoder::encode);
private static final ImmutableMap<TransactionType, Encoder> POOLED_TRANSACTION_ENCODERS =
ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionEncoder::encode);
/**
* Encodes a transaction into RLP format.
*
* @param transaction the transaction to encode
* @param rlpOutput the RLP output stream
* @param encodingContext the encoding context
*/
public static void encodeRLP(
final Transaction transaction,
final RLPOutput rlpOutput,
final EncodingContext encodingContext) {
final TransactionType transactionType = getTransactionType(transaction);
Bytes opaqueBytes = encodeOpaqueBytes(transaction, encodingContext);
encodeRLP(transactionType, opaqueBytes, rlpOutput);
}
public static void encodeForWire(
/**
* Encodes a transaction into RLP format.
*
* @param transactionType the type of the transaction
* @param opaqueBytes the bytes of the transaction
* @param rlpOutput the RLP output stream
*/
public static void encodeRLP(
final TransactionType transactionType, final Bytes opaqueBytes, final RLPOutput rlpOutput) {
checkNotNull(transactionType, "Transaction type was not specified.");
if (TransactionType.FRONTIER.equals(transactionType)) {
@ -65,142 +77,58 @@ public class TransactionEncoder {
}
}
public static Bytes encodeOpaqueBytes(final Transaction transaction) {
final TransactionType transactionType =
checkNotNull(
transaction.getType(), "Transaction type for %s was not specified.", transaction);
/**
* Encodes a transaction into opaque bytes.
*
* @param transaction the transaction to encode
* @param encodingContext the encoding context
* @return the encoded transaction as bytes
*/
public static Bytes encodeOpaqueBytes(
final Transaction transaction, final EncodingContext encodingContext) {
final TransactionType transactionType = getTransactionType(transaction);
if (TransactionType.FRONTIER.equals(transactionType)) {
return RLP.encode(rlpOutput -> encodeFrontier(transaction, rlpOutput));
return RLP.encode(rlpOutput -> FrontierTransactionEncoder.encode(transaction, rlpOutput));
} else {
final Encoder encoder =
checkNotNull(
TYPED_TRANSACTION_ENCODERS.get(transactionType),
"Developer Error. A supported transaction type %s has no associated encoding logic",
transactionType);
final Encoder encoder = getEncoder(transactionType, encodingContext);
final BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeByte(transactionType.getSerializedType());
out.writeByte(transaction.getType().getSerializedType());
encoder.encode(transaction, out);
return out.encoded();
}
}
static void encodeFrontier(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeLongScalar(transaction.getNonce());
out.writeUInt256Scalar(transaction.getGasPrice().orElseThrow());
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeSignatureAndV(transaction, out);
out.endList();
}
static void encodeAccessList(final Transaction transaction, final RLPOutput rlpOutput) {
rlpOutput.startList();
encodeAccessListInner(
transaction.getChainId(),
transaction.getNonce(),
transaction.getGasPrice().orElseThrow(),
transaction.getGasLimit(),
transaction.getTo(),
transaction.getValue(),
transaction.getPayload(),
transaction
.getAccessList()
.orElseThrow(
() ->
new IllegalStateException(
"Developer error: access list should be guaranteed to be present")),
rlpOutput);
rlpOutput.writeIntScalar(transaction.getSignature().getRecId());
writeSignature(transaction, rlpOutput);
rlpOutput.endList();
}
public static void encodeAccessListInner(
final Optional<BigInteger> chainId,
final long nonce,
final Wei gasPrice,
final long gasLimit,
final Optional<Address> to,
final Wei value,
final Bytes payload,
final List<AccessListEntry> accessList,
final RLPOutput rlpOutput) {
rlpOutput.writeBigIntegerScalar(chainId.orElseThrow());
rlpOutput.writeLongScalar(nonce);
rlpOutput.writeUInt256Scalar(gasPrice);
rlpOutput.writeLongScalar(gasLimit);
rlpOutput.writeBytes(to.map(Bytes::copy).orElse(Bytes.EMPTY));
rlpOutput.writeUInt256Scalar(value);
rlpOutput.writeBytes(payload);
/*
Access List encoding should look like this
where hex strings represent raw bytes
[
[
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
[
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x0000000000000000000000000000000000000000000000000000000000000007"
]
],
[
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
[]
]
] */
writeAccessList(rlpOutput, Optional.of(accessList));
}
static void encodeEIP1559(final Transaction transaction, final RLPOutput out) {
out.startList();
out.writeBigIntegerScalar(transaction.getChainId().orElseThrow());
out.writeLongScalar(transaction.getNonce());
out.writeUInt256Scalar(transaction.getMaxPriorityFeePerGas().orElseThrow());
out.writeUInt256Scalar(transaction.getMaxFeePerGas().orElseThrow());
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY));
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeAccessList(out, transaction.getAccessList());
writeSignatureAndRecoveryId(transaction, out);
out.endList();
}
public static void writeAccessList(
final RLPOutput out, final Optional<List<AccessListEntry>> accessListEntries) {
if (accessListEntries.isEmpty()) {
out.writeEmptyList();
} else {
out.writeList(
accessListEntries.get(),
(accessListEntry, accessListEntryRLPOutput) -> {
accessListEntryRLPOutput.startList();
out.writeBytes(accessListEntry.address());
out.writeList(
accessListEntry.storageKeys(),
(storageKeyBytes, storageKeyBytesRLPOutput) ->
storageKeyBytesRLPOutput.writeBytes(storageKeyBytes));
accessListEntryRLPOutput.endList();
});
}
}
private static void writeSignatureAndV(final Transaction transaction, final RLPOutput out) {
static void writeSignatureAndV(final Transaction transaction, final RLPOutput out) {
out.writeBigIntegerScalar(transaction.getV());
writeSignature(transaction, out);
}
public static void writeSignatureAndRecoveryId(
final Transaction transaction, final RLPOutput out) {
static void writeSignatureAndRecoveryId(final Transaction transaction, final RLPOutput out) {
out.writeIntScalar(transaction.getSignature().getRecId());
writeSignature(transaction, out);
}
private static void writeSignature(final Transaction transaction, final RLPOutput out) {
static void writeSignature(final Transaction transaction, final RLPOutput out) {
out.writeBigIntegerScalar(transaction.getSignature().getR());
out.writeBigIntegerScalar(transaction.getSignature().getS());
}
private static TransactionType getTransactionType(final Transaction transaction) {
return checkNotNull(
transaction.getType(), "Transaction type for %s was not specified.", transaction);
}
private static Encoder getEncoder(
final TransactionType transactionType, final EncodingContext encodingContext) {
if (encodingContext.equals(EncodingContext.POOLED_TRANSACTION)) {
if (POOLED_TRANSACTION_ENCODERS.containsKey(transactionType)) {
return POOLED_TRANSACTION_ENCODERS.get(transactionType);
}
}
return checkNotNull(
TYPED_TRANSACTION_ENCODERS.get(transactionType),
"Developer Error. A supported transaction type %s has no associated encoding logic",
transactionType);
}
}

@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.Withdrawal;
import org.hyperledger.besu.ethereum.core.encoding.DepositEncoder;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.WithdrawalEncoder;
import org.hyperledger.besu.ethereum.rlp.RLP;
@ -62,7 +63,11 @@ public final class BodyValidation {
IntStream.range(0, transactions.size())
.forEach(
i -> trie.put(indexKey(i), TransactionEncoder.encodeOpaqueBytes(transactions.get(i))));
i ->
trie.put(
indexKey(i),
TransactionEncoder.encodeOpaqueBytes(
transactions.get(i), EncodingContext.BLOCK_BODY)));
return Hash.wrap(trie.getRootHash());
}

@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import java.io.BufferedReader;
import java.io.IOException;
@ -33,7 +32,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class BlobTransactionEncodingTest {
private static Stream<Arguments> provideTypedTransactionBytes() throws IOException {
private static Stream<Arguments> provideOpaqueBytesNoBlobsWithCommitments() {
return Stream.of(
createArgument(
"0x03f89d850120b996ed3685012a1a646085012a1a64608303345094ffb38a7a99e3e2335be83fc74b7faa19d55312418308a80280c085012a1a6460e1a00153a6a1e053cf4c5a09e84088ed8ad7cb53d76c8168f1b82f7cfebfcd06da1a01a007785223eec68459d72265f10bdb30ec3415252a63100605a03142fa211ebbe9a07dbbf9e081fa7b9a01202e4d9ee0e0e513f80efbbab6c784635429905389ce86"),
@ -45,36 +44,44 @@ public class BlobTransactionEncodingTest {
"0x03f897850120b996ed80840bebc200843b9aca078303345094c8d369b164361a8961286cfbab3bc10f962185a88080c08411e1a300e1a0011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b080a032f122f06e5802224db4c8a58fd22c75173a713f63f89936f811c144b9e40129a043a2a872cbfa5727007adf6a48febe5f190d2e4cd5ed6122823fb6ff47ecda32"));
}
private static Stream<Arguments> provideTypedTransactionBytesForNetwork() throws IOException {
return Stream.of(createArgumentFromFile("blob1.txt"));
private static Stream<Arguments> provideOpaqueBytesForNetwork() throws IOException {
return Stream.of(createArgumentFromFile("blob2.txt"));
}
@ParameterizedTest(name = "{index} {0}")
@MethodSource("provideTypedTransactionBytesForNetwork")
@MethodSource("provideOpaqueBytesForNetwork")
public void blobTransactionEncodingDecodingForNetWorkTest(
final TypedTransactionBytesArgument argument) {
Bytes bytes = argument.bytes;
// Decode the transaction from the wire using the TransactionDecoder.
final Transaction transaction = TransactionDecoder.decodeForWire(RLP.input(bytes));
final Transaction transaction =
TransactionDecoder.decodeOpaqueBytes(bytes, EncodingContext.POOLED_TRANSACTION);
final BytesValueRLPOutput output = new BytesValueRLPOutput();
TransactionEncoder.encodeRLP(transaction.getType(), bytes, output);
final BytesValueRLPOutput bytesValueRLPOutput = new BytesValueRLPOutput();
BlobTransactionEncoder.encodeForWireNetwork(transaction, bytesValueRLPOutput);
Bytes encodedRLP = bytesValueRLPOutput.encoded();
assertThat(encodedRLP.size()).isEqualTo(bytes.size());
assertThat(encodedRLP).isEqualTo(bytes);
TransactionEncoder.encodeRLP(
transaction, bytesValueRLPOutput, EncodingContext.POOLED_TRANSACTION);
assertThat(transaction.getSize()).isEqualTo(bytes.size());
}
@ParameterizedTest(name = "{index} {0}")
@MethodSource("provideTypedTransactionBytes")
@MethodSource("provideOpaqueBytesNoBlobsWithCommitments")
public void blobTransactionEncodingDecodingTest(final TypedTransactionBytesArgument argument) {
Bytes bytes = argument.bytes;
// Decode the transaction from the wire using the TransactionDecoder.
final Transaction transaction = TransactionDecoder.decodeForWire(RLP.input(bytes));
final BytesValueRLPOutput output = new BytesValueRLPOutput();
final Transaction transaction =
TransactionDecoder.decodeOpaqueBytes(bytes, EncodingContext.BLOCK_BODY);
// Encode the transaction for wire using the TransactionEncoder.
TransactionEncoder.encodeForWire(transaction, output);
Bytes encoded = TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.BLOCK_BODY);
// Assert that the encoded transaction matches the original bytes.
assertThat(output.encoded().toHexString()).isEqualTo(bytes.toHexString());
assertThat(encoded.toHexString()).isEqualTo(bytes.toHexString());
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
TransactionEncoder.encodeRLP(transaction.getType(), bytes, rlpOutput);
assertThat(transaction.getSize()).isEqualTo(bytes.size());
}
private static Arguments createArgumentFromFile(final String path) throws IOException {
@ -96,9 +103,7 @@ public class BlobTransactionEncodingTest {
}
private static Arguments createArgument(final String hex) {
BytesValueRLPOutput out = new BytesValueRLPOutput();
out.writeBytes(Bytes.fromHexString(hex));
return Arguments.of(new TypedTransactionBytesArgument(out.encoded()));
return Arguments.of(new TypedTransactionBytesArgument(Bytes.fromHexString(hex)));
}
@SuppressWarnings("UnusedVariable")

@ -22,6 +22,7 @@ import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.Arrays;
@ -44,8 +45,7 @@ class TransactionRLPDecoderTest {
@Test
void decodeFrontierNominalCase() {
final Transaction transaction =
TransactionDecoder.decodeForWire(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP)));
final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP)));
assertThat(transaction).isNotNull();
assertThat(transaction.getGasPrice().get()).isEqualByComparingTo(Wei.of(50L));
assertThat(transaction.getMaxPriorityFeePerGas()).isEmpty();
@ -54,8 +54,7 @@ class TransactionRLPDecoderTest {
@Test
void decodeEIP1559NominalCase() {
final Transaction transaction =
TransactionDecoder.decodeForWire(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP)));
final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP)));
assertThat(transaction).isNotNull();
assertThat(transaction.getMaxPriorityFeePerGas()).hasValue(Wei.of(2L));
assertThat(transaction.getMaxFeePerGas()).hasValue(Wei.of(new BigInteger("5000000000", 10)));
@ -66,15 +65,16 @@ class TransactionRLPDecoderTest {
final String txWithBigFees =
"0x02f84e0101a1648a5f8b2dcad5ea5ba6b720ff069c1d87c21a4a6a5b3766b39e2c2792367bb066a1ffa5ffaf5b0560d3a9fb186c2ede2ae6751bc0b4fef9107cf36389630b6196a38805800180c0010203";
assertThatThrownBy(
() -> TransactionDecoder.decodeOpaqueBytes(Bytes.fromHexString(txWithBigFees)))
() ->
TransactionDecoder.decodeOpaqueBytes(
Bytes.fromHexString(txWithBigFees), EncodingContext.BLOCK_BODY))
.isInstanceOf(RLPException.class);
}
@Test
void shouldDecodeWithHighNonce() {
final Transaction transaction =
TransactionDecoder.decodeForWire(
RLP.input(Bytes.fromHexString(NONCE_64_BIT_MAX_MINUS_2_TX_RLP)));
decodeRLP(RLP.input(Bytes.fromHexString(NONCE_64_BIT_MAX_MINUS_2_TX_RLP)));
assertThat(transaction).isNotNull();
assertThat(transaction.getNonce()).isEqualTo(MAX_NONCE - 1);
}
@ -94,16 +94,21 @@ class TransactionRLPDecoderTest {
// Create bytes from String
final Bytes bytes = Bytes.fromHexString(rlp_tx);
// Decode bytes into a transaction
final Transaction transaction = TransactionDecoder.decodeForWire(RLP.input(bytes));
final Transaction transaction = decodeRLP(RLP.input(bytes));
Bytes transactionBytes =
TransactionEncoder.encodeOpaqueBytes(transaction, EncodingContext.POOLED_TRANSACTION);
// Bytes size should be equal to transaction size
assertThat(transaction.getSize()).isEqualTo(bytes.size());
assertThat(transaction.getSize()).isEqualTo(transactionBytes.size());
}
@ParameterizedTest
@ValueSource(strings = {FRONTIER_TX_RLP, EIP1559_TX_RLP, NONCE_64_BIT_MAX_MINUS_2_TX_RLP})
void shouldReturnCorrectEncodedBytes(final String txRlp) {
final Transaction transaction =
TransactionDecoder.decodeForWire(RLP.input(Bytes.fromHexString(txRlp)));
final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(txRlp)));
assertThat(transaction.encoded()).isEqualTo(Bytes.fromHexString(txRlp));
}
private Transaction decodeRLP(final RLPInput input) {
return TransactionDecoder.decodeRLP(input, EncodingContext.POOLED_TRANSACTION);
}
}

@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
@ -36,19 +37,17 @@ class TransactionRLPEncoderTest {
@Test
void encodeFrontierTxNominalCase() {
final Transaction transaction =
TransactionDecoder.decodeForWire(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP)));
final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP)));
final BytesValueRLPOutput output = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(transaction, output);
encodeRLP(transaction, output);
assertThat(output.encoded().toHexString()).isEqualTo(FRONTIER_TX_RLP);
}
@Test
void encodeEIP1559TxNominalCase() {
final Transaction transaction =
TransactionDecoder.decodeForWire(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP)));
final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP)));
final BytesValueRLPOutput output = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(transaction, output);
encodeRLP(transaction, output);
assertThat(output.encoded().toHexString()).isEqualTo(EIP1559_TX_RLP);
}
@ -70,10 +69,17 @@ class TransactionRLPEncoderTest {
@Test
void shouldEncodeWithHighNonce() {
final Transaction transaction =
TransactionDecoder.decodeForWire(
RLP.input(Bytes.fromHexString(NONCE_64_BIT_MAX_MINUS_2_TX_RLP)));
decodeRLP(RLP.input(Bytes.fromHexString(NONCE_64_BIT_MAX_MINUS_2_TX_RLP)));
final BytesValueRLPOutput output = new BytesValueRLPOutput();
TransactionEncoder.encodeForWire(transaction, output);
encodeRLP(transaction, output);
assertThat(output.encoded().toHexString()).isEqualTo(NONCE_64_BIT_MAX_MINUS_2_TX_RLP);
}
private Transaction decodeRLP(final RLPInput input) {
return TransactionDecoder.decodeRLP(input, EncodingContext.BLOCK_BODY);
}
private void encodeRLP(final Transaction transaction, final BytesValueRLPOutput output) {
TransactionEncoder.encodeRLP(transaction, output, EncodingContext.BLOCK_BODY);
}
}

File diff suppressed because one or more lines are too long

@ -20,6 +20,8 @@ import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.messages.BlockBodiesMessage;
import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage;
@ -269,8 +271,9 @@ class EthServer {
if (maybeTx.isEmpty()) {
continue;
}
final BytesValueRLPOutput txRlp = new BytesValueRLPOutput();
maybeTx.get().writeTo(txRlp);
TransactionEncoder.encodeRLP(maybeTx.get(), txRlp, EncodingContext.POOLED_TRANSACTION);
final int encodedSize = txRlp.encodedSize();
if (responseSizeEstimate + encodedSize > maxMessageSize) {
break;

@ -15,7 +15,9 @@
package org.hyperledger.besu.ethereum.eth.messages;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractMessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
@ -44,11 +46,7 @@ public final class PooledTransactionsMessage extends AbstractMessageData {
out.writeList(
transactions,
(transaction, rlpOutput) -> {
if (transaction.getType().supportsBlob()) {
BlobTransactionEncoder.encodeForWireNetwork(transaction, rlpOutput);
} else {
transaction.writeTo(rlpOutput);
}
TransactionEncoder.encodeRLP(transaction, rlpOutput, EncodingContext.POOLED_TRANSACTION);
});
return new PooledTransactionsMessage(out.encoded());
}
@ -80,7 +78,9 @@ public final class PooledTransactionsMessage extends AbstractMessageData {
public List<Transaction> transactions() {
if (pooledTransactions == null) {
final BytesValueRLPInput in = new BytesValueRLPInput(getData(), false);
pooledTransactions = in.readList(Transaction::readFrom);
pooledTransactions =
in.readList(
input -> TransactionDecoder.decodeRLP(input, EncodingContext.POOLED_TRANSACTION));
}
return pooledTransactions;
}

Loading…
Cancel
Save