Refactor transaction rlp encoding / decoding (#1375)

* Add utility classes to deal with RLP encoding / decoding of transactions.
- Added `TransactionRLPDecoder` with `Transaction decode(RLPInput input);` method.
- Added `TransactionRLPEncoder` with `void encode(Transaction transaction, RLPOutput output);` method.
- Added `TransactionRLPEncoderDecoder` with 2 static methods:
    - `static void encode(final Transaction transaction, final RLPOutput output)`
    - `static Transaction decode(final RLPInput input)`
- Updated `Transaction` class to use `TransactionRLPEncoderDecoder`.

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>
pull/1382/head
Abdelhamid Bakhta 4 years ago committed by GitHub
parent fd98049969
commit 31df8660d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 124
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  2. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Wei.java
  3. 132
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoder.java
  4. 86
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoder.java
  5. 68
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoderTest.java
  6. 85
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java
  7. 2
      plugin-api/build.gradle
  8. 14
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Transaction.java
  9. 20
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionType.java

@ -19,6 +19,8 @@ import static org.hyperledger.besu.crypto.Hash.keccak256;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.ethereum.core.encoding.TransactionRLPDecoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionRLPEncoder;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
@ -38,16 +40,16 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
// Used for transactions that are not tied to a specific chain
// (e.g. does not have a chain id associated with it).
private static final BigInteger REPLAY_UNPROTECTED_V_BASE = BigInteger.valueOf(27);
private static final BigInteger REPLAY_UNPROTECTED_V_BASE_PLUS_1 = BigInteger.valueOf(28);
public static final BigInteger REPLAY_UNPROTECTED_V_BASE = BigInteger.valueOf(27);
public static final BigInteger REPLAY_UNPROTECTED_V_BASE_PLUS_1 = BigInteger.valueOf(28);
private static final BigInteger REPLAY_PROTECTED_V_BASE = BigInteger.valueOf(35);
public static final BigInteger REPLAY_PROTECTED_V_BASE = BigInteger.valueOf(35);
// The v signature parameter starts at 36 because 1 is the first valid chainId so:
// chainId > 1 implies that 2 * chainId + V_BASE > 36.
private static final BigInteger REPLAY_PROTECTED_V_MIN = BigInteger.valueOf(36);
public static final BigInteger REPLAY_PROTECTED_V_MIN = BigInteger.valueOf(36);
private static final BigInteger TWO = BigInteger.valueOf(2);
public static final BigInteger TWO = BigInteger.valueOf(2);
private final long nonce;
@ -86,86 +88,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
}
public static Transaction readFrom(final RLPInput input) throws RLPException {
if (ExperimentalEIPs.eip1559Enabled) {
return readFromExperimental(input);
}
input.enterList();
final Builder builder =
builder()
.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().toBytes().toUnsignedBigInteger();
final BigInteger s = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
final SECP256K1.Signature signature = SECP256K1.Signature.create(r, s, recId);
input.leaveList();
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
}
public static Transaction readFromExperimental(final RLPInput input) throws RLPException {
input.enterList();
final Builder builder =
builder()
.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 Bytes maybeGasPremiumOrV = input.readBytes();
final Bytes maybeFeeCapOrR = input.readBytes();
final Bytes maybeVOrS = input.readBytes();
final BigInteger v, r, s;
// if this is the end of the list we are processing a legacy transaction
if (input.isEndOfCurrentList()) {
v = maybeGasPremiumOrV.toUnsignedBigInteger();
r = maybeFeeCapOrR.toUnsignedBigInteger();
s = maybeVOrS.toUnsignedBigInteger();
} else {
// otherwise this is an EIP-1559 transaction
builder
.gasPremium(Wei.of(maybeGasPremiumOrV.toBigInteger()))
.feeCap(Wei.of(maybeFeeCapOrR.toBigInteger()));
v = maybeVOrS.toBigInteger();
r = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
s = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
}
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 SECP256K1.Signature signature = SECP256K1.Signature.create(r, s, recId);
input.leaveList();
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
return TransactionRLPDecoder.decodeTransaction(input);
}
/**
@ -408,36 +331,7 @@ public class Transaction implements org.hyperledger.besu.plugin.data.Transaction
* @param out the output to write the transaction to
*/
public void writeTo(final RLPOutput out) {
out.startList();
out.writeLongScalar(getNonce());
final boolean asEIP1559 =
ExperimentalEIPs.eip1559Enabled
&& (gasPrice == null || gasPrice.isZero())
&& gasPremium != null
&& feeCap != null;
if (asEIP1559) {
out.writeNull();
} else {
out.writeUInt256Scalar(getGasPrice());
}
out.writeLongScalar(getGasLimit());
out.writeBytes(getTo().isPresent() ? getTo().get() : Bytes.EMPTY);
out.writeUInt256Scalar(getValue());
out.writeBytes(getPayload());
if (ExperimentalEIPs.eip1559Enabled && gasPremium != null && feeCap != null) {
out.writeUInt256Scalar(gasPremium);
out.writeUInt256Scalar(feeCap);
}
writeSignature(out);
out.endList();
}
private void writeSignature(final RLPOutput out) {
out.writeBigIntegerScalar(getV());
out.writeBigIntegerScalar(getSignature().getR());
out.writeBigIntegerScalar(getSignature().getS());
TransactionRLPEncoder.encode(this, out);
}
@Override

