From 11acfde03c28a0e5808585b095b9c9e440588f34 Mon Sep 17 00:00:00 2001 From: thinkAfCod Date: Tue, 2 Jan 2024 20:39:43 +0800 Subject: [PATCH] feat: execute deposit tx --- .../besu/controller/BesuController.java | 2 + .../besu/config/GenesisConfigOptions.java | 22 +++ .../besu/config/JsonGenesisConfigOptions.java | 39 +++- .../besu/config/StubGenesisConfigOptions.java | 15 ++ .../besu/datatypes/RollupGasData.java | 58 ++++++ .../besu/datatypes/Transaction.java | 7 + .../AbstractEngineForkchoiceUpdated.java | 9 +- .../EnginePayloadAttributesParameter.java | 4 +- .../results/TransactionReceiptResult.java | 11 ++ .../flat/RewardTraceGeneratorTest.java | 6 +- .../besu/ethereum/core/Transaction.java | 22 ++- .../ethereum/core/TransactionReceipt.java | 107 ++++++++--- .../mainnet/AbstractBlockProcessor.java | 49 ++++- .../mainnet/ClassicBlockProcessor.java | 4 +- .../mainnet/ClassicProtocolSpecs.java | 3 +- .../ethereum/mainnet/L1CostCalculator.java | 114 ++++++++++++ .../mainnet/MainnetBlockProcessor.java | 8 +- .../mainnet/MainnetProtocolSpecs.java | 86 ++++++++- .../mainnet/MainnetTransactionProcessor.java | 169 ++++++++++++++++-- .../mainnet/MainnetTransactionValidator.java | 15 +- .../mainnet/ProtocolScheduleBuilder.java | 8 + .../ethereum/mainnet/ProtocolSpecBuilder.java | 19 +- .../mainnet/TransactionValidatorFactory.java | 3 +- .../BlockImportExceptionHandlingTest.java | 5 +- .../mainnet/AbstractBlockProcessorTest.java | 3 +- .../mainnet/MainnetBlockProcessorTest.java | 10 +- .../NoRewardProtocolScheduleWrapper.java | 3 +- 27 files changed, 721 insertions(+), 80 deletions(-) create mode 100644 datatypes/src/main/java/org/hyperledger/besu/datatypes/RollupGasData.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/L1CostCalculator.java diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java index bb46003b24b..582b43b701a 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java @@ -357,6 +357,8 @@ BesuControllerBuilder fromGenesisConfig( builder = new QbftBesuControllerBuilder(); } else if (configOptions.isClique()) { builder = new CliqueBesuControllerBuilder(); + } else if (configOptions.isOptimism()) { + builder = new MainnetBesuControllerBuilder(); } else { throw new IllegalArgumentException("Unknown consensus mechanism defined"); } diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java index db3b1f4eeee..2ff1928e717 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java @@ -277,6 +277,14 @@ default boolean isConsensusMigration() { */ OptionalLong getBedrockBlock(); + /** + * Returns whether a fork scheduled at bedrock block number is active at the given head block + * number + * + * @return the boolean + */ + boolean isBedrockBlock(long headBlock); + /** * Gets regolith time. * @@ -284,6 +292,13 @@ default boolean isConsensusMigration() { */ OptionalLong getRegolithTime(); + /** + * Returns whether a fork scheduled at regolith timestamp is active at the given head timestamp. + * + * @return the boolean + */ + boolean isRegolith(long headTime); + /** * Gets canyon time. * @@ -291,6 +306,13 @@ default boolean isConsensusMigration() { */ OptionalLong getCanyonTime(); + /** + * Returns whether a fork scheduled at canyon timestamp is active at the given head timestamp. + * + * @return the boolean + */ + boolean isCanyon(long headTime); + /** * Gets base fee per gas. * diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java index e7a5088c27e..edef6a96227 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -317,17 +317,50 @@ public OptionalLong getExperimentalEipsTime() { @Override public OptionalLong getBedrockBlock() { - return getOptionalLong("bedrockBlock"); + return getOptionalLong("bedrockblock"); + } + + @Override + public boolean isBedrockBlock(final long headBlock) { + OptionalLong bedrockBlock = getBedrockBlock(); + if (!bedrockBlock.isPresent()) { + return false; + } + return bedrockBlock.getAsLong() <= headBlock; } @Override public OptionalLong getRegolithTime() { - return getOptionalLong("regolithTime"); + return getOptionalLong("regolithtime"); + } + + @Override + public boolean isRegolith(final long headTime) { + if (!isOptimism()) { + return false; + } + var regolithTime = getRegolithTime(); + if (regolithTime.isPresent()) { + return regolithTime.getAsLong() <= headTime; + } + return false; } @Override public OptionalLong getCanyonTime() { - return getOptionalLong("canyonTime"); + return getOptionalLong("canyontime"); + } + + @Override + public boolean isCanyon(final long headTime) { + if (!isOptimism()) { + return false; + } + var canyonTime = getCanyonTime(); + if (canyonTime.isPresent()) { + return canyonTime.getAsLong() <= headTime; + } + return false; } @Override diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index e457d11f827..31c1dab1f08 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -260,16 +260,31 @@ public OptionalLong getBedrockBlock() { return bedrockBlock; } + @Override + public boolean isBedrockBlock(final long headBlock) { + return false; + } + @Override public OptionalLong getRegolithTime() { return regolithTime; } + @Override + public boolean isRegolith(final long headTime) { + return false; + } + @Override public OptionalLong getCanyonTime() { return canyonTime; } + @Override + public boolean isCanyon(final long headTime) { + return false; + } + @Override public Optional getBaseFeePerGas() { return baseFeePerGas; diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/RollupGasData.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/RollupGasData.java new file mode 100644 index 00000000000..be4f2fb113f --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/RollupGasData.java @@ -0,0 +1,58 @@ +/* + * 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.datatypes; + +import org.apache.tuweni.bytes.Bytes; + +/** Optimism roll up data records. */ +public class RollupGasData { + + private static final RollupGasData empty = new RollupGasData(0L, 0L); + + private final long zeroes; + + private final long ones; + + RollupGasData(final long zeroes, final long ones) { + this.zeroes = zeroes; + this.ones = ones; + } + + public long getZeroes() { + return zeroes; + } + + public long getOnes() { + return ones; + } + + public static RollupGasData fromPayload(final Bytes payload) { + if (payload == null) { + return empty; + } + final int length = payload.size(); + int zeroes = 0; + int ones = 0; + for (int i = 0; i < length; i++) { + byte b = payload.get(i); + if (b == 0) { + zeroes++; + } else { + ones++; + } + } + return new RollupGasData(zeroes, ones); + } +} 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 7a8156efdd5..a3d19f69620 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java @@ -220,6 +220,13 @@ default Optional getMaxFeePerBlobGas() { */ Optional getIsSystemTx(); + /** + * Return the roll up gas data. + * + * @return roll up gas data + */ + RollupGasData getRollupGasData(); + /** * Return the address of the contract, if the transaction creates one * diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java index 24e15454f01..bddca4af23a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java @@ -36,8 +36,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateForkchoiceResult; import org.hyperledger.besu.ethereum.core.BlockHeader; -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.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; @@ -224,7 +225,11 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) payloadAttributes.getTransactions() == null ? null : payloadAttributes.getTransactions().stream() - .map(s -> Transaction.readFrom(Bytes.fromHexString(s))) + .map(Bytes::fromHexString) + .map( + in -> + TransactionDecoder.decodeOpaqueBytes( + in, EncodingContext.BLOCK_BODY)) .collect(toList())), Optional.ofNullable(payloadAttributes.getGasLimit()))); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java index 4ee4741a671..17bcd0aca7c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java @@ -46,7 +46,7 @@ public EnginePayloadAttributesParameter( @JsonProperty("parentBeaconBlockRoot") final String parentBeaconBlockRoot, @JsonProperty("noTxPool") final Boolean noTxPool, @JsonProperty("transactions") final List transactions, - @JsonProperty("gasLimit") final Long gasLimit) { + @JsonProperty("gasLimit") final UnsignedLongParameter gasLimit) { this.timestamp = Long.decode(timestamp); this.prevRandao = Bytes32.fromHexString(prevRandao); this.suggestedFeeRecipient = Address.fromHexString(suggestedFeeRecipient); @@ -55,7 +55,7 @@ public EnginePayloadAttributesParameter( parentBeaconBlockRoot == null ? null : Bytes32.fromHexString(parentBeaconBlockRoot); this.noTxPool = noTxPool; this.transactions = transactions; - this.gasLimit = gasLimit; + this.gasLimit = gasLimit.getValue(); } public Long getTimestamp() { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionReceiptResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionReceiptResult.java index b2f39d7a287..dfb2ccbdaa0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionReceiptResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionReceiptResult.java @@ -37,6 +37,7 @@ "cumulativeGasUsed", "from", "gasUsed", + "depositNonce", "effectiveGasPrice", "logs", "logsBloom", @@ -58,6 +59,7 @@ public abstract class TransactionReceiptResult { private final String cumulativeGasUsed; private final String from; private final String gasUsed; + private final String depositNonce; private final String effectiveGasPrice; private final List logs; private final String logsBloom; @@ -83,6 +85,10 @@ protected TransactionReceiptResult(final TransactionReceiptWithMetadata receiptW this.gasUsed = Quantity.create(receiptWithMetadata.getGasUsed()); this.blobGasUsed = receiptWithMetadata.getBlobGasUsed().map(Quantity::create).orElse(null); this.blobGasPrice = receiptWithMetadata.getBlobGasPrice().map(Quantity::create).orElse(null); + this.depositNonce = + this.receipt.getDepositNonce().isPresent() + ? Quantity.create(this.receipt.getDepositNonce().get()) + : null; this.effectiveGasPrice = Quantity.create(txn.getEffectiveGasPrice(receiptWithMetadata.getBaseFee())); @@ -146,6 +152,11 @@ public String getBlobGasPrice() { return blobGasPrice; } + @JsonGetter(value = "depositNonce") + public String getDepositNonce() { + return depositNonce; + } + @JsonGetter(value = "effectiveGasPrice") public String getEffectiveGasPrice() { return effectiveGasPrice; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java index f8275182051..1e5e2631413 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/RewardTraceGeneratorTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace; @@ -35,6 +36,7 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.OptionalLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -55,6 +57,7 @@ public class RewardTraceGeneratorTest { @Mock private ProtocolSpec protocolSpec; @Mock private MiningBeneficiaryCalculator miningBeneficiaryCalculator; @Mock private MainnetTransactionProcessor transactionProcessor; + @Mock private GenesisConfigOptions genesisConfigOptions; private final Address ommerBeneficiary = Address.wrap(Bytes.fromHexString("0x095e7baea6a6c7c4c2dfeb977efac326af552d87")); @@ -91,7 +94,8 @@ public void assertThatTraceGeneratorReturnValidRewardsForMainnetBlockProcessor() blockReward, BlockHeader::getCoinbase, true, - protocolSchedule); + protocolSchedule, + Optional.of(genesisConfigOptions)); when(protocolSpec.getBlockProcessor()).thenReturn(blockProcessor); final Stream traceStream = 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 39ccecbf9e2..6ba75ea625c 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 @@ -31,6 +31,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.KZGCommitment; import org.hyperledger.besu.datatypes.KZGProof; +import org.hyperledger.besu.datatypes.RollupGasData; import org.hyperledger.besu.datatypes.Sha256Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; @@ -125,6 +126,8 @@ public class Transaction private final Optional isSystemTx; + private final RollupGasData rollupGasData; + public static Builder builder() { return new Builder(); } @@ -185,10 +188,12 @@ private Transaction( final Optional blobsWithCommitments, final Optional sourceHash, final Optional mint, - final Optional isSystemTx) { + final Optional isSystemTx, + final RollupGasData rollupGasData) { this.sourceHash = sourceHash; this.mint = mint; this.isSystemTx = isSystemTx; + this.rollupGasData = rollupGasData; if (!forCopy) { if (transactionType.requiresChainId()) { @@ -695,6 +700,11 @@ public Optional getIsSystemTx() { return isSystemTx; } + @Override + public RollupGasData getRollupGasData() { + return rollupGasData; + } + /** * Return the list of transaction hashes extracted from the collection of Transaction passed as * argument @@ -1071,7 +1081,7 @@ public Transaction detachedCopy() { : Optional.of( blobsWithCommitmentsDetachedCopy( blobsWithCommitments.get(), detachedVersionedHashes.get())); - + final Bytes copiedPayload = payload.copy(); return new Transaction( true, transactionType, @@ -1084,7 +1094,7 @@ public Transaction detachedCopy() { detachedTo, value, signature, - payload.copy(), + copiedPayload, detachedAccessList, sender, chainId, @@ -1092,7 +1102,8 @@ public Transaction detachedCopy() { detachedBlobsWithCommitments, sourceHash, mint, - isSystemTx); + isSystemTx, + RollupGasData.fromPayload(copiedPayload)); } private AccessListEntry accessListDetachedCopy(final AccessListEntry accessListEntry) { @@ -1296,7 +1307,8 @@ public Transaction build() { Optional.ofNullable(blobsWithCommitments), Optional.ofNullable(sourceHash), Optional.ofNullable(mint), - Optional.ofNullable(isSystemTx)); + Optional.ofNullable(isSystemTx), + RollupGasData.fromPayload(payload)); } public Transaction signAndBuild(final KeyPair keys) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java index b7419b1572d..e295c531707 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java @@ -57,6 +57,9 @@ public class TransactionReceipt implements org.hyperledger.besu.plugin.data.Tran private final TransactionReceiptType transactionReceiptType; private final Optional revertReason; + private final Optional depositNonce; + private final Optional depositReceptVersion; + /** * Creates an instance of a state root-encoded transaction receipt. * @@ -77,24 +80,9 @@ public TransactionReceipt( cumulativeGasUsed, logs, LogsBloomFilter.builder().insertLogs(logs).build(), - revertReason); - } - - private TransactionReceipt( - final TransactionType transactionType, - final Hash stateRoot, - final long cumulativeGasUsed, - final List logs, - final LogsBloomFilter bloomFilter, - final Optional revertReason) { - this( - transactionType, - stateRoot, - NONEXISTENT, - cumulativeGasUsed, - logs, - bloomFilter, - revertReason); + revertReason, + Optional.empty(), + Optional.empty()); } /** @@ -117,7 +105,9 @@ public TransactionReceipt( cumulativeGasUsed, logs, LogsBloomFilter.builder().insertLogs(logs).build(), - revertReason); + revertReason, + Optional.empty(), + Optional.empty()); } public TransactionReceipt( @@ -127,7 +117,16 @@ public TransactionReceipt( final List logs, final LogsBloomFilter bloomFilter, final Optional revertReason) { - this(transactionType, null, status, cumulativeGasUsed, logs, bloomFilter, revertReason); + this( + transactionType, + null, + status, + cumulativeGasUsed, + logs, + bloomFilter, + revertReason, + Optional.empty(), + Optional.empty()); } public TransactionReceipt( @@ -145,6 +144,26 @@ public TransactionReceipt( maybeRevertReason); } + public TransactionReceipt( + final TransactionType transactionType, + final int status, + final long cumulativeGasUsed, + final List logs, + final Optional maybeRevertReason, + final Optional depositNonce, + final Optional depositReceptVersion) { + this( + transactionType, + null, + status, + cumulativeGasUsed, + logs, + LogsBloomFilter.builder().insertLogs(logs).build(), + maybeRevertReason, + depositNonce, + depositReceptVersion); + } + private TransactionReceipt( final TransactionType transactionType, final Hash stateRoot, @@ -152,7 +171,9 @@ private TransactionReceipt( final long cumulativeGasUsed, final List logs, final LogsBloomFilter bloomFilter, - final Optional revertReason) { + final Optional revertReason, + final Optional depositNonce, + final Optional depositReceptVersion) { this.transactionType = transactionType; this.stateRoot = stateRoot; this.cumulativeGasUsed = cumulativeGasUsed; @@ -161,6 +182,8 @@ private TransactionReceipt( this.bloomFilter = bloomFilter; this.transactionReceiptType = stateRoot == null ? TransactionReceiptType.STATUS : TransactionReceiptType.ROOT; + this.depositNonce = depositNonce; + this.depositReceptVersion = depositReceptVersion; this.revertReason = revertReason; } @@ -202,6 +225,12 @@ public void writeToForReceiptTrie(final RLPOutput rlpOutput, final boolean withR rlpOutput.writeLongScalar(cumulativeGasUsed); rlpOutput.writeBytes(bloomFilter); rlpOutput.writeList(logs, Log::writeTo); + + if (transactionType.equals(TransactionType.OPTIMISM_DEPOSIT)) { + depositNonce.ifPresentOrElse(rlpOutput::writeLongScalar, rlpOutput::writeNull); + depositReceptVersion.ifPresentOrElse(rlpOutput::writeLongScalar, rlpOutput::writeNull); + } + if (withRevertReason && revertReason.isPresent()) { rlpOutput.writeBytes(revertReason.get()); } @@ -245,10 +274,16 @@ public static TransactionReceipt readFrom( final LogsBloomFilter bloomFilter = LogsBloomFilter.readFrom(input); final List logs = input.readList(Log::readFrom); final Optional revertReason; + Optional depositNonce = Optional.empty(); + Optional depositReceptVersion = Optional.empty(); if (input.isEndOfCurrentList()) { revertReason = Optional.empty(); } else { - if (!revertReasonAllowed) { + if (transactionType.equals(TransactionType.OPTIMISM_DEPOSIT)) { + depositNonce = Optional.of(input.readBytes()).map(Bytes::toLong); + depositReceptVersion = Optional.of(input.readBytes()).map(Bytes::toLong); + } + if (revertReasonAllowed) { throw new RLPException("Unexpected value at end of TransactionReceipt"); } revertReason = Optional.of(input.readBytes()); @@ -260,12 +295,28 @@ public static TransactionReceipt readFrom( final int status = firstElement.readIntScalar(); input.leaveList(); return new TransactionReceipt( - transactionType, status, cumulativeGas, logs, bloomFilter, revertReason); + transactionType, + null, + status, + cumulativeGas, + logs, + bloomFilter, + revertReason, + depositNonce, + depositReceptVersion); } else { final Hash stateRoot = Hash.wrap(firstElement.readBytes32()); input.leaveList(); return new TransactionReceipt( - transactionType, stateRoot, cumulativeGas, logs, bloomFilter, revertReason); + transactionType, + stateRoot, + 0, + cumulativeGas, + logs, + bloomFilter, + revertReason, + depositNonce, + depositReceptVersion); } } @@ -336,6 +387,14 @@ public Optional getRevertReason() { return revertReason; } + public Optional getDepositNonce() { + return depositNonce; + } + + public Optional getDepositReceptVersion() { + return depositReceptVersion; + } + @Override public boolean equals(final Object obj) { if (obj == this) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 103fb3782d1..88e322b036c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -16,6 +16,7 @@ import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; @@ -35,6 +36,8 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -72,6 +75,8 @@ TransactionReceipt create( protected final boolean skipZeroBlockRewards; private final ProtocolSchedule protocolSchedule; + private final Optional genesisOptions; + protected final MiningBeneficiaryCalculator miningBeneficiaryCalculator; protected AbstractBlockProcessor( @@ -80,13 +85,15 @@ protected AbstractBlockProcessor( final Wei blockReward, final MiningBeneficiaryCalculator miningBeneficiaryCalculator, final boolean skipZeroBlockRewards, - final ProtocolSchedule protocolSchedule) { + final ProtocolSchedule protocolSchedule, + final Optional genesisOptions) { this.transactionProcessor = transactionProcessor; this.transactionReceiptFactory = transactionReceiptFactory; this.blockReward = blockReward; this.miningBeneficiaryCalculator = miningBeneficiaryCalculator; this.skipZeroBlockRewards = skipZeroBlockRewards; this.protocolSchedule = protocolSchedule; + this.genesisOptions = genesisOptions; } @Override @@ -114,6 +121,16 @@ public BlockProcessingResult processBlock( if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) { return new BlockProcessingResult(Optional.empty(), "provided gas insufficient"); } + transaction + .getMint() + .ifPresent( + mint -> { + WorldUpdater mintUpdater = worldState.updater(); + final MutableAccount sender = + mintUpdater.getOrCreateSenderAccount(transaction.getSender()); + sender.incrementBalance(transaction.getMint().orElse(Wei.ZERO)); + mintUpdater.commit(); + }); final WorldUpdater worldStateUpdater = worldState.updater(); @@ -134,6 +151,9 @@ public BlockProcessingResult processBlock( calculateExcessBlobGasForParent(protocolSpec, parentHeader))) .orElse(Wei.ZERO); + Account sender = worldState.get(transaction.getSender()); + long nonce = sender.getNonce(); + final TransactionProcessingResult result = transactionProcessor.processTransaction( blockchain, @@ -163,10 +183,29 @@ public BlockProcessingResult processBlock( worldStateUpdater.commit(); currentGasUsed += transaction.getGasLimit() - result.getGasRemaining(); - final TransactionReceipt transactionReceipt = - transactionReceiptFactory.create( - transaction.getType(), result, worldState, currentGasUsed); - receipts.add(transactionReceipt); + TransactionReceipt receipt; + if (!TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType())) { + receipt = + transactionReceiptFactory.create( + transaction.getType(), result, worldState, currentGasUsed); + } else { + GenesisConfigOptions options = genesisOptions.orElseThrow(); + Optional depositNonce = + options.isRegolith(blockHeader.getTimestamp()) ? Optional.of(nonce) : Optional.empty(); + + Optional canyonDepositReceiptVer = + options.isCanyon(blockHeader.getTimestamp()) ? Optional.of(1L) : Optional.empty(); + receipt = + new TransactionReceipt( + transaction.getType(), + result.isSuccessful() ? 1 : 0, + currentGasUsed, + result.getLogs(), + Optional.empty(), + depositNonce, + canyonDepositReceiptVer); + } + receipts.add(receipt); } final Optional maybeWithdrawalsProcessor = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicBlockProcessor.java index 044017b8728..3d1a2c636aa 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicBlockProcessor.java @@ -22,6 +22,7 @@ import java.math.BigInteger; import java.util.List; +import java.util.Optional; import java.util.OptionalLong; import org.slf4j.Logger; @@ -49,7 +50,8 @@ public ClassicBlockProcessor( blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule); + protocolSchedule, + Optional.empty()); eraLength = eraLen.orElse(DEFAULT_ERA_LENGTH); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java index bc460811ae0..d351ffc22ee 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java @@ -106,7 +106,8 @@ public static ProtocolSpecBuilder gothamDefinition( blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule) -> + protocolSchedule, + genesisConfigOptions) -> new ClassicBlockProcessor( transactionProcessor, transactionReceiptFactory, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/L1CostCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/L1CostCalculator.java new file mode 100644 index 00000000000..13e4cfa9897 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/L1CostCalculator.java @@ -0,0 +1,114 @@ +/* + * 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.mainnet; + +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.RollupGasData; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.units.bigints.UInt256; + +/** L1 Cost calculator. */ +public class L1CostCalculator { + + private static final long TX_DATA_ZERO_COST = 4L; + private static final long TX_DATA_NON_ZERO_GAS_EIP2028_COST = 16L; + private static final long TX_DATA_NON_ZERO_GAS_FRONTIER_COST = 68L; + + private static final Address l1BlockAddr = + Address.fromHexString("0x4200000000000000000000000000000000000015"); + private static final UInt256 l1BaseFeeSlot = UInt256.valueOf(1L); + private static final UInt256 overheadSlot = UInt256.valueOf(5L); + private static final UInt256 scalarSlot = UInt256.valueOf(6L); + private long cachedBlock; + private UInt256 l1FBaseFee; + private UInt256 overhead; + private UInt256 scalar; + + public L1CostCalculator() { + this.cachedBlock = 0L; + this.l1FBaseFee = UInt256.ZERO; + this.overhead = UInt256.ZERO; + this.scalar = UInt256.ZERO; + } + + /** + * Calculates the l1 cost of transaction. + * + * @param options genesis config options + * @param blockHeader block header info + * @param transaction transaction is checked + * @param worldState instance of the WorldState + * @return l1 costed gas + */ + public long l1Cost( + final GenesisConfigOptions options, + final ProcessableBlockHeader blockHeader, + final Transaction transaction, + final WorldUpdater worldState) { + long gas = 0; + boolean isRegolith = + options.isOptimism() + && options.getRegolithTime().orElseThrow() > blockHeader.getTimestamp(); + + gas += calculateRollupDataGasCost(transaction.getRollupGasData(), isRegolith); + + boolean isOptimism = options.isOptimism(); + boolean isDepositTx = TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType()); + if (!isOptimism || isDepositTx || gas == 0) { + return 0L; + } + if (blockHeader.getNumber() != cachedBlock) { + MutableAccount systemConfig = worldState.getOrCreate(l1BlockAddr); + l1FBaseFee = systemConfig.getStorageValue(l1BaseFeeSlot); + overhead = systemConfig.getStorageValue(overheadSlot); + scalar = systemConfig.getStorageValue(scalarSlot); + cachedBlock = blockHeader.getNumber(); + } + UInt256 l1GasUsed = + UInt256.valueOf(gas) + .add(overhead) + .multiply(l1FBaseFee) + .multiply(scalar) + .divide(UInt256.valueOf(1_000_000L)); + + return l1GasUsed.toLong(); + } + + /** + * calculates rollup data gas cost. + * + * @param rollupGasData rollup gas data record + * @param isRegolith flag that transaction time is bigger than regolith time + * @return the transaction gas value + */ + public static long calculateRollupDataGasCost( + final RollupGasData rollupGasData, final boolean isRegolith) { + var gas = rollupGasData.getZeroes() * TX_DATA_ZERO_COST; + if (isRegolith) { + gas += rollupGasData.getOnes() * TX_DATA_NON_ZERO_GAS_EIP2028_COST; + } else { + gas += + (rollupGasData.getOnes() + TX_DATA_NON_ZERO_GAS_FRONTIER_COST) + * TX_DATA_NON_ZERO_GAS_EIP2028_COST; + } + return gas; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessor.java index 9f2096f63d2..a19ac318dd1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessor.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.mainnet; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -22,6 +23,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.List; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,14 +38,16 @@ public MainnetBlockProcessor( final Wei blockReward, final MiningBeneficiaryCalculator miningBeneficiaryCalculator, final boolean skipZeroBlockRewards, - final ProtocolSchedule protocolSchedule) { + final ProtocolSchedule protocolSchedule, + final Optional genesisConfigOptions) { super( transactionProcessor, transactionReceiptFactory, blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule); + protocolSchedule, + genesisConfigOptions); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 8aa0a7ee29a..6719b632e3b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -217,7 +217,8 @@ public static ProtocolSpecBuilder daoRecoveryInitDefinition( blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule) -> + protocolSchedule, + genesisConfigOptions) -> new DaoBlockProcessor( new MainnetBlockProcessor( transactionProcessor, @@ -225,7 +226,8 @@ public static ProtocolSpecBuilder daoRecoveryInitDefinition( blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule))) + protocolSchedule, + genesisConfigOptions))) .name("DaoRecoveryInit"); } @@ -483,7 +485,9 @@ static ProtocolSpecBuilder londonDefinition( false, stackSizeLimit, feeMarket, - CoinbaseFeePriceCalculator.eip1559())) + CoinbaseFeePriceCalculator.eip1559(), + Optional.of(genesisConfigOptions), + Optional.of(new L1CostCalculator()))) .contractCreationProcessorBuilder( (gasCalculator, evm) -> new ContractCreationProcessor( @@ -506,6 +510,7 @@ static ProtocolSpecBuilder londonDefinition( MainnetBlockHeaderValidator.createBaseFeeMarketOmmerValidator( (BaseFeeMarket) feeMarket)) .blockBodyValidatorBuilder(BaseFeeBlockBodyValidator::new) + .genesisConfigOptions(Optional.of(genesisConfigOptions)) .name("London"); } @@ -612,7 +617,9 @@ static ProtocolSpecBuilder shanghaiDefinition( true, stackSizeLimit, feeMarket, - CoinbaseFeePriceCalculator.eip1559())) + CoinbaseFeePriceCalculator.eip1559(), + Optional.of(genesisConfigOptions), + Optional.of(new L1CostCalculator()))) // Contract creation rules for EIP-3860 Limit and meter intitcode .transactionValidatorFactoryBuilder( (gasCalculator, gasLimitCalculator, feeMarket) -> @@ -627,9 +634,11 @@ static ProtocolSpecBuilder shanghaiDefinition( TransactionType.ACCESS_LIST, TransactionType.EIP1559, TransactionType.OPTIMISM_DEPOSIT), - SHANGHAI_INIT_CODE_SIZE_LIMIT)) + SHANGHAI_INIT_CODE_SIZE_LIMIT, + genesisConfigOptions)) .withdrawalsProcessor(new WithdrawalsProcessor()) .withdrawalsValidator(new WithdrawalsValidator.AllowedWithdrawals()) + .genesisConfigOptions(Optional.of(genesisConfigOptions)) .name("Shanghai"); } @@ -703,9 +712,76 @@ static ProtocolSpecBuilder cancunDefinition( SHANGHAI_INIT_CODE_SIZE_LIMIT)) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::cancun) .blockHeaderValidatorBuilder(MainnetBlockHeaderValidator::cancunBlockHeaderValidator) + .genesisConfigOptions(Optional.of(genesisConfigOptions)) .name("Cancun"); } + static ProtocolSpecBuilder canyonDefinition( + final Optional chainId, + final OptionalInt configContractSizeLimit, + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final EvmConfiguration evmConfiguration) { + + // extra variables need to support flipping the warm coinbase flag. + final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + + return parisDefinition( + chainId, + configContractSizeLimit, + configStackSizeLimit, + enableRevertReason, + genesisConfigOptions, + evmConfiguration) + // gas calculator has new code to support EIP-3860 limit and meter initcode + .gasCalculator(ShanghaiGasCalculator::new) + // EVM has a new operation for EIP-3855 PUSH0 instruction + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.shanghai( + gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + // we need to flip the Warm Coinbase flag for EIP-3651 warm coinbase + .transactionProcessorBuilder( + (gasCalculator, + feeMarket, + transactionValidatorFactory, + contractCreationProcessor, + messageCallProcessor) -> + new MainnetTransactionProcessor( + gasCalculator, + transactionValidatorFactory, + contractCreationProcessor, + messageCallProcessor, + true, + true, + stackSizeLimit, + feeMarket, + CoinbaseFeePriceCalculator.eip1559(), + Optional.of(genesisConfigOptions), + Optional.of(new L1CostCalculator()))) + // Contract creation rules for EIP-3860 Limit and meter intitcode + .transactionValidatorFactoryBuilder( + (gasCalculator, gasLimitCalculator, feeMarket) -> + new TransactionValidatorFactory( + gasCalculator, + gasLimitCalculator, + feeMarket, + true, + chainId, + Set.of( + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559, + TransactionType.OPTIMISM_DEPOSIT), + SHANGHAI_INIT_CODE_SIZE_LIMIT, + genesisConfigOptions)) + .withdrawalsProcessor(new WithdrawalsProcessor()) + .withdrawalsValidator(new WithdrawalsValidator.AllowedWithdrawals()) + .genesisConfigOptions(Optional.of(genesisConfigOptions)) + .name("Canyon"); + } + static ProtocolSpecBuilder futureEipsDefinition( final Optional chainId, final OptionalInt configContractSizeLimit, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index b592a7b9172..2dc9350c83b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -19,8 +19,10 @@ import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH; +import org.hyperledger.besu.config.GenesisConfigOptions; 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.chain.Blockchain; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; @@ -42,6 +44,7 @@ import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import java.nio.charset.StandardCharsets; import java.util.Deque; import java.util.HashSet; import java.util.List; @@ -77,6 +80,10 @@ public class MainnetTransactionProcessor { protected final FeeMarket feeMarket; private final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator; + private final Optional genesisConfigOptions; + + private final Optional l1CostCalculator; + public MainnetTransactionProcessor( final GasCalculator gasCalculator, final TransactionValidatorFactory transactionValidatorFactory, @@ -96,6 +103,33 @@ public MainnetTransactionProcessor( this.maxStackSize = maxStackSize; this.feeMarket = feeMarket; this.coinbaseFeePriceCalculator = coinbaseFeePriceCalculator; + this.genesisConfigOptions = Optional.empty(); + this.l1CostCalculator = Optional.empty(); + } + + public MainnetTransactionProcessor( + final GasCalculator gasCalculator, + final TransactionValidatorFactory transactionValidatorFactory, + final AbstractMessageProcessor contractCreationProcessor, + final AbstractMessageProcessor messageCallProcessor, + final boolean clearEmptyAccounts, + final boolean warmCoinbase, + final int maxStackSize, + final FeeMarket feeMarket, + final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator, + final Optional genesisConfigOptions, + final Optional l1CostCalculator) { + this.gasCalculator = gasCalculator; + this.transactionValidatorFactory = transactionValidatorFactory; + this.contractCreationProcessor = contractCreationProcessor; + this.messageCallProcessor = messageCallProcessor; + this.clearEmptyAccounts = clearEmptyAccounts; + this.warmCoinbase = warmCoinbase; + this.maxStackSize = maxStackSize; + this.feeMarket = feeMarket; + this.coinbaseFeePriceCalculator = coinbaseFeePriceCalculator; + this.genesisConfigOptions = genesisConfigOptions; + this.l1CostCalculator = l1CostCalculator; } /** @@ -273,7 +307,10 @@ public TransactionProcessingResult processTransaction( LOG.trace("Starting execution of {}", transaction); ValidationResult validationResult = transactionValidator.validate( - transaction, 1, blockHeader.getBaseFee(), transactionValidationParams); + transaction, + blockHeader.getTimestamp(), + blockHeader.getBaseFee(), + transactionValidationParams); // Make sure the transaction is intrinsically valid before trying to // compare against a sender account (because the transaction may not // be signed correctly to extract the sender). @@ -300,20 +337,42 @@ public TransactionProcessingResult processTransaction( previousNonce, sender.getNonce()); - final Wei transactionGasPrice = - feeMarket.getTransactionPriceCalculator().price(transaction, blockHeader.getBaseFee()); + Wei transactionGasPrice; + if (!TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType())) { + transactionGasPrice = + feeMarket.getTransactionPriceCalculator().price(transaction, blockHeader.getBaseFee()); - final long blobGas = gasCalculator.blobGasCost(transaction.getBlobCount()); + final long blobGas = gasCalculator.blobGasCost(transaction.getBlobCount()); - final Wei upfrontGasCost = - transaction.getUpfrontGasCost(transactionGasPrice, blobGasPrice, blobGas); - final Wei previousBalance = sender.decrementBalance(upfrontGasCost); - LOG.trace( - "Deducted sender {} upfront gas cost {} ({} -> {})", - senderAddress, - upfrontGasCost, - previousBalance, - sender.getBalance()); + final Wei upfrontGasCost = + transaction.getUpfrontGasCost(transactionGasPrice, blobGasPrice, blobGas); + final Wei previousBalance = sender.decrementBalance(upfrontGasCost); + LOG.trace( + "Deducted sender {} upfront gas cost {} ({} -> {})", + senderAddress, + upfrontGasCost, + previousBalance, + sender.getBalance()); + } else { + transactionGasPrice = Wei.ZERO; + } + + genesisConfigOptions.ifPresent( + options -> { + if (!options.isOptimism() + || !options.isRegolith(blockHeader.getTimestamp()) + || TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType())) { + return; + } + final long l1Cost = + l1CostCalculator + .map( + l1CostCalculator -> + l1CostCalculator.l1Cost( + genesisConfigOptions.get(), blockHeader, transaction, worldState)) + .orElse(0L); + sender.decrementBalance(Wei.of(l1Cost)); + }); final List accessListEntries = transaction.getAccessList().orElse(List.of()); // we need to keep a separate hash set of addresses in case they specify no storage. @@ -372,7 +431,10 @@ public TransactionProcessingResult processTransaction( .blockHashLookup(blockHashLookup) .contextVariables(contextVariablesBuilder.build()) .accessListWarmAddresses(addressList) - .accessListWarmStorage(storageList); + .accessListWarmStorage(storageList) + .isDepositTx(TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType())) + .isSystemTx(transaction.getIsSystemTx().orElse(false)) + .mint(transaction.getMint()); if (transaction.getVersionedHashes().isPresent()) { commonMessageFrameBuilder.versionedHashes( @@ -433,6 +495,30 @@ public TransactionProcessingResult processTransaction( gasAvailable - initialFrame.getRemainingGas()); } + final long gasUsedByTransaction = transaction.getGasLimit() - initialFrame.getRemainingGas(); + + boolean isRegolith = + genesisConfigOptions + .map(options -> options.isRegolith(blockHeader.getTimestamp())) + .orElse(false); + + // if deposit: skip refunds, skip tipping coinbase + // Regolith changes this behaviour to report the actual gasUsed instead of always reporting + // all gas used. + if (initialFrame.isDepositTx() && !isRegolith) { + var gasUsed = transaction.getGasLimit(); + if (initialFrame.isSystemTx()) { + gasUsed = 0L; + } + if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + return TransactionProcessingResult.successful( + initialFrame.getLogs(), gasUsed, 0L, initialFrame.getOutputData(), validationResult); + } else { + return TransactionProcessingResult.failed( + gasUsed, 0L, validationResult, initialFrame.getRevertReason()); + } + } + // Refund the sender by what we should and pay the miner fee (note that we're doing them one // after the other so that if it is the same account somehow, we end up with the right result) final long selfDestructRefund = @@ -449,7 +535,21 @@ public TransactionProcessingResult processTransaction( .addArgument(balancePriorToRefund) .addArgument(sender.getBalance()) .log(); - final long gasUsedByTransaction = transaction.getGasLimit() - initialFrame.getRemainingGas(); + + // Skip coinbase payments for deposit tx in Regolith + if (initialFrame.isDepositTx() && isRegolith) { + if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + return TransactionProcessingResult.successful( + initialFrame.getLogs(), + gasUsedByTransaction, + refundedGas, + initialFrame.getOutputData(), + validationResult); + } else { + return TransactionProcessingResult.failed( + gasUsedByTransaction, refundedGas, validationResult, initialFrame.getRevertReason()); + } + } // update the coinbase final var coinbase = worldState.getOrCreate(miningBeneficiary); @@ -482,6 +582,30 @@ public TransactionProcessingResult processTransaction( worldState.clearAccountsThatAreEmpty(); } + // Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock + // blocks (these are pre-bedrock, but don't follow l2 geth rules) + // Note optimismConfig will not be nil if rules.IsOptimismBedrock is true + genesisConfigOptions.ifPresent( + options -> { + if (!options.isBedrockBlock(blockHeader.getNumber())) { + return; + } + MutableAccount opBaseFeeRecipient = + worldState.getOrCreate( + Address.fromHexString("0x4200000000000000000000000000000000000019")); + opBaseFeeRecipient.incrementBalance( + blockHeader.getBaseFee().get().multiply(gasUsedByTransaction)); + + l1CostCalculator.ifPresent( + costCal -> { + final long l1Cost = costCal.l1Cost(options, blockHeader, transaction, worldState); + MutableAccount opL1FeeRecipient = + worldState.getOrCreate( + Address.fromHexString("0x420000000000000000000000000000000000001A")); + opL1FeeRecipient.incrementBalance(Wei.of(l1Cost)); + }); + }); + if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { return TransactionProcessingResult.successful( initialFrame.getLogs(), @@ -497,6 +621,21 @@ public TransactionProcessingResult processTransaction( // need to throw to trigger the heal throw re; } catch (final RuntimeException re) { + if (TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType())) { + worldState.revert(); + worldState.getAccount(transaction.getSender()).incrementNonce(); + var gasUsed = transaction.getGasLimit(); + if (transaction.getIsSystemTx().get() + && genesisConfigOptions.get().isRegolith(blockHeader.getTimestamp())) { + gasUsed = 0L; + } + final String msg = String.format("failed deposit: %s", re); + return TransactionProcessingResult.failed( + gasUsed, + 0L, + ValidationResult.valid(), + Optional.of(Bytes.wrap(msg.getBytes(StandardCharsets.UTF_8)))); + } LOG.error("Critical Exception Processing Transaction", re); return TransactionProcessingResult.invalid( ValidationResult.invalid( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 466c7855271..1fb9abf225a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -108,10 +108,12 @@ public ValidationResult validate( final long blockTimestamp, final Optional baseFee, final TransactionValidationParams transactionValidationParams) { - final ValidationResult signatureResult = - validateTransactionSignature(transaction); - if (transaction.getType() != TransactionType.OPTIMISM_DEPOSIT && !signatureResult.isValid()) { - return signatureResult; + if (transaction.getType() != TransactionType.OPTIMISM_DEPOSIT) { + final ValidationResult signatureResult = + validateTransactionSignature(transaction); + if (!signatureResult.isValid()) { + return signatureResult; + } } if (transaction.getType().supportsBlob()) { @@ -228,6 +230,9 @@ public ValidationResult validateForSender( final Transaction transaction, final Account sender, final TransactionValidationParams validationParams) { + if (TransactionType.OPTIMISM_DEPOSIT.equals(transaction.getType())) { + return ValidationResult.valid(); + } Wei senderBalance = Account.DEFAULT_BALANCE; long senderNonce = Account.DEFAULT_NONCE; Hash codeHash = Hash.EMPTY; @@ -238,6 +243,8 @@ public ValidationResult validateForSender( if (sender.getCodeHash() != null) codeHash = sender.getCodeHash(); } + // todo calculate gas cost with l1 gas cost + final Wei upfrontCost = transaction.getUpfrontCost(gasCalculator.blobGasCost(transaction.getBlobCount())); if (upfrontCost.compareTo(senderBalance) > 0) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java index 786fdd35976..7a2cf0521a4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java @@ -275,6 +275,8 @@ private long validateForkOrder( private TreeMap buildMilestoneMap( final MainnetProtocolSpecFactory specFactory) { + Stream> milestones = createMilestones(specFactory); + milestones.close(); return createMilestones(specFactory) .flatMap(Optional::stream) .collect( @@ -287,6 +289,12 @@ private TreeMap buildMilestoneMap( private Stream> createMilestones( final MainnetProtocolSpecFactory specFactory) { + if (config.isOptimism()) { + return Stream.of( + blockNumberMilestone(config.getBedrockBlock(), specFactory.londonDefinition(config)), + timestampMilestone(config.getRegolithTime(), specFactory.londonDefinition(config)), + timestampMilestone(config.getCanyonTime(), specFactory.shanghaiDefinition(config))); + } return Stream.of( blockNumberMilestone(OptionalLong.of(0), specFactory.frontierDefinition()), blockNumberMilestone(config.getHomesteadBlockNumber(), specFactory.homesteadDefinition()), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index 7b39ebe2f5f..351570174a1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -19,6 +19,7 @@ import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY; import static org.hyperledger.besu.ethereum.core.PrivacyParameters.PLUGIN_PRIVACY; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.GasLimitCalculator; @@ -81,6 +82,7 @@ public class ProtocolSpecBuilder { private PoWHasher powHasher = PoWHasher.ETHASH_LIGHT; private boolean isPoS = false; private boolean isReplayProtectionSupported = false; + private Optional genesisConfigOptions = Optional.empty(); public ProtocolSpecBuilder gasCalculator(final Supplier gasCalculatorBuilder) { this.gasCalculatorBuilder = gasCalculatorBuilder; @@ -276,6 +278,11 @@ public ProtocolSpecBuilder isReplayProtectionSupported( return this; } + public ProtocolSpecBuilder genesisConfigOptions(final Optional options) { + this.genesisConfigOptions = options; + return this; + } + public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { checkNotNull(gasCalculatorBuilder, "Missing gasCalculator"); checkNotNull(gasLimitCalculatorBuilder, "Missing gasLimitCalculatorBuilder"); @@ -333,7 +340,8 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { final BlockBodyValidator blockBodyValidator = blockBodyValidatorBuilder.apply(protocolSchedule); - BlockProcessor blockProcessor = createBlockProcessor(transactionProcessor, protocolSchedule); + BlockProcessor blockProcessor = + createBlockProcessor(transactionProcessor, protocolSchedule, genesisConfigOptions); // Set private Tx Processor PrivateTransactionProcessor privateTransactionProcessor = createPrivateTransactionProcessor( @@ -426,14 +434,16 @@ private PrivateTransactionProcessor createPrivateTransactionProcessor( private BlockProcessor createBlockProcessor( final MainnetTransactionProcessor transactionProcessor, - final ProtocolSchedule protocolSchedule) { + final ProtocolSchedule protocolSchedule, + final Optional genesisConfigOptions) { return blockProcessorBuilder.apply( transactionProcessor, transactionReceiptFactory, blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule); + protocolSchedule, + genesisConfigOptions); } private BlockHeaderValidator createBlockHeaderValidator( @@ -472,7 +482,8 @@ BlockProcessor apply( Wei blockReward, MiningBeneficiaryCalculator miningBeneficiaryCalculator, boolean skipZeroBlockRewards, - ProtocolSchedule protocolSchedule); + ProtocolSchedule protocolSchedule, + Optional genesisConfigOptions); } public interface BlockValidatorBuilder { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java index ae238c18be4..d539dce8fde 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java @@ -103,7 +103,8 @@ public TransactionValidatorFactory( checkSignatureMalleability, chainId, acceptedTransactionTypes, - maxInitcodeSize)); + maxInitcodeSize, + Optional.of(genesisOptions))); } public void setPermissionTransactionFilter( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java index b936ed28fa3..5628093296c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; @@ -64,6 +65,7 @@ public class BlockImportExceptionHandlingTest { mock(AbstractBlockProcessor.TransactionReceiptFactory.class); private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); + private final GenesisConfigOptions genesisConfigOptions = mock(GenesisConfigOptions.class); private final BlockProcessor blockProcessor = new MainnetBlockProcessor( transactionProcessor, @@ -71,7 +73,8 @@ public class BlockImportExceptionHandlingTest { Wei.ZERO, BlockHeader::getCoinbase, true, - protocolSchedule); + protocolSchedule, + Optional.of(genesisConfigOptions)); private final BlockHeaderValidator blockHeaderValidator = mock(BlockHeaderValidator.class); private final BlockBodyValidator blockBodyValidator = mock(BlockBodyValidator.class); private final ProtocolContext protocolContext = mock(ProtocolContext.class); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java index 867bfeb7e5f..edd43b1b4cf 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java @@ -156,7 +156,8 @@ protected TestBlockProcessor( blockReward, miningBeneficiaryCalculator, skipZeroBlockRewards, - protocolSchedule); + protocolSchedule, + Optional.empty()); } @Override diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java index 1b106686bfc..c724f5cdd79 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -30,6 +31,8 @@ import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; +import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -44,6 +47,7 @@ public class MainnetBlockProcessorTest extends AbstractBlockProcessorTest { mock(AbstractBlockProcessor.TransactionReceiptFactory.class); private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); private final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + private final GenesisConfigOptions genesisConfig = mock(GenesisConfigOptions.class); @BeforeEach public void setup() { @@ -60,7 +64,8 @@ public void noAccountCreatedWhenBlockRewardIsZeroAndSkipped() { Wei.ZERO, BlockHeader::getCoinbase, true, - protocolSchedule); + protocolSchedule, + Optional.of(genesisConfig)); final MutableWorldState worldState = ReferenceTestWorldState.create(emptyMap()); final Hash initialHash = worldState.rootHash(); @@ -86,7 +91,8 @@ public void accountCreatedWhenBlockRewardIsZeroAndNotSkipped() { Wei.ZERO, BlockHeader::getCoinbase, false, - protocolSchedule); + protocolSchedule, + Optional.of(genesisConfig)); final MutableWorldState worldState = ReferenceTestWorldState.create(emptyMap()); final Hash initialHash = worldState.rootHash(); diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java index f4ad7774058..07c2172f675 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java @@ -51,7 +51,8 @@ public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { Wei.ZERO, original.getMiningBeneficiaryCalculator(), original.isSkipZeroBlockRewards(), - delegate); + delegate, + Optional.empty()); final BlockValidator noRewardBlockValidator = new MainnetBlockValidator( original.getBlockHeaderValidator(),