Skip to content

Commit

Permalink
refactor: use preferred headlong API when decoding RLP (#17525)
Browse files Browse the repository at this point in the history
Headlong's `RLPItem.asBigInt` preferred over `asByte/asInt/asLong`.

Fixes #17523 

Signed-off-by: David S Bakin <117694041+david-bakin-sl@users.noreply.github.com>
  • Loading branch information
david-bakin-sl authored Jan 24, 2025
1 parent a7996a3 commit a1d2d0b
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
* Copyright (C) 2022-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -81,7 +81,7 @@ public static EthTxData populateEthTxData(byte[] data) {
return populateLegacyEthTxData(rlpItem, data);
}

return switch (rlpItem.asByte()) {
return switch (asByte(rlpItem)) {
case 1 -> populateEip2390EthTxData(decoder.next(), data);
case 2 -> populateEip1559EthTxData(decoder.next(), data);
case 3 -> null; // We don't currently support Cancun "blob" transactions
Expand Down Expand Up @@ -401,11 +401,11 @@ private static EthTxData populateLegacyEthTxData(RLPItem rlpItem, byte[] rawTx)
rawTx,
EthTransactionType.LEGACY_ETHEREUM,
chainId,
rlpList.get(0).asLong(), // nonce
asLong(rlpList.get(0)), // nonce
rlpList.get(1).asBytes(), // gasPrice
null, // maxPriorityGas
null, // maxGas
rlpList.get(2).asLong(), // gasLimit
asLong(rlpList.get(2)), // gasLimit
rlpList.get(3).data(), // to
rlpList.get(4).asBigInt(), // value
rlpList.get(5).data(), // callData
Expand Down Expand Up @@ -436,16 +436,16 @@ private static EthTxData populateEip1559EthTxData(RLPItem rlpItem, byte[] rawTx)
rawTx,
EthTransactionType.EIP1559,
rlpList.get(0).data(), // chainId
rlpList.get(1).asLong(), // nonce
asLong(rlpList.get(1)), // nonce
null, // gasPrice
rlpList.get(2).data(), // maxPriorityGas
rlpList.get(3).data(), // maxGas
rlpList.get(4).asLong(), // gasLimit
asLong(rlpList.get(4)), // gasLimit
rlpList.get(5).data(), // to
rlpList.get(6).asBigInt(), // value
rlpList.get(7).data(), // callData
rlpList.get(8).data(), // accessList
rlpList.get(9).asByte(), // recId
asByte(rlpList.get(9)), // recId
null, // v
rlpList.get(10).data(), // r
rlpList.get(11).data() // s
Expand All @@ -471,16 +471,16 @@ private static EthTxData populateEip2390EthTxData(RLPItem rlpItem, byte[] rawTx)
rawTx,
EthTransactionType.EIP2930,
rlpList.get(0).data(), // chainId
rlpList.get(1).asLong(), // nonce
asLong(rlpList.get(1)), // nonce
rlpList.get(2).data(), // gasPrice
null, // maxPriorityGas
null, // maxGas
rlpList.get(3).asLong(), // gasLimit
asLong(rlpList.get(3)), // gasLimit
rlpList.get(4).data(), // to
rlpList.get(5).asBigInt(), // value
rlpList.get(6).data(), // callData
rlpList.get(7).data(), // accessList
rlpList.get(8).asByte(), // recId
asByte(rlpList.get(8)), // recId
null, // v
rlpList.get(9).data(), // r
rlpList.get(10).data() // s
Expand All @@ -492,4 +492,31 @@ private static EthTxData populateEip2390EthTxData(RLPItem rlpItem, byte[] rawTx)
private static boolean isLegacyUnprotectedEtx(@NonNull BigInteger vBI) {
return vBI.compareTo(LEGACY_V_BYTE_SIGNATURE_0) == 0 || vBI.compareTo(LEGACY_V_BYTE_SIGNATURE_1) == 0;
}

// `asByte` and `asLong` always return positive values by replacing out of range values with
// `MAX_VALUE`. (`RLPItem.asBigInt` cannot return negative values: Negative values cannot be
// encoded in RLP.)

private static byte asByte(@NonNull final RLPItem rlpItem) {
var v = rlpItem.asBigInt(false);
if (v.compareTo(BigInteger.ZERO) < 0) throwOutOfRange();
if (v.compareTo(BigInteger.valueOf(Byte.MAX_VALUE)) > 0) throwOutOfRange();
return v.byteValueExact();
}

private static long asLong(@NonNull final RLPItem rlpItem) {
var v = rlpItem.asBigInt(false);
if (v.compareTo(BigInteger.ZERO) < 0) throwOutOfRange();
if (v.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) throwOutOfRange();
return v.longValueExact();
}

private static void throwOutOfRange() {
class OutOfRangeException extends IllegalArgumentException {
public OutOfRangeException() {
super("EthTxData has RLPItem out of range");
}
}
throw new OutOfRangeException();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
* Copyright (C) 2022-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,6 +29,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.esaulpaugh.headlong.rlp.RLPEncoder;
import com.esaulpaugh.headlong.util.Integers;
import com.google.protobuf.ByteString;
import com.hedera.node.app.hapi.utils.ethereum.EthTxData.EthTransactionType;
import com.swirlds.common.utility.CommonUtils;
Expand Down Expand Up @@ -355,6 +356,7 @@ byte[][] normalRlpData() {
@Test
void parsingErrors() {
final var wrongData = Hex.encode(ByteString.copyFromUtf8("wrong").toByteArray());
final var negativeInteger = Integers.toBytes(Long.MIN_VALUE);

// invalid nonce
var normalData = normalRlpData();
Expand All @@ -363,13 +365,20 @@ void parsingErrors() {

assertNull(EthTxData.populateEthTxData(RLPEncoder.encodeSequentially(new byte[] {2}, invalidNonceData)));

// invalid gasLimit
// invalid gasLimit: too large
normalData = normalRlpData();
normalData[4] = wrongData;
final var invalidGasLimitData = Arrays.asList(normalData);

assertNull(EthTxData.populateEthTxData(RLPEncoder.encodeSequentially(new byte[] {2}, invalidGasLimitData)));

// invalid gaslimit: negative
normalData = normalRlpData();
normalData[4] = negativeInteger;
final var invalidGasDataNegative = Arrays.asList(normalData);

assertNull(EthTxData.populateEthTxData(RLPEncoder.encodeSequentially(new byte[] {2}, invalidGasDataNegative)));

// invalid recId
normalData = normalRlpData();
normalData[9] = wrongData;
Expand Down

0 comments on commit a1d2d0b

Please sign in to comment.