@ -55,6 +55,10 @@ public final class Wei extends BaseUInt256Value<Wei> implements Quantity {
return new Wei(value);
}
public static Wei ofNumber(final Number value) {
return new Wei((BigInteger) value);
}
public static Wei wrap(final Bytes32 value) {
return new Wei(UInt256.fromBytes(value));
}

@ -0,0 +1,132 @@
/*
* Copyright ConsenSys AG.
*
* 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.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
@FunctionalInterface
public interface TransactionRLPDecoder {
TransactionRLPDecoder FRONTIER = frontierDecoder();
TransactionRLPDecoder EIP1559 = eip1559Decoder();
static Transaction decodeTransaction(final RLPInput input) {
return (ExperimentalEIPs.eip1559Enabled ? EIP1559 : FRONTIER).decode(input);
}
Transaction decode(RLPInput input);
static TransactionRLPDecoder frontierDecoder() {
return input -> {
input.enterList();
final Transaction.Builder builder =
Transaction.builder()
.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().toBytes().toUnsignedBigInteger();
final BigInteger s = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
final SECP256K1.Signature signature = SECP256K1.Signature.create(r, s, recId);
input.leaveList();
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
};
}
static TransactionRLPDecoder eip1559Decoder() {
return input -> {
input.enterList();
final Transaction.Builder builder =
Transaction.builder()
.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 Bytes maybeGasPremiumOrV = input.readBytes();
final Bytes maybeFeeCapOrR = input.readBytes();
final Bytes maybeVOrS = input.readBytes();
final BigInteger v, r, s;
// if this is the end of the list we are processing a legacy transaction
if (input.isEndOfCurrentList()) {
v = maybeGasPremiumOrV.toUnsignedBigInteger();
r = maybeFeeCapOrR.toUnsignedBigInteger();
s = maybeVOrS.toUnsignedBigInteger();
} else {
// otherwise this is an EIP-1559 transaction
builder
.gasPremium(Wei.of(maybeGasPremiumOrV.toBigInteger()))
.feeCap(Wei.of(maybeFeeCapOrR.toBigInteger()));
v = maybeVOrS.toBigInteger();
r = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
s = input.readUInt256Scalar().toBytes().toUnsignedBigInteger();
}
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 SECP256K1.Signature signature = SECP256K1.Signature.create(r, s, recId);
input.leaveList();
chainId.ifPresent(builder::chainId);
return builder.signature(signature).build();
};
}
}

@ -0,0 +1,86 @@
/*
* Copyright ConsenSys AG.
*
* 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.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
import org.hyperledger.besu.plugin.data.Quantity;
import org.hyperledger.besu.plugin.data.TransactionType;
import com.google.common.collect.ImmutableMap;
import org.apache.tuweni.bytes.Bytes;
public class TransactionRLPEncoder {
private static final Encoder FRONTIER = frontierEncoder();
private static final Encoder EIP1559 = eip1559Encoder();
private static final ImmutableMap<TransactionType, Encoder> ENCODERS =
ImmutableMap.of(TransactionType.FRONTIER, FRONTIER, TransactionType.EIP1559, EIP1559);
public static void encode(final Transaction transaction, final RLPOutput output) {
ENCODERS.getOrDefault(transaction.getType(), FRONTIER).encode(transaction, output);
}
static Encoder frontierEncoder() {
return (transaction, out) -> {
out.startList();
out.writeLongScalar(transaction.getNonce());
out.writeUInt256Scalar(transaction.getGasPrice());
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().isPresent() ? transaction.getTo().get() : Bytes.EMPTY);
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeSignature(transaction, out);
out.endList();
};
}
static Encoder eip1559Encoder() {
return (transaction, out) -> {
if (!ExperimentalEIPs.eip1559Enabled
|| !TransactionType.EIP1559.equals(transaction.getType())) {
throw new RuntimeException("Invalid transaction format");
}
out.startList();
out.writeLongScalar(transaction.getNonce());
out.writeNull();
out.writeLongScalar(transaction.getGasLimit());
out.writeBytes(transaction.getTo().isPresent() ? transaction.getTo().get() : Bytes.EMPTY);
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
out.writeUInt256Scalar(
transaction.getGasPremium().map(Quantity::getValue).map(Wei::ofNumber).orElseThrow());
out.writeUInt256Scalar(
transaction.getFeeCap().map(Quantity::getValue).map(Wei::ofNumber).orElseThrow());
writeSignature(transaction, out);
out.endList();
};
}
private static void writeSignature(final Transaction transaction, final RLPOutput out) {
out.writeBigIntegerScalar(transaction.getV());
out.writeBigIntegerScalar(transaction.getSignature().getR());
out.writeBigIntegerScalar(transaction.getSignature().getS());
}
@FunctionalInterface
interface Encoder {
void encode(Transaction transaction, RLPOutput output);
}
}

@ -0,0 +1,68 @@
/*
* Copyright ConsenSys AG.
*
* 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.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.rlp.RLPException;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
public class TransactionRLPDecoderTest {
private static final String FRONTIER_TX_RLP =
"0xf901fc8032830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b561ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884";
private static final String EIP1559_TX_RLP =
"0xf902028032830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b5682020f8201711ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884";
@Test
public void decodeFrontierNominalCase() {
final Transaction transaction =
TransactionRLPDecoder.decodeTransaction(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP)));
assertThat(transaction).isNotNull();
assertThat(transaction.getGasPrice()).isEqualByComparingTo(Wei.of(50L));
assertThat(transaction.getGasPremium()).isEmpty();
assertThat(transaction.getFeeCap()).isEmpty();
}
@Test
public void decodeEIP1559NominalCase() {
ExperimentalEIPs.eip1559Enabled = true;
final Transaction transaction =
TransactionRLPDecoder.decodeTransaction(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP)));
assertThat(transaction).isNotNull();
assertThat(transaction.getGasPremium()).hasValue(Wei.of(527L));
assertThat(transaction.getFeeCap()).hasValue(Wei.of(369L));
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
@Test
public void decodeEIP1559FailureWhenNotEnabled() {
ExperimentalEIPs.eip1559Enabled = false;
assertThatThrownBy(
() ->
TransactionRLPDecoder.decodeTransaction(
RLP.input(Bytes.fromHexString(EIP1559_TX_RLP))))
.isInstanceOf(RLPException.class)
.hasMessageContaining("Not at the end of the current list");
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
}

@ -0,0 +1,85 @@
/*
* Copyright ConsenSys AG.
*
* 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.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
/*
* Copyright ConsenSys AG.
*
* 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
*/
public class TransactionRLPEncoderTest {
private static final String FRONTIER_TX_RLP =
"0xf901fc8032830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b561ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884";
private static final String EIP1559_TX_RLP =
"0xf902028032830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b5682020f8201711ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884";
@Test
public void encodeFrontierTxNominalCase() {
final Transaction transaction =
TransactionRLPDecoder.frontierDecoder()
.decode(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP)));
final BytesValueRLPOutput output = new BytesValueRLPOutput();
TransactionRLPEncoder.encode(transaction, output);
assertThat(FRONTIER_TX_RLP).isEqualTo(output.encoded().toHexString());
}
@Test
public void encodeEIP1559TxNominalCase() {
ExperimentalEIPs.eip1559Enabled = true;
final Transaction transaction =
TransactionRLPDecoder.eip1559Decoder()
.decode(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP)));
final BytesValueRLPOutput output = new BytesValueRLPOutput();
TransactionRLPEncoder.encode(transaction, output);
assertThat(
"0xf902028080830138808080b901ae60056013565b6101918061001d6000396000f35b3360008190555056006001600060e060020a6000350480630a874df61461003a57806341c0e1b514610058578063a02b161e14610066578063dbbdf0831461007757005b610045600435610149565b80600160a060020a031660005260206000f35b610060610161565b60006000f35b6100716004356100d4565b60006000f35b61008560043560243561008b565b60006000f35b600054600160a060020a031632600160a060020a031614156100ac576100b1565b6100d0565b8060018360005260205260406000208190555081600060005260206000a15b5050565b600054600160a060020a031633600160a060020a031614158015610118575033600160a060020a0316600182600052602052604060002054600160a060020a031614155b61012157610126565b610146565b600060018260005260205260406000208190555080600060005260206000a15b50565b60006001826000526020526040600020549050919050565b600054600160a060020a031633600160a060020a0316146101815761018f565b600054600160a060020a0316ff5b5682020f8201711ca0c5689ed1ad124753d54576dfb4b571465a41900a1dff4058d8adf16f752013d0a01221cbd70ec28c94a3b55ec771bcbc70778d6ee0b51ca7ea9514594c861b1884")
.isEqualTo(output.encoded().toHexString());
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
@Test
public void encodeEIP1559TxFailureNotEnabled() {
ExperimentalEIPs.eip1559Enabled = false;
assertThatThrownBy(
() ->
TransactionRLPEncoder.encode(
TransactionRLPDecoder.eip1559Decoder()
.decode(RLP.input(Bytes.fromHexString(EIP1559_TX_RLP))),
new BytesValueRLPOutput()))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("Invalid transaction format");
ExperimentalEIPs.eip1559Enabled = ExperimentalEIPs.EIP1559_ENABLED_DEFAULT_VALUE;
}
}

@ -64,7 +64,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'vMdHOeXMKEHayVPnyspzIMFUKXgGJV9A63L5bktLN2U='
knownHash = 'gQ91kSJ/YS1eUWUrEbuei1qGi0uq5yGe/5pVmY5MwU4='
}
check.dependsOn('checkAPIChanges')

@ -190,4 +190,18 @@ public interface Transaction {
default boolean isEIP1559Transaction() {
return false;
}
/**
* Returns the type of the transaction.
*
* @return the type of the transaction
*/
@Unstable
default TransactionType getType() {
if (isEIP1559Transaction()) {
return TransactionType.EIP1559;
} else {
return TransactionType.FRONTIER;
}
}
}

@ -0,0 +1,20 @@
/*
* Copyright ConsenSys AG.
*
* 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.plugin.data;
public enum TransactionType {
FRONTIER,
EIP1559
}
Loading…
Cancel
Save