diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java index 8f8c982c28..b52006a479 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java @@ -21,11 +21,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.util.DomainObjectDecodeUtils; import org.hyperledger.besu.ethereum.core.Hash; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; @@ -34,7 +34,6 @@ import java.util.function.Supplier; import com.google.common.base.Suppliers; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes; public class EthSendRawTransaction implements JsonRpcMethod { private static final Logger LOG = LogManager.getLogger(); @@ -68,7 +67,7 @@ public class EthSendRawTransaction implements JsonRpcMethod { final Transaction transaction; try { - transaction = Transaction.readFrom(RLP.input(Bytes.fromHexString(rawTransaction))); + transaction = DomainObjectDecodeUtils.decodeRawTransaction(rawTransaction); } catch (final RLPException | IllegalArgumentException e) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java index 1efcb70df0..815d18b98b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java @@ -14,9 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.util; +import static org.hyperledger.besu.plugin.data.TransactionType.FRONTIER; + import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; +import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPException; import org.apache.logging.log4j.LogManager; @@ -29,7 +32,12 @@ public class DomainObjectDecodeUtils { public static Transaction decodeRawTransaction(final String rawTransaction) throws InvalidJsonRpcRequestException { try { - return TransactionDecoder.decodeOpaqueBytes(Bytes.fromHexString(rawTransaction)); + Bytes txnBytes = Bytes.fromHexString(rawTransaction); + if (!txnBytes.isEmpty() && FRONTIER.compareTo(txnBytes.get(0)) < 0) { + return TransactionDecoder.decodeOpaqueBytes(txnBytes); + } else { + return Transaction.readFrom(RLP.input(txnBytes)); + } } catch (final IllegalArgumentException | RLPException e) { LOG.debug(e); throw new InvalidJsonRpcRequestException("Invalid raw transaction hex", e); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java new file mode 100644 index 0000000000..cbc94e7db1 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java @@ -0,0 +1,76 @@ +/* + * + * 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.api.util; + +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.ethereum.core.AccessListEntry; +import org.hyperledger.besu.ethereum.core.Address; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.Wei; +import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.math.BigInteger; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +public class DomainObjectDecodeUtilsTest { + + static final BlockDataGenerator gen = new BlockDataGenerator(); + private static final SECPSignature signature = + SignatureAlgorithmFactory.getInstance() + .createSignature(BigInteger.ONE, BigInteger.TEN, (byte) 1); + private static final Address sender = + Address.fromHexString("0x0000000000000000000000000000000000000003"); + + private static final Transaction accessListTxn = + Transaction.builder() + .chainId(BigInteger.valueOf(2018)) + .accessList(List.of(new AccessListEntry(gen.address(), List.of(gen.bytes32())))) + .nonce(1) + .gasPrice(Wei.of(12)) + .gasLimit(43) + .payload(Bytes.EMPTY) + .value(Wei.ZERO) + .signature(signature) + .sender(sender) + .guessType() + .build(); + + @Test + public void testAccessListRLPSerDes() { + final BytesValueRLPOutput encoded = new BytesValueRLPOutput(); + TransactionEncoder.encodeForWire(accessListTxn, encoded); + Transaction decoded = + DomainObjectDecodeUtils.decodeRawTransaction(encoded.encoded().toHexString()); + Assertions.assertThat(decoded.getAccessList().isPresent()).isTrue(); + Assertions.assertThat(decoded.getAccessList().map(List::size).get()).isEqualTo(1); + } + + @Test + public void testAccessList2718OpaqueSerDes() { + final Bytes encoded = TransactionEncoder.encodeOpaqueBytes(accessListTxn); + Transaction decoded = DomainObjectDecodeUtils.decodeRawTransaction(encoded.toString()); + Assertions.assertThat(decoded.getAccessList().isPresent()).isTrue(); + Assertions.assertThat(decoded.getAccessList().map(List::size).get()).isEqualTo(1); + } +} diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 79c6460cdb..c46794ca11 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -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 = 'buMv4cF0qEizHP3LMbnrm88TcccAy/v04iPgMjlUQ4k=' + knownHash = 'CSYnesxrM26KNr8yz9WZEWLvxkHMBofp0QA4kkSZ3Zw=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionType.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionType.java index bb9bebe932..2c1dfad265 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionType.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionType.java @@ -35,6 +35,10 @@ public enum TransactionType { return (byte) this.typeValue; } + public int compareTo(final Byte b) { + return Byte.valueOf(getSerializedType()).compareTo(b); + } + public static TransactionType of(final int serializedTypeValue) { return Arrays.stream(TransactionType.values()) .filter(transactionType -> transactionType.typeValue == serializedTypeValue)