From 084edf72197eb15a80d01e24c9e4c800c31cb9a8 Mon Sep 17 00:00:00 2001 From: Chen Kai <281165273grape@gmail.com> Date: Thu, 23 Nov 2023 10:38:49 +0800 Subject: [PATCH] feat:add op deposit tx encode/decode. Signed-off-by: Chen Kai <281165273grape@gmail.com> --- .../besu/datatypes/Transaction.java | 21 +++++++++ .../besu/datatypes/TransactionType.java | 9 ++-- .../besu/ethereum/core/Transaction.java | 27 +++++++++--- .../OptimismDepositTransactionDecoder.java | 44 +++++++++++++++++++ .../OptimismDepositTransactionEncoder.java | 40 +++++++++++++++++ .../core/encoding/TransactionDecoder.java | 4 +- .../core/encoding/TransactionEncoder.java | 4 +- .../ethereum/core/BlockDataGenerator.java | 16 ++++++- .../ethereum/core/TransactionTestFixture.java | 6 +++ .../encoding/TransactionRLPDecoderTest.java | 33 +++++++++++++- .../encoding/TransactionRLPEncoderTest.java | 23 ++++++++++ .../eth/transactions/PendingTransaction.java | 17 +++++++ 12 files changed, 231 insertions(+), 13 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionDecoder.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionEncoder.java diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java index 3407513ca74..7a8156efdd5 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java @@ -199,6 +199,27 @@ default Optional getMaxFeePerBlobGas() { */ Optional getBlobsWithCommitments(); + /** + * Return the source hash for this transaction. + * + * @return optional source hash + */ + Optional getSourceHash(); + + /** + * Return the mint value for this transaction. + * + * @return optional mint value + */ + Optional getMint(); + + /** + * Return the is system transaction flag for this transaction. + * + * @return optional is system transaction flag + */ + Optional getIsSystemTx(); + /** * Return the address of the contract, if the transaction creates one * diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java index 984a4cc7467..ac2ff5ff338 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java @@ -27,7 +27,9 @@ public enum TransactionType { /** Eip1559 transaction type. */ EIP1559(0x02), /** Blob transaction type. */ - BLOB(0x03); + BLOB(0x03), + /** Optimism Deposit transaction type. */ + OPTIMISM_DEPOSIT(0x7e); private static final Set ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES = Set.of(ACCESS_LIST, EIP1559, BLOB); @@ -83,7 +85,8 @@ public static TransactionType of(final int serializedTypeValue) { TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559, - TransactionType.BLOB + TransactionType.BLOB, + TransactionType.OPTIMISM_DEPOSIT }) .filter(transactionType -> transactionType.typeValue == serializedTypeValue) .findFirst() @@ -117,7 +120,7 @@ public boolean supports1559FeeMarket() { * @return the boolean */ public boolean requiresChainId() { - return !this.equals(FRONTIER); + return !this.equals(FRONTIER) && !this.equals(OPTIMISM_DEPOSIT); } /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index 99be1f0eb14..39ccecbf9e2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -121,7 +121,7 @@ public class Transaction private final Optional sourceHash; - private final Optional mint; + private final Optional mint; private final Optional isSystemTx; @@ -184,7 +184,7 @@ private Transaction( final Optional> versionedHashes, final Optional blobsWithCommitments, final Optional sourceHash, - final Optional mint, + final Optional mint, final Optional isSystemTx) { this.sourceHash = sourceHash; this.mint = mint; @@ -243,7 +243,9 @@ private Transaction( this.versionedHashes = versionedHashes; this.blobsWithCommitments = blobsWithCommitments; - if (!forCopy && isUpfrontGasCostTooHigh()) { + if (!forCopy + && transactionType != TransactionType.OPTIMISM_DEPOSIT + && isUpfrontGasCostTooHigh()) { throw new IllegalArgumentException("Upfront gas cost exceeds UInt256"); } } @@ -678,6 +680,21 @@ public Optional getBlobsWithCommitments() { return blobsWithCommitments; } + @Override + public Optional getSourceHash() { + return sourceHash; + } + + @Override + public Optional getMint() { + return mint; + } + + @Override + public Optional getIsSystemTx() { + return isSystemTx; + } + /** * Return the list of transaction hashes extracted from the collection of Transaction passed as * argument @@ -1138,7 +1155,7 @@ public static class Builder { private Hash sourceHash; - private BigInteger mint; + private Wei mint; private Boolean isSystemTx; @@ -1230,7 +1247,7 @@ public Builder sourceHash(final Hash sourceHash) { return this; } - public Builder mint(final BigInteger mint) { + public Builder mint(final Wei mint) { this.mint = mint; return this; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionDecoder.java new file mode 100644 index 00000000000..3e8a27c382a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionDecoder.java @@ -0,0 +1,44 @@ +/* + * Copyright optimism-java. + * + * 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.Address; +import org.hyperledger.besu.datatypes.Hash; +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; + +public class OptimismDepositTransactionDecoder { + + public static Transaction decode(final RLPInput input) { + input.enterList(); + final Transaction.Builder builder = + Transaction.builder() + .type(TransactionType.OPTIMISM_DEPOSIT) + .sourceHash(Hash.wrap(input.readBytes32())) + .sender(Address.wrap(input.readBytes())) + .to(input.readBytes(v -> v.isEmpty() ? null : Address.wrap(v))) + .mint(Wei.of(input.readUInt256Scalar())) + .value(Wei.of(input.readUInt256Scalar())) + .gasLimit(input.readLongScalar()) + .isSystemTx(input.readBigIntegerScalar().compareTo(BigInteger.ONE) == 0) + .payload(input.readBytes()); + input.leaveList(); + return builder.build(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionEncoder.java new file mode 100644 index 00000000000..db0d314e23e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/OptimismDepositTransactionEncoder.java @@ -0,0 +1,40 @@ +/* + * Copyright optimism-java. + * + * 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.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import java.math.BigInteger; + +import org.apache.tuweni.bytes.Bytes; + +public class OptimismDepositTransactionEncoder { + + public static void encode(final Transaction transaction, final RLPOutput out) { + out.startList(); + out.writeBytes(transaction.getSourceHash().orElseThrow()); + out.writeBytes(transaction.getSender()); + out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY)); + out.writeUInt256Scalar(transaction.getMint().orElse(Wei.ZERO)); + out.writeUInt256Scalar(transaction.getValue() != null ? transaction.getValue() : Wei.ZERO); + out.writeLongScalar(transaction.getGasLimit()); + out.writeBigIntegerScalar( + transaction.getIsSystemTx().map(b -> b ? BigInteger.ONE : BigInteger.ZERO).orElseThrow()); + out.writeBytes(transaction.getPayload() != null ? transaction.getPayload() : Bytes.EMPTY); + out.endList(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java index fe6687cdb5c..5ba7375c9b6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java @@ -40,7 +40,9 @@ interface Decoder { TransactionType.EIP1559, EIP1559TransactionDecoder::decode, TransactionType.BLOB, - BlobTransactionDecoder::decode); + BlobTransactionDecoder::decode, + TransactionType.OPTIMISM_DEPOSIT, + OptimismDepositTransactionDecoder::decode); private static final ImmutableMap POOLED_TRANSACTION_DECODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionDecoder::decode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java index 20fe75885e4..c6813178118 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java @@ -39,7 +39,9 @@ interface Encoder { TransactionType.EIP1559, EIP1559TransactionEncoder::encode, TransactionType.BLOB, - BlobTransactionEncoder::encode); + BlobTransactionEncoder::encode, + TransactionType.OPTIMISM_DEPOSIT, + OptimismDepositTransactionEncoder::encode); private static final ImmutableMap POOLED_TRANSACTION_ENCODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionEncoder::encode); diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index becbb45238e..a56f29b5399 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -376,7 +376,7 @@ public Transaction transaction( case EIP1559 -> eip1559Transaction(payload, to); case ACCESS_LIST -> accessListTransaction(payload, to); case BLOB -> blobTransaction(payload, to); - + case OPTIMISM_DEPOSIT -> optimismDepositTransaction(payload, to); // no default, all types accounted for. }; } @@ -450,6 +450,20 @@ private Transaction frontierTransaction(final Bytes payload, final Address to) { .signAndBuild(generateKeyPair()); } + private Transaction optimismDepositTransaction(final Bytes payload, final Address to) { + return Transaction.builder() + .type(TransactionType.OPTIMISM_DEPOSIT) + .gasLimit(positiveLong()) + .to(to) + .value(Wei.wrap(bytes32())) + .payload(payload) + .isSystemTx(false) + .mint(Wei.of(BigInteger.ONE)) + .sourceHash(Hash.ZERO) + .chainId(BigInteger.TEN) + .signAndBuild(generateKeyPair()); + } + public Set transactions(final int n, final TransactionType... transactionTypes) { return Stream.generate(() -> transaction(transactionTypes)) .parallel() diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java index 933e6b3ce0b..fac550af012 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; @@ -92,6 +93,11 @@ public Transaction createTransaction(final KeyPair keys) { builder.kzgBlobs(bwc.getKzgCommitments(), bwc.getBlobs(), bwc.getKzgProofs()); }); break; + case OPTIMISM_DEPOSIT: + builder.sourceHash(Hash.ZERO); + builder.isSystemTx(true); + builder.mint(Wei.of(BigInteger.ONE)); + break; } to.ifPresent(builder::to); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoderTest.java index 245561158b0..fa013272436 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPDecoderTest.java @@ -17,7 +17,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hyperledger.besu.evm.account.Account.MAX_NONCE; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +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; @@ -43,6 +47,24 @@ class TransactionRLPDecoderTest { private static final String NONCE_64_BIT_MAX_MINUS_2_TX_RLP = "0xf86788fffffffffffffffe0182520894095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"; + private static final String OPTIMISM_DEPOSIT_TX_RLP = + "0xb8417ef83ea0000000000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000080648082c3500180"; + + @Test + void decodeOptimismDepositNominalCase() { + final Transaction transaction = + decodeRLP(RLP.input(Bytes.fromHexString(OPTIMISM_DEPOSIT_TX_RLP))); + assertThat(transaction).isNotNull(); + assertThat(transaction.getType()).isEqualTo(TransactionType.OPTIMISM_DEPOSIT); + assertThat(transaction.getSourceHash().get()).isEqualTo(Hash.ZERO); + assertThat(transaction.getSender()).isEqualTo(Address.ZERO); + assertTrue(transaction.getIsSystemTx().get()); + assertThat(transaction.getMint().get()).isEqualByComparingTo(Wei.of(100L)); + assertThat(transaction.getValue()).isEqualByComparingTo(Wei.ZERO); + assertThat(transaction.getGasLimit()).isEqualTo(50000L); + assertTrue(transaction.getData().isEmpty()); + } + @Test void decodeFrontierNominalCase() { final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP))); @@ -84,7 +106,8 @@ private static Collection dataTransactionSize() { new Object[][] { {FRONTIER_TX_RLP, "FRONTIER_TX_RLP"}, {EIP1559_TX_RLP, "EIP1559_TX_RLP"}, - {NONCE_64_BIT_MAX_MINUS_2_TX_RLP, "NONCE_64_BIT_MAX_MINUS_2_TX_RLP"} + {NONCE_64_BIT_MAX_MINUS_2_TX_RLP, "NONCE_64_BIT_MAX_MINUS_2_TX_RLP"}, + {OPTIMISM_DEPOSIT_TX_RLP, "OPTIMISM_DEPOSIT_TX_RLP"} }); } @@ -102,7 +125,13 @@ void shouldCalculateCorrectTransactionSize(final String rlp_tx, final String ign } @ParameterizedTest - @ValueSource(strings = {FRONTIER_TX_RLP, EIP1559_TX_RLP, NONCE_64_BIT_MAX_MINUS_2_TX_RLP}) + @ValueSource( + strings = { + FRONTIER_TX_RLP, + EIP1559_TX_RLP, + NONCE_64_BIT_MAX_MINUS_2_TX_RLP, + OPTIMISM_DEPOSIT_TX_RLP + }) void shouldReturnCorrectEncodedBytes(final String txRlp) { final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(txRlp))); assertThat(transaction.encoded()).isEqualTo(Bytes.fromHexString(txRlp)); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java index fa84bc5ef7a..bdbf04b10b9 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/TransactionRLPEncoderTest.java @@ -16,6 +16,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.Transaction; @@ -35,6 +39,25 @@ class TransactionRLPEncoderTest { private static final String NONCE_64_BIT_MAX_MINUS_2_TX_RLP = "0xf86788fffffffffffffffe0182520894095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"; + private static final String OPTIMISM_DEPOSIT_TX_RLP = + "0xb8417ef83ea0000000000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000080648082c3500180"; + + @Test + void encodeOptimismDepositNominalCase() { + final Transaction transaction = + Transaction.builder() + .type(TransactionType.OPTIMISM_DEPOSIT) + .sourceHash(Hash.ZERO) + .sender(Address.ZERO) + .mint(Wei.of(100L)) + .gasLimit(50000L) + .isSystemTx(true) + .build(); + final BytesValueRLPOutput output = new BytesValueRLPOutput(); + encodeRLP(transaction, output); + assertThat(output.encoded().toHexString()).isEqualTo(OPTIMISM_DEPOSIT_TX_RLP); + } + @Test void encodeFrontierTxNominalCase() { final Transaction transaction = decodeRLP(RLP.input(Bytes.fromHexString(FRONTIER_TX_RLP))); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java index dbc461ecc38..2cfe48ac870 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java @@ -46,6 +46,12 @@ public abstract class PendingTransaction static final int BLOB_SIZE = 131136; static final int BLOBS_WITH_COMMITMENTS_SIZE = 32; static final int PENDING_TRANSACTION_MEMORY_SIZE = 40; + + static final int SOURCE_HASH_SIZE = 32; + + static final int IS_SYSTEM_TX_SIZE = 1; + + static final int MINT_SIZE = 32; private static final AtomicLong TRANSACTIONS_ADDED = new AtomicLong(); private final Transaction transaction; private final long addedAt; @@ -131,6 +137,7 @@ private int computeMemorySize() { case ACCESS_LIST -> computeAccessListMemorySize(); case EIP1559 -> computeEIP1559MemorySize(); case BLOB -> computeBlobMemorySize(); + case OPTIMISM_DEPOSIT -> computeOptimismDepositMemorySize(); } + PENDING_TRANSACTION_MEMORY_SIZE; } @@ -210,6 +217,16 @@ private int computeAccessListEntriesMemorySize() { .orElse(0); } + // TODO: correct memory size for OptimismDeposit transactions + private int computeOptimismDepositMemorySize() { + return FRONTIER_AND_ACCESS_LIST_BASE_MEMORY_SIZE + + computePayloadMemorySize() + + computeToMemorySize() + + SOURCE_HASH_SIZE + + IS_SYSTEM_TX_SIZE + + MINT_SIZE; + } + public static List toTransactionList( final Collection transactionsInfo) { return transactionsInfo.stream().map(PendingTransaction::getTransaction).toList();