From 18345ede8d1ba2c778cbe325154775b263b5558b Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 13 Jun 2024 03:27:29 +1000 Subject: [PATCH 01/22] ignore flaky test (#7211) Signed-off-by: Sally MacFarlane --- .../NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java | 2 ++ ...odesSmartContractPermissioningStaticNodesAcceptanceTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java index 935db106ca5..de888904fdf 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class NodeSmartContractPermissioningOutOfSyncAcceptanceTest @@ -42,6 +43,7 @@ public void setUp() throws InterruptedException { } @Test + @Disabled("test is flaky #7108") public void addNodeToClusterAndVerifyNonBootNodePeerConnectionWorksAfterSync() { final long blockchainHeight = 25L; waitForBlockHeight(permissionedNodeA, blockchainHeight); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java index 4f516d1cda5..898f0e47ad0 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java @@ -25,8 +25,10 @@ import javax.annotation.Nonnull; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +@Disabled("flaky test #7155") public class NodesSmartContractPermissioningStaticNodesAcceptanceTest extends NodeSmartContractPermissioningAcceptanceTestBase { From 16c4f92c18462e301b38bd8c684d101729483b27 Mon Sep 17 00:00:00 2001 From: Karim Taam Date: Wed, 12 Jun 2024 20:02:03 +0200 Subject: [PATCH 02/22] correctly checks the storage in ToyAccount (#7214) Signed-off-by: Karim Taam --- .../test/java/org/hyperledger/besu/evm/toy/ToyAccount.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java index 8548a50d8ee..4dd3018a552 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java @@ -171,7 +171,10 @@ public Map getUpdatedStorage() { */ @Override public boolean isStorageEmpty() { - return storage.isEmpty(); + if (storage.isEmpty()) { + return parent == null || parent.isStorageEmpty(); + } + return false; } @Override From 8a8f1ce7125a690f530c193697c73c2a61b61280 Mon Sep 17 00:00:00 2001 From: Justin Florentine Date: Wed, 12 Jun 2024 15:36:44 -0400 Subject: [PATCH 03/22] Issue Template for Release managers (#7207) * Creates a new issue template for releases Signed-off-by: Justin Florentine * aligns release process with wiki docs Signed-off-by: Justin Florentine --------- Signed-off-by: Justin Florentine Co-authored-by: Sally MacFarlane --- .github/ISSUE_TEMPLATE/release-checklist.md | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/release-checklist.md diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md new file mode 100644 index 00000000000..f484b7a5902 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -0,0 +1,35 @@ +--- +name: Release Checklist +about: items to be completed for each release +title: '' +labels: '' +assignees: '' + +--- + +- [ ] Confirm anything outstanding for release with other maintainers on #besu-release in Discord + - [ ] Notify maintainers about updating changelog for in-flight PRs + - [ ] Update changelog if necessary, and merge a PR for it to main +- [ ] Optional: for hotfixes, create a release branch and cherry-pick, e.g. `release--hotfix` +- [ ] Optional: create a PR into main from the hotfix branch to see the CI checks pass +- [ ] On the appropriate branch/commit, create a calver tag for the release candidate, format example: `24.4.0-RC2` +- [ ] Sign-off with team; confirm tag is correct in #besu-release in Discord +- [ ] Consensys staff start burn-in using the proposed release tag +- [ ] Sign off burn-in; convey burn-in results in #besu-release in Discord +- [ ] Using the same git sha, create a calver tag for the FULL RELEASE, example format `24.4.0` +- [ ] Using the FULL RELEASE tag, create a release in github to trigger the workflows. Once published: + - makes the release "latest" in github + - this is now public and notifies subscribed users + - publishes artefacts and version-specific docker tags + - publishes the docker `latest` tag variants +- [ ] Draft homebrew PR +- [ ] Draft documentation release +- [ ] Ensure binary SHAs are correct on the release page +- [ ] Docker release startup test: + - `docker run hyperledger/besu:` + - `docker run hyperledger/besu:-arm64` + - `docker run --platform linux/amd64 hyperledger/besu:-amd64` + - `docker run --pull=always hyperledger/besu:latest` (check version is ) +- [ ] Merge homebrew PR +- [ ] Publish Docs Release +- [ ] Social announcements From 365737c2ebb726181062bfd684fd025b14eda90d Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Wed, 12 Jun 2024 14:07:58 -0600 Subject: [PATCH 04/22] Remove EIP-3074 code (#7208) Remove all EIP-3074 code from Besu. Since EIP-3074 has been replaced with EIP-7702 in Pectra, and there is no intent to schedule it for a future fork there is no need to retain the code. Signed-off-by: Danno Ferrin --- .../org/hyperledger/besu/evm/MainnetEVMs.java | 6 +- .../besu/evm/frame/MessageFrame.java | 25 +-- .../besu/evm/gascalculator/GasCalculator.java | 43 ----- .../gascalculator/PragueGasCalculator.java | 58 ------- .../besu/evm/operation/AuthCallOperation.java | 102 ------------ .../besu/evm/operation/AuthOperation.java | 118 -------------- .../PragueGasCalculatorTest.java | 56 +------ .../evm/operations/AuthOperationTest.java | 153 ------------------ .../evm/processor/AuthCallProcessorTest.java | 152 ----------------- 9 files changed, 10 insertions(+), 703 deletions(-) delete mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/AuthCallOperation.java delete mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/AuthOperation.java delete mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operations/AuthOperationTest.java delete mode 100644 evm/src/test/java/org/hyperledger/besu/evm/processor/AuthCallProcessorTest.java diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index 3afa09ee6e7..adc26009eaf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -33,8 +33,6 @@ import org.hyperledger.besu.evm.operation.AddOperation; import org.hyperledger.besu.evm.operation.AddressOperation; import org.hyperledger.besu.evm.operation.AndOperation; -import org.hyperledger.besu.evm.operation.AuthCallOperation; -import org.hyperledger.besu.evm.operation.AuthOperation; import org.hyperledger.besu.evm.operation.BalanceOperation; import org.hyperledger.besu.evm.operation.BaseFeeOperation; import org.hyperledger.besu.evm.operation.BlobBaseFeeOperation; @@ -951,9 +949,7 @@ public static void registerPragueOperations( final BigInteger chainID) { registerCancunOperations(registry, gasCalculator, chainID); - // EIP-3074 AUTH and AUTHCALL - registry.put(new AuthOperation(gasCalculator)); - registry.put(new AuthCallOperation(gasCalculator)); + // TODO add EOF operations here once PragueEOF is collapsed into Prague } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index 7ac0b4aaf40..d006c5de569 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -246,9 +246,6 @@ public enum Type { /** The mark of the undoable collections at the creation of this message frame */ private final long undoMark; - /** mutated by AUTH operation */ - private Address authorizedBy = null; - /** * Builder builder. * @@ -1373,24 +1370,6 @@ public Optional> getVersionedHashes() { return txValues.versionedHashes(); } - /** - * Accessor for address that authorized future AUTHCALLs. - * - * @return the revert reason - */ - public Address getAuthorizedBy() { - return authorizedBy; - } - - /** - * Mutator for address that authorizes future AUTHCALLs, set by AUTH opcode - * - * @param authorizedBy the address that authorizes future AUTHCALLs - */ - public void setAuthorizedBy(final Address authorizedBy) { - this.authorizedBy = authorizedBy; - } - /** Reset. */ public void reset() { maybeUpdatedMemory = Optional.empty(); @@ -1428,7 +1407,9 @@ public static class Builder { private Optional> versionedHashes = Optional.empty(); /** Instantiates a new Builder. */ - public Builder() {} + public Builder() { + // constructor added to deal with JavaDoc linting rules. + } /** * The "parent" message frame. When present some fields will be populated from the parent and diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 7a3f57c81dd..06f24c534e8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -210,35 +210,6 @@ long callOperationGasCost( Address contract, boolean accountIsWarm); - /** - * Returns the gas cost for AUTHCALL. - * - * @param frame The current frame - * @param stipend The gas stipend being provided by the CALL caller - * @param inputDataOffset The offset in memory to retrieve the CALL input data - * @param inputDataLength The CALL input data length - * @param outputDataOffset The offset in memory to place the CALL output data - * @param outputDataLength The CALL output data length - * @param transferValue The wei being transferred - * @param invoker The contract calling out on behalf of the authority - * @param invokee The address of the recipient (never null) - * @param accountIsWarm The address of the contract is "warm" as per EIP-2929 - * @return The gas cost for the CALL operation - */ - default long authCallOperationGasCost( - final MessageFrame frame, - final long stipend, - final long inputDataOffset, - final long inputDataLength, - final long outputDataOffset, - final long outputDataLength, - final Wei transferValue, - final Account invoker, - final Address invokee, - final boolean accountIsWarm) { - return 0L; - } - /** * Gets additional call stipend. * @@ -646,18 +617,4 @@ default long computeExcessBlobGas(final long parentExcessBlobGas, final int newB default long computeExcessBlobGas(final long parentExcessBlobGas, final long blobGasUsed) { return 0L; } - - /** - * Returns the gas cost of validating an auth commitment for an AUTHCALL - * - * @param frame the current frame, with memory to be read from - * @param offset start of memory read - * @param length amount of memory read - * @param authority address to check for warmup - * @return total gas cost for the operation - */ - default long authOperationGasCost( - final MessageFrame frame, final long offset, final long length, final Address authority) { - return 0L; - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java index 9dde4bdb66a..e2789bba336 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java @@ -16,11 +16,6 @@ import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.frame.MessageFrame; - /** * Gas Calculator for Prague * @@ -32,8 +27,6 @@ * */ public class PragueGasCalculator extends CancunGasCalculator { - private static final int AUTH_OP_FIXED_FEE = 3100; - private static final long AUTH_CALL_VALUE_TRANSFER_GAS_COST = 6700; /** Instantiates a new Prague Gas Calculator. */ public PragueGasCalculator() { @@ -48,55 +41,4 @@ public PragueGasCalculator() { protected PragueGasCalculator(final int maxPrecompile) { super(maxPrecompile); } - - @Override - public long authOperationGasCost( - final MessageFrame frame, final long offset, final long length, final Address authority) { - final long memoryExpansionGasCost = memoryExpansionGasCost(frame, offset, length); - final long accessFee = frame.isAddressWarm(authority) ? 100 : 2600; - final long gasCost = AUTH_OP_FIXED_FEE + memoryExpansionGasCost + accessFee; - return gasCost; - } - - /** - * Returns the gas cost to call another contract on behalf of an authority - * - * @return the gas cost to call another contract on behalf of an authority - */ - @Override - public long authCallOperationGasCost( - final MessageFrame frame, - final long stipend, - final long inputDataOffset, - final long inputDataLength, - final long outputDataOffset, - final long outputDataLength, - final Wei transferValue, - final Account invoker, - final Address invokee, - final boolean accountIsWarm) { - - final long inputDataMemoryExpansionCost = - memoryExpansionGasCost(frame, inputDataOffset, inputDataLength); - final long outputDataMemoryExpansionCost = - memoryExpansionGasCost(frame, outputDataOffset, outputDataLength); - final long memoryExpansionCost = - Math.max(inputDataMemoryExpansionCost, outputDataMemoryExpansionCost); - - final long staticGasCost = getWarmStorageReadCost(); - - long dynamicGasCost = accountIsWarm ? 0 : getColdAccountAccessCost() - getWarmStorageReadCost(); - - if (!transferValue.isZero()) { - dynamicGasCost += AUTH_CALL_VALUE_TRANSFER_GAS_COST; - } - - if ((invoker == null || invoker.isEmpty()) && !transferValue.isZero()) { - dynamicGasCost += newAccountGasCost(); - } - - long cost = staticGasCost + memoryExpansionCost + dynamicGasCost; - - return cost; - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AuthCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AuthCallOperation.java deleted file mode 100644 index 3d4fffffb3a..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AuthCallOperation.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * 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.evm.operation; - -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes32; - -/** Introduced via EIP-3074 to call another contract with a different authorization context. */ -public class AuthCallOperation extends AbstractCallOperation { - - /** - * Instantiates a new AuthCallOperation. - * - * @param gasCalculator a Prague or later gas calculator - */ - public AuthCallOperation(final GasCalculator gasCalculator) { - super(0xF7, "AUTHCALL", 7, 1, gasCalculator); - } - - @Override - protected Address to(final MessageFrame frame) { - return Words.toAddress(frame.getStackItem(1)); - } - - @Override - protected Wei value(final MessageFrame frame) { - return Wei.wrap(frame.getStackItem(2)); - } - - @Override - protected Wei apparentValue(final MessageFrame frame) { - return value(frame); - } - - @Override - protected long inputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(3)); - } - - @Override - protected long inputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(4)); - } - - @Override - protected long outputDataOffset(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(5)); - } - - @Override - protected long outputDataLength(final MessageFrame frame) { - return clampedToLong(frame.getStackItem(6)); - } - - @Override - protected Address address(final MessageFrame frame) { - return to(frame); - } - - @Override - protected Address sender(final MessageFrame frame) { - return frame.getAuthorizedBy(); - } - - @Override - public long gasAvailableForChildCall(final MessageFrame frame) { - return gasCalculator().gasAvailableForChildCall(frame, gas(frame), !value(frame).isZero()); - } - - @Override - public OperationResult execute(final MessageFrame frame, final EVM evm) { - if (frame.isStatic() && !value(frame).isZero()) { - return new OperationResult(cost(frame, true), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); - } else if (frame.getAuthorizedBy() != null) { - return super.execute(frame, evm); - } else { - frame.pushStackItem(Bytes32.ZERO); - return new OperationResult(cost(frame, true), null); - } - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AuthOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AuthOperation.java deleted file mode 100644 index 2de52ab4a54..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AuthOperation.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * 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.evm.operation; - -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; - -import org.hyperledger.besu.crypto.Hash; -import org.hyperledger.besu.crypto.SECPPublicKey; -import org.hyperledger.besu.crypto.SECPSignature; -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; - -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** The AUTH operation. */ -public class AuthOperation extends AbstractOperation { - - /** The constant MAGIC defined by EIP-3074 */ - public static final byte MAGIC = 0x4; - - private static final Logger LOG = LoggerFactory.getLogger(AuthOperation.class); - - private static final SignatureAlgorithm signatureAlgorithm = - SignatureAlgorithmFactory.getInstance(); - - /** - * Instantiates a new AuthOperation. - * - * @param gasCalculator a Prague or later gas calculator - */ - public AuthOperation(final GasCalculator gasCalculator) { - super(0xF6, "AUTH", 3, 1, gasCalculator); - } - - @Override - public OperationResult execute(final MessageFrame frame, final EVM evm) { - // create authority from stack - Address authority = Words.toAddress(frame.getStackItem(0)); - long offset = clampedToLong(frame.getStackItem(1)); - long length = clampedToLong(frame.getStackItem(2)); - - final long gasCost = - super.gasCalculator().authOperationGasCost(frame, offset, length, authority); - if (frame.getRemainingGas() < gasCost) { - return new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } - - byte yParity = frame.readMemory(offset, 1).get(0); - Bytes32 r = Bytes32.wrap(frame.readMemory(offset + 1, 32)); - Bytes32 s = Bytes32.wrap(frame.readMemory(offset + 33, 32)); - Bytes32 commit = Bytes32.wrap(frame.readMemory(offset + 65, 32)); - Bytes32 invoker = Bytes32.leftPad(frame.getContractAddress()); - // TODO add test for getting sender nonce when account does not exist - Bytes32 senderNonce = - Bytes32.leftPad( - Bytes.ofUnsignedLong( - Optional.ofNullable(frame.getWorldUpdater().getAccount(authority)) - .map(Account::getNonce) - .orElse(0L))); - if (evm.getChainId().isEmpty()) { - frame.pushStackItem(UInt256.ZERO); - LOG.error("ChainId is not set"); - return new OperationResult(0, null); - } - Bytes authPreImage = - Bytes.concatenate( - Bytes.ofUnsignedShort(MAGIC), evm.getChainId().get(), senderNonce, invoker, commit); - Bytes32 messageHash = Hash.keccak256(authPreImage); - Optional publicKey; - try { - SECPSignature signature = - signatureAlgorithm.createSignature( - r.toUnsignedBigInteger(), s.toUnsignedBigInteger(), yParity); - publicKey = signatureAlgorithm.recoverPublicKeyFromSignature(messageHash, signature); - } catch (IllegalArgumentException e) { - - frame.pushStackItem(UInt256.ZERO); - return new OperationResult(gasCost, null); - } - if (publicKey.isPresent()) { - Address signerAddress = Address.extract(publicKey.get()); - if (signerAddress.equals(authority)) { - frame.setAuthorizedBy(authority); - frame.pushStackItem(UInt256.ONE); - } else { - frame.pushStackItem(UInt256.ZERO); - } - } else { - frame.pushStackItem(UInt256.ZERO); - } - return new OperationResult(gasCost, null); - } -} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java index 72cacd47cfe..c528dab64ff 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java @@ -14,61 +14,17 @@ */ package org.hyperledger.besu.evm.gascalculator; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.Test; -public class PragueGasCalculatorTest { +class PragueGasCalculatorTest { @Test - public void testAuthOperationGasCost() { - PragueGasCalculator pragueGasCalculator = new PragueGasCalculator(); - MessageFrame runningIn = mock(MessageFrame.class); - Address authority = Address.fromHexString("0xdeadbeef"); - when(runningIn.isAddressWarm(authority)).thenReturn(true); - long gasSpent = pragueGasCalculator.authOperationGasCost(runningIn, 0, 97, authority); - assertEquals( - 3100 + 100 + pragueGasCalculator.memoryExpansionGasCost(runningIn, 0, 97), gasSpent); - } - - @Test - public void testAuthCallOperationGasCostWithTransfer() { - PragueGasCalculator pragueGasCalculator = new PragueGasCalculator(); - MessageFrame runningIn = mock(MessageFrame.class); - Account invoker = mock(MutableAccount.class); - when(invoker.getAddress()).thenReturn(Address.fromHexString("0xCafeBabe")); - Address invokee = Address.fromHexString("0xdeadbeef"); - when(runningIn.isAddressWarm(invokee)).thenReturn(true); - long gasSpentInAuthCall = - pragueGasCalculator.authCallOperationGasCost( - runningIn, 63, 0, 97, 100, 97, Wei.ONE, invoker, invokee, true); - long gasSpentInCall = - pragueGasCalculator.callOperationGasCost( - runningIn, 63, 0, 97, 100, 97, Wei.ONE, invoker, invokee, true); - assertEquals(gasSpentInCall - 2300, gasSpentInAuthCall); - } - - @Test - public void testAuthCallOperationGasCostNoTransfer() { - PragueGasCalculator pragueGasCalculator = new PragueGasCalculator(); - MessageFrame runningIn = mock(MessageFrame.class); - Account invoker = mock(MutableAccount.class); - when(invoker.getAddress()).thenReturn(Address.fromHexString("0xCafeBabe")); - Address invokee = Address.fromHexString("0xdeadbeef"); - when(runningIn.isAddressWarm(invokee)).thenReturn(true); - long gasSpentInAuthCall = - pragueGasCalculator.authCallOperationGasCost( - runningIn, 63, 0, 97, 100, 97, Wei.ZERO, invoker, invokee, true); - long gasSpentInCall = - pragueGasCalculator.callOperationGasCost( - runningIn, 63, 0, 97, 100, 97, Wei.ZERO, invoker, invokee, true); - assertEquals(gasSpentInCall, gasSpentInAuthCall); + void testPrecompileSize() { + PragueGasCalculator subject = new PragueGasCalculator(); + assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse(); + assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/AuthOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/AuthOperationTest.java deleted file mode 100644 index be76e885b24..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/AuthOperationTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * 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.evm.operations; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.crypto.Hash; -import org.hyperledger.besu.crypto.KeyPair; -import org.hyperledger.besu.crypto.SECPSignature; -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; -import org.hyperledger.besu.evm.operation.AuthOperation; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.Test; - -public class AuthOperationTest { - - @Test - public void testAuthOperation() { - SignatureAlgorithm algo = SignatureAlgorithmFactory.getInstance(); - KeyPair keys = algo.generateKeyPair(); - Address authingAddress = Address.extract(keys.getPublicKey()); - EVM fakeEVM = mock(EVM.class); - - Optional chainId = Optional.of(Bytes.of(1)); - when(fakeEVM.getChainId()).thenReturn(chainId); - long senderNonce = 0; - Address invokerAddress = Address.fromHexString("0xdeadbeef"); - Bytes32 invoker = Bytes32.leftPad(invokerAddress); - Bytes32 contractCommitment = Bytes32.leftPad(Bytes.fromHexString("0x1234")); - Bytes authPreImage = - Bytes.concatenate( - Bytes.ofUnsignedShort(AuthOperation.MAGIC), - chainId.get(), - Bytes32.leftPad(Bytes.ofUnsignedLong(senderNonce)), - invoker, - contractCommitment); - Bytes32 messageHash = Hash.keccak256(authPreImage); - SECPSignature signature = algo.sign(messageHash, keys); - - MessageFrame frame = mock(MessageFrame.class); - when(frame.getContractAddress()).thenReturn(invokerAddress); - MutableAccount authingAccount = mock(MutableAccount.class); - when(authingAccount.getAddress()).thenReturn(authingAddress); - when(authingAccount.getNonce()).thenReturn(senderNonce); - when(frame.getRemainingGas()).thenReturn(1000000L); - WorldUpdater state = mock(WorldUpdater.class); - - when(state.getAccount(authingAddress)).thenReturn(authingAccount); - - when(frame.getWorldUpdater()).thenReturn(state); - - when(frame.getSenderAddress()).thenReturn(authingAddress); - when(state.getSenderAccount(frame)).thenReturn(authingAccount); - when(frame.getStackItem(0)).thenReturn(authingAddress); - when(frame.getStackItem(1)).thenReturn(Bytes.of(0)); - when(frame.getStackItem(2)).thenReturn(Bytes.of(97)); - Bytes encodedSignature = signature.encodedBytes(); - when(frame.readMemory(0, 1)).thenReturn(encodedSignature.slice(64, 1)); - when(frame.readMemory(1, 32)).thenReturn(Bytes32.wrap(encodedSignature.slice(0, 32).toArray())); - when(frame.readMemory(33, 32)) - .thenReturn(Bytes32.wrap(encodedSignature.slice(32, 32).toArray())); - when(frame.readMemory(65, 32)).thenReturn(contractCommitment); - - AuthOperation authOperation = new AuthOperation(new PragueGasCalculator()); - authOperation.execute(frame, fakeEVM); - verify(frame).setAuthorizedBy(authingAddress); - verify(frame).pushStackItem(UInt256.ONE); - } - - @Test - public void testAuthOperationNegative() { - SignatureAlgorithm algo = SignatureAlgorithmFactory.getInstance(); - KeyPair keys = algo.generateKeyPair(); - Address authingAddress = Address.extract(keys.getPublicKey()); - EVM fakeEVM = mock(EVM.class); - - Optional chainId = Optional.of(Bytes.of(1)); - when(fakeEVM.getChainId()).thenReturn(chainId); - long senderNonce = 0; - Address invokerAddress = Address.fromHexString("0xdeadbeef"); - Bytes32 invoker = Bytes32.leftPad(invokerAddress); - Bytes32 contractCommitment = Bytes32.leftPad(Bytes.fromHexString("0x1234")); - Bytes authPreImage = - Bytes.concatenate( - Bytes.ofUnsignedShort(AuthOperation.MAGIC), - chainId.get(), - Bytes32.leftPad(Bytes.ofUnsignedLong(senderNonce)), - invoker, - contractCommitment); - Bytes32 messageHash = Hash.keccak256(authPreImage); - - // Generate a new key pair to create an incorrect signature - KeyPair wrongKeys = algo.generateKeyPair(); - SECPSignature wrongSignature = algo.sign(messageHash, wrongKeys); - - MessageFrame frame = mock(MessageFrame.class); - when(frame.getRemainingGas()).thenReturn(1000000L); - when(frame.getContractAddress()).thenReturn(invokerAddress); - MutableAccount authingAccount = mock(MutableAccount.class); - when(authingAccount.getAddress()).thenReturn(authingAddress); - when(authingAccount.getNonce()).thenReturn(senderNonce); - - WorldUpdater state = mock(WorldUpdater.class); - - when(state.getAccount(authingAddress)).thenReturn(authingAccount); - - when(frame.getWorldUpdater()).thenReturn(state); - - when(frame.getSenderAddress()).thenReturn(authingAddress); - when(state.getSenderAccount(frame)).thenReturn(authingAccount); - when(frame.getStackItem(0)).thenReturn(authingAddress); - when(frame.getStackItem(1)).thenReturn(Bytes.of(0)); - when(frame.getStackItem(2)).thenReturn(Bytes.of(97)); - Bytes encodedSignature = wrongSignature.encodedBytes(); // Use the wrong signature - when(frame.readMemory(0, 1)).thenReturn(encodedSignature.slice(64, 1)); - when(frame.readMemory(1, 32)).thenReturn(Bytes32.wrap(encodedSignature.slice(0, 32).toArray())); - when(frame.readMemory(33, 32)) - .thenReturn(Bytes32.wrap(encodedSignature.slice(32, 32).toArray())); - when(frame.readMemory(65, 32)).thenReturn(contractCommitment); - - AuthOperation authOperation = new AuthOperation(new PragueGasCalculator()); - authOperation.execute(frame, fakeEVM); - verify(frame, never()).setAuthorizedBy(authingAddress); // The address should not be authorized - verify(frame).pushStackItem(UInt256.ZERO); // The stack should contain UInt256.ZERO - } -} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/AuthCallProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/AuthCallProcessorTest.java deleted file mode 100644 index 21144a4806b..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/AuthCallProcessorTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * 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.evm.processor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import org.hyperledger.besu.crypto.Hash; -import org.hyperledger.besu.crypto.KeyPair; -import org.hyperledger.besu.crypto.SECPSignature; -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.MainnetEVMs; -import org.hyperledger.besu.evm.fluent.EVMExecutor; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.operation.AuthOperation; -import org.hyperledger.besu.evm.toy.ToyWorld; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -public class AuthCallProcessorTest extends MessageCallProcessorTest { - - MessageCallProcessor spyingMessageCallProcessor; - ArgumentCaptor frameCaptor = ArgumentCaptor.forClass(MessageFrame.class); - - WorldUpdater toyWorld = new ToyWorld(); - - @Test - public void authCallHappyPath() { - final EVM pragueEVM = - MainnetEVMs.prague(new PragueGasCalculator(), BigInteger.ONE, EvmConfiguration.DEFAULT); - final EVMExecutor executor = EVMExecutor.evm(pragueEVM); - this.spyingMessageCallProcessor = - spy(new MessageCallProcessor(pragueEVM, precompileContractRegistry)); - executor.messageCallProcessor(this.spyingMessageCallProcessor); - - executor.worldUpdater(toyWorld); - executor.gas(10_000_000_000L); - - SignatureAlgorithm algo = SignatureAlgorithmFactory.getInstance(); - KeyPair keys = algo.generateKeyPair(); - Optional chainId = Optional.of(Bytes.of(1)); - long senderNonce = 0; - Address invokerAddress = Address.fromHexString("0xdeadbeef"); - Bytes32 invoker = Bytes32.leftPad(invokerAddress); - Bytes32 contractCommitment = Bytes32.leftPad(Bytes.fromHexString("0x1234")); - Bytes authPreImage = - Bytes.concatenate( - Bytes.ofUnsignedShort(AuthOperation.MAGIC), - Bytes32.leftPad(chainId.get()), - Bytes32.leftPad(Bytes.ofUnsignedLong(senderNonce)), - invoker, - contractCommitment); - Bytes32 messageHash = Hash.keccak256(authPreImage); - SECPSignature signature = algo.sign(messageHash, keys); - Bytes encodedSignature = signature.encodedBytes(); - - Bytes authParam = - Bytes.concatenate( - encodedSignature.slice(64, 1), // y parity - encodedSignature.slice(0, 32), // r - encodedSignature.slice(32, 32), // s - contractCommitment); - - toyWorld.createAccount( - Address.extract(keys.getPublicKey()), 0, Wei.MAX_WEI); // initialize authority account - toyWorld.createAccount(invokerAddress, 0, Wei.MAX_WEI); // initialize invoker account - final Bytes codeBytes = - Bytes.fromHexString( - "0x" - + "6061" // push 97 the calldata length - + "6000" // push 0 the offset - + "6000" // push 0 the destination offset - + "37" // calldatacopy 97 bytes of the auth param to mem 0 - + "6061" // param is 97 bytes (0x61) - + "6000" // push 0 where in mem to find auth param - + "73" // push next 20 bytes for the authority address - + Address.extract(keys.getPublicKey()) - .toUnprefixedHexString() // push authority address - + "F6" // AUTH call, should work and set authorizedBy on the frame - + "6000" // push 0 for return length, we don't care about the return - + "6000" // push 0 for return offset, we don't care about the return - + "6000" // push 0 for input length - + "6000" // push 0 for input offset - + "60FF" // push 255 for the value being sent - + "73deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" // push20 the invokee address - + "60FF" // push 255 gas - + "F7"); // AUTHCALL, should work - executor.contract(invokerAddress); - executor.execute(codeBytes, authParam, Wei.ZERO, invokerAddress); - verify(this.spyingMessageCallProcessor, times(2)) - .start(frameCaptor.capture(), any()); // one for parent frame, one for child - List frames = frameCaptor.getAllValues(); - assertThat(frames.get(0).getStackItem(0)).isEqualTo((Bytes.of(1))); - } - - @Test - public void unauthorizedAuthCall() { - final EVM pragueEVM = - MainnetEVMs.prague(new PragueGasCalculator(), BigInteger.ONE, EvmConfiguration.DEFAULT); - final EVMExecutor executor = EVMExecutor.evm(pragueEVM); - this.spyingMessageCallProcessor = - spy(new MessageCallProcessor(pragueEVM, precompileContractRegistry)); - executor.messageCallProcessor(this.spyingMessageCallProcessor); - - executor.gas(10_000_000_000L); - - final Bytes codeBytes = - Bytes.fromHexString( - "0x" - + "6000" // push 0 for return length - + "6000" // push 0 for return offset - + "6000" // push 0 for input length - + "6000" // push 0 for input offset - + "60FF" // push 255 for the value being sent - + "73deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" // push20 the invokee address - + "60FF" // push 255 gas - + "F7"); // AUTHCALL without prior AUTH, should fail - - executor.execute(codeBytes, Bytes.EMPTY, Wei.ZERO, Address.ZERO); - verify(this.spyingMessageCallProcessor).start(frameCaptor.capture(), any()); - assertThat(frameCaptor.getValue().getStackItem(0)).isEqualTo(Bytes32.ZERO); - } -} From 85d286aa85a68e19522f9da99dad607895b7e11f Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Wed, 12 Jun 2024 15:09:18 -0600 Subject: [PATCH 05/22] EIP-7692 "Mega" EOF Implementation (#7169) A complete and up to date implementation of EIP-7692 EOF v.1. For genesis file activation use "PragueEOFTime", for references tests it activates as part of Prague. Signed-off-by: Danno Ferrin --- CHANGELOG.md | 1 + .../org/hyperledger/besu/cli/BesuCommand.java | 3 +- .../besu/config/GenesisConfigOptions.java | 7 + .../besu/config/JsonGenesisConfigOptions.java | 7 + .../besu/config/StubGenesisConfigOptions.java | 18 + .../besu/config/GenesisConfigOptionsTest.java | 8 + .../besu/ethereum/chain/GenesisState.java | 8 + .../mainnet/MainnetProtocolSpecFactory.java | 11 + .../mainnet/MainnetProtocolSpecs.java | 82 +- .../mainnet/MainnetTransactionProcessor.java | 14 +- .../mainnet/ProtocolScheduleBuilder.java | 2 + .../privacy/PrivateTransactionProcessor.java | 6 +- .../transaction/TransactionInvalidReason.java | 1 + .../besu/evmtool/CodeValidateSubCommand.java | 21 +- .../besu/evmtool/EOFTestSubCommand.java | 226 ++++ .../besu/evmtool/EvmToolCommand.java | 30 +- .../evmtool/MainnetGenesisFileModule.java | 3 + .../besu/evmtool/PrettyPrintSubCommand.java | 81 ++ .../besu/evmtool/StateTestSubCommand.java | 4 +- .../evmtool/benchmarks/BenchmarkExecutor.java | 7 +- .../evmtool/CodeValidationSubCommandTest.java | 50 +- .../besu/evmtool/EvmToolSpecTests.java | 6 +- .../besu/evmtool/pretty-print/rjumpv-max.json | 8 + .../besu/evmtool/pretty-print/rjumpv.json | 8 + .../evmtool/pretty-print/subcontainers.json | 8 + .../besu/evmtool/state-test/blockhash.json | 2 +- .../besu/evmtool/state-test/create-eof.json | 86 ++ .../state-test/create-invalid-eof.json | 78 ++ .../besu/evmtool/trace/create-eof.json | 25 + ethereum/referencetests/build.gradle | 42 +- .../referencetests/EOFTestCaseSpec.java | 53 + .../ReferenceTestProtocolSchedules.java | 2 +- .../StateTestVersionedTransaction.java | 2 +- .../ethereum/eof/EOFReferenceTestTools.java | 142 ++ .../vm/BlockchainReferenceTestTools.java | 15 +- .../vm/GeneralStateReferenceTestTools.java | 2 +- .../BlockchainReferenceTest.java.template | 14 +- .../templates/EOFReferenceTest.java.template | 42 + .../GeneralStateReferenceTest.java.template | 14 +- .../java/org/hyperledger/besu/evm/Code.java | 68 + .../java/org/hyperledger/besu/evm/EVM.java | 12 +- .../hyperledger/besu/evm/EvmSpecVersion.java | 16 +- .../org/hyperledger/besu/evm/MainnetEVMs.java | 331 ++++- .../besu/evm/code/CodeFactory.java | 79 +- .../besu/evm/code/CodeInvalid.java | 44 +- .../besu/evm/code/CodeSection.java | 20 +- .../org/hyperledger/besu/evm/code/CodeV0.java | 47 +- .../org/hyperledger/besu/evm/code/CodeV1.java | 99 +- .../besu/evm/code/CodeV1Validation.java | 1172 +++++++---------- .../hyperledger/besu/evm/code/EOFLayout.java | 540 +++++++- .../hyperledger/besu/evm/code/OpcodeInfo.java | 336 +++++ .../hyperledger/besu/evm/code/WorkList.java | 95 ++ .../CachedInvalidCodeRule.java | 2 +- .../EOFValidationCodeRule.java | 14 +- .../besu/evm/fluent/EVMExecutor.java | 75 +- .../besu/evm/frame/ExceptionalHaltReason.java | 36 +- .../hyperledger/besu/evm/frame/Memory.java | 5 - .../besu/evm/frame/MessageFrame.java | 114 +- .../gascalculator/FrontierGasCalculator.java | 33 +- .../besu/evm/gascalculator/GasCalculator.java | 28 + .../gascalculator/PragueEOFGasCalculator.java | 57 + .../besu/evm/internal/ReturnStack.java | 89 +- .../evm/operation/AbstractCallOperation.java | 79 +- .../operation/AbstractCreateOperation.java | 83 +- .../operation/AbstractExtCallOperation.java | 201 +++ .../besu/evm/operation/AbstractOperation.java | 2 - .../besu/evm/operation/AddModOperation.java | 2 +- .../besu/evm/operation/CallFOperation.java | 37 +- .../besu/evm/operation/Create2Operation.java | 2 +- .../besu/evm/operation/CreateOperation.java | 2 +- .../besu/evm/operation/DataCopyOperation.java | 67 + .../evm/operation/DataLoadNOperation.java | 54 + .../besu/evm/operation/DataLoadOperation.java | 52 + .../besu/evm/operation/DataSizeOperation.java | 47 + .../evm/operation/DelegateCallOperation.java | 5 + .../besu/evm/operation/DupNOperation.java | 55 + .../evm/operation/EOFCreateOperation.java | 91 ++ .../besu/evm/operation/ExchangeOperation.java | 60 + .../besu/evm/operation/ExtCallOperation.java | 69 + .../evm/operation/ExtCodeCopyOperation.java | 11 +- .../evm/operation/ExtCodeHashOperation.java | 12 +- .../evm/operation/ExtCodeSizeOperation.java | 17 +- .../operation/ExtDelegateCallOperation.java | 73 + .../evm/operation/ExtStaticCallOperation.java | 73 + .../besu/evm/operation/JumpFOperation.java | 34 +- .../operation/RelativeJumpIfOperation.java | 14 +- .../evm/operation/RelativeJumpOperation.java | 12 +- .../RelativeJumpVectorOperation.java | 31 +- .../besu/evm/operation/RetFOperation.java | 15 +- .../operation/ReturnContractOperation.java | 76 ++ .../operation/ReturnDataCopyOperation.java | 14 +- .../operation/ReturnDataLoadOperation.java | 56 + .../besu/evm/operation/StopOperation.java | 5 +- .../besu/evm/operation/SwapNOperation.java | 59 + .../besu/evm/operation/TLoadOperation.java | 3 - .../processor/AbstractMessageProcessor.java | 6 +- .../processor/ContractCreationProcessor.java | 3 +- .../besu/evm/tracing/StandardJsonTracer.java | 20 +- .../besu/evm/EOFTestConstants.java | 95 ++ .../besu/evm/code/CodeFactoryTest.java | 11 +- .../hyperledger/besu/evm/code/CodeV0Test.java | 2 +- .../hyperledger/besu/evm/code/CodeV1Test.java | 438 +++--- .../besu/evm/code/EOFLayoutTest.java | 164 ++- .../besu/evm/fluent/EVMExecutorTest.java | 6 +- .../PragueEOFGasCalculatorTest.java | 41 + .../besu/evm/internal/CodeCacheTest.java | 2 +- .../AbstractCreateOperationTest.java | 6 +- .../evm/operations/CallFOperationTest.java | 99 +- .../evm/operations/Create2OperationTest.java | 50 +- .../evm/operations/CreateOperationTest.java | 39 +- .../operations/EofCreateOperationTest.java | 160 +++ .../evm/operations/ExtCallOperationTest.java | 274 ++++ .../ExtDelegateCallOperationTest.java | 258 ++++ .../ExtStaticCallOperationTest.java | 185 +++ .../evm/operations/JumpFOperationTest.java | 74 +- .../evm/operations/JumpOperationTest.java | 10 +- .../operations/RelativeJumpOperationTest.java | 45 +- .../evm/operations/RetFOperationTest.java | 70 +- .../operations/SelfDestructOperationTest.java | 2 +- .../ContractCreationProcessorTest.java | 72 +- .../evm/testutils/OperationsTestUtils.java | 48 + .../testutils/TestMessageFrameBuilder.java | 9 +- .../besu/testutil/JsonTestParameters.java | 24 +- 123 files changed, 6178 insertions(+), 1899 deletions(-) create mode 100644 ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java create mode 100644 ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json create mode 100644 ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json create mode 100644 ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java create mode 100644 ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java create mode 100644 ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c3efddef70d..cba041d31d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Improve the selection of the most profitable built block [#7174](https://github.com/hyperledger/besu/pull/7174) - Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) - Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) +- A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) ### Bug fixes - Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 535dcc69364..b14efb2e80b 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1504,7 +1504,8 @@ private void configureNativeLibs() { } if (genesisConfigOptionsSupplier.get().getCancunTime().isPresent() - || genesisConfigOptionsSupplier.get().getPragueTime().isPresent()) { + || genesisConfigOptionsSupplier.get().getPragueTime().isPresent() + || genesisConfigOptionsSupplier.get().getPragueEOFTime().isPresent()) { if (kzgTrustedSetupFile != null) { KZGPointEvalPrecompiledContract.init(kzgTrustedSetupFile); } else { 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 1645688f098..8e47af3b753 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java @@ -249,6 +249,13 @@ default boolean isConsensusMigration() { */ OptionalLong getPragueTime(); + /** + * Gets Prague EOF time. + * + * @return the prague time + */ + OptionalLong getPragueEOFTime(); + /** * Gets future eips time. * 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 6118fc5080f..d5635d9ae37 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -298,6 +298,11 @@ public OptionalLong getPragueTime() { return getOptionalLong("praguetime"); } + @Override + public OptionalLong getPragueEOFTime() { + return getOptionalLong("pragueeoftime"); + } + @Override public OptionalLong getFutureEipsTime() { return getOptionalLong("futureeipstime"); @@ -457,6 +462,7 @@ public Map asMap() { getShanghaiTime().ifPresent(l -> builder.put("shanghaiTime", l)); getCancunTime().ifPresent(l -> builder.put("cancunTime", l)); getPragueTime().ifPresent(l -> builder.put("pragueTime", l)); + getPragueEOFTime().ifPresent(l -> builder.put("pragueEOFTime", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h.toHexString())); getFutureEipsTime().ifPresent(l -> builder.put("futureEipsTime", l)); @@ -605,6 +611,7 @@ public List getForkBlockTimestamps() { getShanghaiTime(), getCancunTime(), getPragueTime(), + getPragueEOFTime(), getFutureEipsTime(), getExperimentalEipsTime()); // when adding forks add an entry to ${REPO_ROOT}/config/src/test/resources/all_forks.json 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 e814adb4aec..32800f58a12 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -49,6 +49,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable private OptionalLong shanghaiTime = OptionalLong.empty(); private OptionalLong cancunTime = OptionalLong.empty(); private OptionalLong pragueTime = OptionalLong.empty(); + private OptionalLong pragueEOFTime = OptionalLong.empty(); private OptionalLong futureEipsTime = OptionalLong.empty(); private OptionalLong experimentalEipsTime = OptionalLong.empty(); private OptionalLong terminalBlockNumber = OptionalLong.empty(); @@ -242,6 +243,11 @@ public OptionalLong getPragueTime() { return pragueTime; } + @Override + public OptionalLong getPragueEOFTime() { + return pragueEOFTime; + } + @Override public OptionalLong getFutureEipsTime() { return futureEipsTime; @@ -635,6 +641,18 @@ public StubGenesisConfigOptions pragueTime(final long timestamp) { return this; } + /** + * PragueEOF time. + * + * @param timestamp the timestamp + * @return the stub genesis config options + */ + public StubGenesisConfigOptions pragueEOFTime(final long timestamp) { + pragueTime = OptionalLong.of(timestamp); + pragueEOFTime = pragueTime; + return this; + } + /** * Future EIPs Time block. * diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java index bb4a8f94a96..0e8138e1bc8 100644 --- a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java @@ -199,6 +199,13 @@ void shouldGetPragueTime() { assertThat(config.getPragueTime()).hasValue(1670470143); } + @Test + void shouldGetPragueEOFTime() { + final GenesisConfigOptions config = + fromConfigOptions(singletonMap("pragueEOFTime", 1670470143)); + assertThat(config.getPragueEOFTime()).hasValue(1670470143); + } + @Test void shouldGetFutureEipsTime() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("futureEipsTime", 1337)); @@ -232,6 +239,7 @@ void shouldNotReturnEmptyOptionalWhenBlockNumberNotSpecified() { assertThat(config.getShanghaiTime()).isEmpty(); assertThat(config.getCancunTime()).isEmpty(); assertThat(config.getPragueTime()).isEmpty(); + assertThat(config.getPragueEOFTime()).isEmpty(); assertThat(config.getFutureEipsTime()).isEmpty(); assertThat(config.getExperimentalEipsTime()).isEmpty(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index d42f2d39a3f..e975a01f8fe 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -312,6 +312,14 @@ private static boolean isPragueAtGenesis(final GenesisConfigFile genesis) { if (pragueTimestamp.isPresent()) { return genesis.getTimestamp() >= pragueTimestamp.getAsLong(); } + return isPragueEOFAtGenesis(genesis); + } + + private static boolean isPragueEOFAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong pragueEOFTimestamp = genesis.getConfigOptions().getPragueEOFTime(); + if (pragueEOFTimestamp.isPresent()) { + return genesis.getTimestamp() >= pragueEOFTimestamp.getAsLong(); + } return isFutureEipsTimeAtGenesis(genesis); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java index 06bc45084a7..095a85ef53f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java @@ -189,6 +189,17 @@ public ProtocolSpecBuilder pragueDefinition(final GenesisConfigOptions genesisCo miningParameters); } + public ProtocolSpecBuilder pragueEOFDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.pragueEOFDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + genesisConfigOptions, + evmConfiguration, + miningParameters); + } + /** * The "future" fork consists of EIPs that have been approved for Ethereum Mainnet but not * scheduled for a fork. This is also known as "Eligible For Inclusion" (EFI) or "Considered for 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 5c1d399264f..16fee28bb50 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 @@ -58,6 +58,7 @@ import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; @@ -735,9 +736,6 @@ static ProtocolSpecBuilder pragueDefinition( final GenesisConfigOptions genesisConfigOptions, final EvmConfiguration evmConfiguration, final MiningParameters miningParameters) { - final int contractSizeLimit = - configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); final Address depositContractAddress = genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); @@ -750,47 +748,64 @@ static ProtocolSpecBuilder pragueDefinition( genesisConfigOptions, evmConfiguration, miningParameters) - // EVM changes to support EOF EIPs (3670, 4200, 4750, 5450) + // EIP-3074 AUTH and AUTCALL gas .gasCalculator(PragueGasCalculator::new) + // EIP-3074 AUTH and AUTCALL .evmBuilder( (gasCalculator, jdCacheConfig) -> MainnetEVMs.prague( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) - // change contract call creator to accept EOF code + + // EIP-2537 BLS12-381 precompiles + .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) + + // EIP-7002 Withdrawls / EIP-6610 Deposits / EIP-7685 Requests + .requestsValidator(pragueRequestsValidator(depositContractAddress)) + // EIP-7002 Withdrawls / EIP-6610 Deposits / EIP-7685 Requests + .requestProcessorCoordinator(pragueRequestsProcessors(depositContractAddress)) + + // EIP-2935 Blockhash processor + .blockHashProcessor(new PragueBlockHashProcessor()) + .name("Prague"); + } + + static ProtocolSpecBuilder pragueEOFDefinition( + final Optional chainId, + final OptionalInt configContractSizeLimit, + final OptionalInt configStackSizeLimit, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters) { + final int contractSizeLimit = + configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); + + return pragueDefinition( + chainId, + configContractSizeLimit, + configStackSizeLimit, + enableRevertReason, + genesisConfigOptions, + evmConfiguration, + miningParameters) + // EIP-7692 EOF v1 Gas calculator + .gasCalculator(PragueEOFGasCalculator::new) + // EIP-7692 EOF v1 EVM and opcodes + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.pragueEOF( + gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + // EIP-7698 EOF v1 creation transaction .contractCreationProcessorBuilder( (gasCalculator, evm) -> new ContractCreationProcessor( gasCalculator, evm, true, - List.of( - MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), + List.of(MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) - // warm blockahsh contract - .transactionProcessorBuilder( - (gasCalculator, - feeMarket, - transactionValidator, - contractCreationProcessor, - messageCallProcessor) -> - new MainnetTransactionProcessor( - gasCalculator, - transactionValidator, - contractCreationProcessor, - messageCallProcessor, - true, - true, - stackSizeLimit, - feeMarket, - CoinbaseFeePriceCalculator.eip1559())) - - // use prague precompiled contracts - .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) - .requestsValidator(pragueRequestsValidator(depositContractAddress)) - .requestProcessorCoordinator(pragueRequestsProcessors(depositContractAddress)) - .blockHashProcessor(new PragueBlockHashProcessor()) - .name("Prague"); + .name("PragueEOF"); } static ProtocolSpecBuilder futureEipsDefinition( @@ -803,7 +818,7 @@ static ProtocolSpecBuilder futureEipsDefinition( final MiningParameters miningParameters) { final int contractSizeLimit = configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - return pragueDefinition( + return pragueEOFDefinition( chainId, configContractSizeLimit, configStackSizeLimit, @@ -823,8 +838,7 @@ static ProtocolSpecBuilder futureEipsDefinition( gasCalculator, evm, true, - List.of( - MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), + List.of(MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) // use future configured precompiled contracts 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 51d300cbc46..d982265f242 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 @@ -32,8 +32,10 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeInvalid; import org.hyperledger.besu.evm.code.CodeV0; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -382,13 +384,14 @@ public TransactionProcessingResult processTransaction( Address.contractAddress(senderAddress, sender.getNonce() - 1L); final Bytes initCodeBytes = transaction.getPayload(); + Code code = contractCreationProcessor.getCodeFromEVMForCreation(initCodeBytes); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.CONTRACT_CREATION) .address(contractAddress) .contract(contractAddress) - .inputData(Bytes.EMPTY) - .code(contractCreationProcessor.getCodeFromEVMUncached(initCodeBytes)) + .inputData(initCodeBytes.slice(code.getSize())) + .code(code) .build(); } else { @SuppressWarnings("OptionalGetWithoutIsPresent") // isContractCall tests isPresent @@ -415,12 +418,17 @@ public TransactionProcessingResult processTransaction( } else { initialFrame.setState(MessageFrame.State.EXCEPTIONAL_HALT); initialFrame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INVALID_CODE)); + validationResult = + ValidationResult.invalid( + TransactionInvalidReason.EOF_CODE_INVALID, + ((CodeInvalid) initialFrame.getCode()).getInvalidReason()); } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { worldUpdater.commit(); } else { - if (initialFrame.getExceptionalHaltReason().isPresent()) { + if (initialFrame.getExceptionalHaltReason().isPresent() + && initialFrame.getCode().isValid()) { validationResult = ValidationResult.invalid( TransactionInvalidReason.EXECUTION_HALTED, 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 2059192c534..78198922ea4 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 @@ -252,6 +252,7 @@ private void validateEthereumForkOrdering() { lastForkBlock = validateForkOrder("Shanghai", config.getShanghaiTime(), lastForkBlock); lastForkBlock = validateForkOrder("Cancun", config.getCancunTime(), lastForkBlock); lastForkBlock = validateForkOrder("Prague", config.getPragueTime(), lastForkBlock); + lastForkBlock = validateForkOrder("PragueEOF", config.getPragueEOFTime(), lastForkBlock); lastForkBlock = validateForkOrder("FutureEips", config.getFutureEipsTime(), lastForkBlock); lastForkBlock = validateForkOrder("ExperimentalEips", config.getExperimentalEipsTime(), lastForkBlock); @@ -331,6 +332,7 @@ private Stream> createMilestones( timestampMilestone(config.getShanghaiTime(), specFactory.shanghaiDefinition(config)), timestampMilestone(config.getCancunTime(), specFactory.cancunDefinition(config)), timestampMilestone(config.getPragueTime(), specFactory.pragueDefinition(config)), + timestampMilestone(config.getPragueEOFTime(), specFactory.pragueEOFDefinition(config)), timestampMilestone(config.getFutureEipsTime(), specFactory.futureEipsDefinition(config)), timestampMilestone( config.getExperimentalEipsTime(), specFactory.experimentalEipsDefinition(config)), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java index d6f615310b8..ad7de59aee7 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeV0; @@ -138,13 +139,14 @@ public TransactionProcessingResult processTransaction( privacyGroupId); final Bytes initCodeBytes = transaction.getPayload(); + Code code = contractCreationProcessor.getCodeFromEVMForCreation(initCodeBytes); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.CONTRACT_CREATION) .address(privateContractAddress) .contract(privateContractAddress) - .inputData(Bytes.EMPTY) - .code(contractCreationProcessor.getCodeFromEVMUncached(initCodeBytes)) + .inputData(initCodeBytes.slice(code.getSize())) + .code(code) .build(); } else { final Address to = transaction.getTo().get(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index 8659b9d8374..a6c2cb69b29 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -49,6 +49,7 @@ public enum TransactionInvalidReason { INVALID_BLOBS, PLUGIN_TX_POOL_VALIDATOR, EXECUTION_HALTED, + EOF_CODE_INVALID, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_INVALID, PRIVATE_TRANSACTION_FAILED, diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java index 210efe014ca..beb51cd4a6c 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java @@ -17,8 +17,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeFactory; -import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1Validation; import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.util.LogConfigurator; @@ -39,7 +40,7 @@ @CommandLine.Command( name = COMMAND_NAME, - description = "Execute an Ethereum State Test.", + description = "Validates EVM code for fuzzing", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class CodeValidateSubCommand implements Runnable { @@ -109,24 +110,26 @@ public String considerCode(final String hexCode) { } catch (RuntimeException re) { return "err: hex string -" + re + "\n"; } - if (codeBytes.size() == 0) { + if (codeBytes.isEmpty()) { return ""; } - var layout = EOFLayout.parseEOF(codeBytes); + EOFLayout layout = EOFLayout.parseEOF(codeBytes); if (!layout.isValid()) { - return "err: layout - " + layout.getInvalidReason() + "\n"; + return "err: layout - " + layout.invalidReason() + "\n"; } - var code = CodeFactory.createCode(codeBytes, 1, true); - if (!code.isValid()) { - return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n"; + String error = CodeV1Validation.validate(layout); + if (error != null) { + return "err: " + error + "\n"; } + Code code = CodeFactory.createCode(codeBytes, 1); + return "OK " + IntStream.range(0, code.getCodeSectionCount()) .mapToObj(code::getCodeSection) - .map(cs -> layout.getContainer().slice(cs.getEntryPoint(), cs.getLength())) + .map(cs -> layout.container().slice(cs.getEntryPoint(), cs.getLength())) .map(Bytes::toUnprefixedHexString) .collect(Collectors.joining(",")) + "\n"; diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java new file mode 100644 index 00000000000..cb2fbbfeb64 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java @@ -0,0 +1,226 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evmtool; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult.failed; +import static org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult.passed; +import static org.hyperledger.besu.evmtool.EOFTestSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; +import org.hyperledger.besu.evm.code.CodeV1Validation; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.util.LogConfigurator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +@CommandLine.Command( + name = COMMAND_NAME, + description = "Runs EOF validation reference tests", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class EOFTestSubCommand implements Runnable { + public static final String COMMAND_NAME = "eof-test"; + @CommandLine.ParentCommand private final EvmToolCommand parentCommand; + + // picocli does it magically + @CommandLine.Parameters private final List eofTestFiles = new ArrayList<>(); + + @CommandLine.Option( + names = {"--fork-name"}, + description = "Limit execution to one fork.") + private String forkName = null; + + @CommandLine.Option( + names = {"--test-name"}, + description = "Limit execution to one test.") + private String testVectorName = null; + + public EOFTestSubCommand() { + this(null); + } + + public EOFTestSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + // presume ethereum mainnet for reference and EOF tests + SignatureAlgorithmFactory.setDefaultInstance(); + final ObjectMapper eofTestMapper = JsonUtils.createObjectMapper(); + + final JavaType javaType = + eofTestMapper + .getTypeFactory() + .constructParametricType(Map.class, String.class, EOFTestCaseSpec.class); + try { + if (eofTestFiles.isEmpty()) { + // if no EOF tests were specified use standard input to get filenames + final BufferedReader in = + new BufferedReader(new InputStreamReader(parentCommand.in, UTF_8)); + while (true) { + final String fileName = in.readLine(); + if (fileName == null) { + // reached end of file. Stop the loop. + break; + } + final File file = new File(fileName); + if (file.isFile()) { + final Map eofTests = eofTestMapper.readValue(file, javaType); + executeEOFTest(file.toString(), eofTests); + } else { + parentCommand.out.println("File not found: " + fileName); + } + } + } else { + for (final Path eofTestFile : eofTestFiles) { + final Map eofTests; + if ("stdin".equals(eofTestFile.toString())) { + eofTests = eofTestMapper.readValue(parentCommand.in, javaType); + } else { + eofTests = eofTestMapper.readValue(eofTestFile.toFile(), javaType); + } + executeEOFTest(eofTestFile.toString(), eofTests); + } + } + } catch (final JsonProcessingException jpe) { + parentCommand.out.println("File content error: " + jpe); + } catch (final IOException e) { + System.err.println("Unable to read EOF test file"); + e.printStackTrace(System.err); + } + } + + record TestExecutionResult( + String fileName, + String group, + String name, + String fork, + boolean pass, + String expectedError, + String actualError) {} + + private void executeEOFTest(final String fileName, final Map eofTests) { + List results = new ArrayList<>(); + + for (var testGroup : eofTests.entrySet()) { + String groupName = testGroup.getKey(); + for (var testVector : testGroup.getValue().getVector().entrySet()) { + String testName = testVector.getKey(); + if (testVectorName != null && !testVectorName.equals(testName)) { + continue; + } + String code = testVector.getValue().code(); + for (var testResult : testVector.getValue().results().entrySet()) { + String expectedForkName = testResult.getKey(); + if (forkName != null && !forkName.equals(expectedForkName)) { + continue; + } + TestResult expectedResult = testResult.getValue(); + EvmSpecVersion evmVersion = EvmSpecVersion.fromName(expectedForkName); + if (evmVersion == null) { + results.add( + new TestExecutionResult( + fileName, + groupName, + testName, + expectedForkName, + false, + "Valid fork name", + "Unknown fork: " + expectedForkName)); + + continue; + } + TestResult actualResult; + if (evmVersion.ordinal() < EvmSpecVersion.PRAGUE_EOF.ordinal()) { + actualResult = failed("EOF_InvalidCode"); + } else { + actualResult = considerCode(code); + } + results.add( + new TestExecutionResult( + fileName, + groupName, + testName, + expectedForkName, + actualResult.result() == expectedResult.result(), + expectedResult.exception(), + actualResult.exception())); + } + } + } + for (TestExecutionResult result : results) { + try { + parentCommand.out.println(JsonUtils.createObjectMapper().writeValueAsString(result)); + } catch (JsonProcessingException e) { + e.printStackTrace(parentCommand.out); + throw new RuntimeException(e); + } + } + } + + public TestResult considerCode(final String hexCode) { + Bytes codeBytes; + try { + codeBytes = + Bytes.fromHexString( + hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", "")); + } catch (RuntimeException re) { + return failed(re.getMessage()); + } + if (codeBytes.isEmpty()) { + return passed(); + } + + var layout = EOFLayout.parseEOF(codeBytes); + if (!layout.isValid()) { + return failed("layout - " + layout.invalidReason()); + } + + var code = CodeFactory.createCode(codeBytes, 1); + if (!code.isValid()) { + return failed("validate " + ((CodeInvalid) code).getInvalidReason()); + } + if (code instanceof CodeV1 codeV1) { + var result = CodeV1Validation.validate(codeV1.getEofLayout()); + if (result != null) { + return (failed("deep validate error: " + result)); + } + } + + return passed(); + } +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index 9e322c61d08..b6e093aa20c 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -87,6 +87,8 @@ BenchmarkSubCommand.class, B11rSubCommand.class, CodeValidateSubCommand.class, + EOFTestSubCommand.class, + PrettyPrintSubCommand.class, StateTestSubCommand.class, T8nSubCommand.class, T8nServerSubCommand.class @@ -140,6 +142,11 @@ void setBytes(final String optionValue) { description = "Receiving address for this invocation.") private final Address receiver = Address.ZERO; + @Option( + names = {"--create"}, + description = "Run call should be a create instead of a call operation.") + private final Boolean createTransaction = false; + @Option( names = {"--contract"}, paramLabel = "
", @@ -340,7 +347,7 @@ public void run() { .nonce(0) .gasPrice(Wei.ZERO) .gasLimit(Long.MAX_VALUE) - .to(receiver) + .to(createTransaction ? null : receiver) .value(Wei.ZERO) .payload(callData) .sender(sender) @@ -361,10 +368,10 @@ public void run() { } final EVM evm = protocolSpec.getEvm(); - if (codeBytes.isEmpty()) { + if (codeBytes.isEmpty() && !createTransaction) { codeBytes = component.getWorldState().get(receiver).getCode(); } - Code code = evm.getCode(Hash.hash(codeBytes), codeBytes); + Code code = evm.getCodeForCreation(codeBytes); if (!code.isValid()) { out.println(((CodeInvalid) code).getInvalidReason()); return; @@ -381,7 +388,9 @@ public void run() { WorldUpdater updater = component.getWorldUpdater(); updater.getOrCreate(sender); - updater.getOrCreate(receiver); + if (!createTransaction) { + updater.getOrCreate(receiver); + } var contractAccount = updater.getOrCreate(contract); contractAccount.setCode(codeBytes); @@ -412,18 +421,23 @@ public void run() { .baseFee(component.getBlockchain().getChainHeadHeader().getBaseFee().orElse(null)) .buildBlockHeader(); + Address contractAddress = + createTransaction ? Address.contractAddress(receiver, 0) : receiver; MessageFrame initialMessageFrame = MessageFrame.builder() - .type(MessageFrame.Type.MESSAGE_CALL) + .type( + createTransaction + ? MessageFrame.Type.CONTRACT_CREATION + : MessageFrame.Type.MESSAGE_CALL) .worldUpdater(updater.updater()) .initialGas(txGas) - .contract(Address.ZERO) - .address(receiver) + .contract(contractAddress) + .address(contractAddress) .originator(sender) .sender(sender) .gasPrice(gasPriceGWei) .blobGasPrice(blobGasPrice) - .inputData(callData) + .inputData(createTransaction ? codeBytes.slice(code.getSize()) : callData) .value(ethValue) .apparentValue(ethValue) .code(code) diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java index af7ed1fc8c8..761c81811ae 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java @@ -116,6 +116,9 @@ public static Map> createSchedules() { Map.entry( "prague", createSchedule(new StubGenesisConfigOptions().pragueTime(0).baseFeePerGas(0x0a))), + Map.entry( + "pragueeof", + createSchedule(new StubGenesisConfigOptions().pragueEOFTime(0).baseFeePerGas(0x0a))), Map.entry( "futureeips", createSchedule(new StubGenesisConfigOptions().futureEipsTime(0).baseFeePerGas(0x0a))), diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java new file mode 100644 index 00000000000..4e03f1aa69e --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java @@ -0,0 +1,81 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evmtool; + +import static org.hyperledger.besu.evmtool.PrettyPrintSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.evm.code.CodeV1Validation; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.util.LogConfigurator; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +@CommandLine.Command( + name = COMMAND_NAME, + description = "Pretty Prints EOF Code", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class PrettyPrintSubCommand implements Runnable { + public static final String COMMAND_NAME = "pretty-print"; + @CommandLine.ParentCommand private final EvmToolCommand parentCommand; + + @CommandLine.Option( + names = {"-f", "--force"}, + description = "Always print well formated code, even if there is an error", + paramLabel = "") + private final Boolean force = false; + + // picocli does it magically + @CommandLine.Parameters private final List codeList = new ArrayList<>(); + + public PrettyPrintSubCommand() { + this(null); + } + + public PrettyPrintSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + + for (var hexCode : codeList) { + Bytes container = Bytes.fromHexString(hexCode); + if (container.get(0) != ((byte) 0xef) && container.get(1) != 0) { + parentCommand.out.println( + "Pretty printing of legacy EVM is not supported. Patches welcome!"); + + } else { + EOFLayout layout = EOFLayout.parseEOF(container); + if (layout.isValid()) { + String validation = CodeV1Validation.validate(layout); + if (validation == null || force) { + layout.prettyPrint(parentCommand.out); + } + if (validation != null) { + parentCommand.out.println("EOF code is invalid - " + validation); + } + } else { + parentCommand.out.println("EOF layout is invalid - " + layout.invalidReason()); + } + } + } + } +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java index 3e1b0270a0b..60a09b26e8e 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java @@ -308,7 +308,9 @@ private void traceTestSpecs(final String test, final List new HomesteadGasCalculator(); case FRONTIER -> new FrontierGasCalculator(); + case TANGERINE_WHISTLE -> null; + case SPURIOUS_DRAGON -> null; case BYZANTIUM -> new ByzantiumGasCalculator(); case CONSTANTINOPLE -> new ConstantinopleGasCalculator(); case PETERSBURG -> new PetersburgGasCalculator(); @@ -139,7 +142,9 @@ public static GasCalculator gasCalculatorForFork(final String fork) { case LONDON, PARIS -> new LondonGasCalculator(); case SHANGHAI -> new ShanghaiGasCalculator(); case CANCUN -> new CancunGasCalculator(); - default -> new PragueGasCalculator(); + case PRAGUE -> new PragueGasCalculator(); + case PRAGUE_EOF, OSAKA, AMSTERDAM, BOGOTA, POLIS, BANGKOK, FUTURE_EIPS, EXPERIMENTAL_EIPS -> + new PragueEOFGasCalculator(); }; } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java index f98256324a7..87af915f665 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java @@ -24,24 +24,24 @@ import org.junit.jupiter.api.Test; import picocli.CommandLine; -public class CodeValidationSubCommandTest { +class CodeValidationSubCommandTest { - static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 00"; - static final String CODE_RETF_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 e4"; - static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 030000 00 00000000 e4"; + static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 040000 00 00800000 00"; + static final String CODE_RETURN_ONLY = "0xef0001 010004 020001-0003 040000 00 00800002 5f5ff3"; + static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 040000 00 00800000 e4"; static final String CODE_INTERIOR_COMMENTS = """ - 0xef0001 010008 020002-000c-0002 030000 00 + 0xef0001 010008 020002-0009-0002 040000 00 # 7 inputs 1 output, - 00000007-07010007 - 59-59-59-59-59-59-59-e30001-50-e4 + 00800004-04010004 + 59-59-59-59-e30001-50-00 # No immediate data - f1-e4"""; + f8-e4"""; static final String CODE_MULTIPLE = - CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETF_ONLY + "\n"; + CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETURN_ONLY + "\n"; @Test - public void testSingleValidViaInput() { + void testSingleValidViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_STOP_ONLY.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = @@ -51,7 +51,7 @@ public void testSingleValidViaInput() { } @Test - public void testSingleInvalidViaInput() { + void testSingleInvalidViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_BAD_MAGIC.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = @@ -61,7 +61,7 @@ public void testSingleInvalidViaInput() { } @Test - public void testMultipleViaInput() { + void testMultipleViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_MULTIPLE.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = @@ -72,12 +72,12 @@ public void testMultipleViaInput() { """ OK 00 err: layout - EOF header byte 1 incorrect - OK e4 + OK 5f5ff3 """); } @Test - public void testSingleValidViaCli() { + void testSingleValidViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = @@ -89,7 +89,7 @@ public void testSingleValidViaCli() { } @Test - public void testSingleInvalidViaCli() { + void testSingleInvalidViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = @@ -101,37 +101,37 @@ public void testSingleInvalidViaCli() { } @Test - public void testMultipleViaCli() { + void testMultipleViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(bais, new PrintStream(baos)); final CommandLine cmd = new CommandLine(codeValidateSubCommand); - cmd.parseArgs(CODE_STOP_ONLY, CODE_BAD_MAGIC, CODE_RETF_ONLY); + cmd.parseArgs(CODE_STOP_ONLY, CODE_BAD_MAGIC, CODE_RETURN_ONLY); codeValidateSubCommand.run(); assertThat(baos.toString(UTF_8)) .contains( """ OK 00 err: layout - EOF header byte 1 incorrect - OK e4 + OK 5f5ff3 """); } @Test - public void testCliEclipsesInput() { + void testCliEclipsesInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_STOP_ONLY.getBytes(UTF_8)); final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(bais, new PrintStream(baos)); final CommandLine cmd = new CommandLine(codeValidateSubCommand); - cmd.parseArgs(CODE_RETF_ONLY); + cmd.parseArgs(CODE_RETURN_ONLY); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 5f5ff3\n"); } @Test - public void testInteriorCommentsSkipped() { + void testInteriorCommentsSkipped() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final CodeValidateSubCommand codeValidateSubCommand = @@ -139,11 +139,11 @@ public void testInteriorCommentsSkipped() { final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_INTERIOR_COMMENTS); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 59595959595959e3000150e4,f1e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 59595959e300015000,f8e4\n"); } @Test - public void testBlankLinesAndCommentsSkipped() { + void testBlankLinesAndCommentsSkipped() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(("# comment\n\n#blank line\n\n" + CODE_MULTIPLE).getBytes(UTF_8)); @@ -155,7 +155,7 @@ public void testBlankLinesAndCommentsSkipped() { """ OK 00 err: layout - EOF header byte 1 incorrect - OK e4 + OK 5f5ff3 """); } } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java index 504d1f04677..1892a472b02 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java @@ -57,6 +57,10 @@ public static Object[][] b11rTests() { return findSpecFiles(new String[] {"b11r"}); } + public static Object[][] prettyPrintTests() { + return findSpecFiles(new String[] {"pretty-print"}); + } + public static Object[][] stateTestTests() { return findSpecFiles(new String[] {"state-test"}); } @@ -110,7 +114,7 @@ private static Object[] pathToParams(final String subDir, final File file) { } @ParameterizedTest(name = "{0}") - @MethodSource({"b11rTests", "stateTestTests", "t8nTests", "traceTests"}) + @MethodSource({"b11rTests", "prettyPrintTests", "stateTestTests", "t8nTests", "traceTests"}) void testBySpec( final String file, final JsonNode cliNode, diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json new file mode 100644 index 00000000000..988a8e31570 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xef0001010004020001090b04000000008000025f35e2ff0007000e0015001c0023002a00310038003f0046004d0054005b0062006900700077007e0085008c0093009a00a100a800af00b600bd00c400cb00d200d900e000e700ee00f500fc0103010a01110118011f0126012d0134013b0142014901500157015e0165016c0173017a01810188018f0196019d01a401ab01b201b901c001c701ce01d501dc01e301ea01f101f801ff0206020d0214021b0222022902300237023e0245024c0253025a02610268026f0276027d0284028b0292029902a002a702ae02b502bc02c302ca02d102d802df02e602ed02f402fb0302030903100317031e0325032c0333033a03410348034f0356035d0364036b0372037903800387038e0395039c03a303aa03b103b803bf03c603cd03d403db03e203e903f003f703fe0405040c0413041a04210428042f0436043d0444044b0452045904600467046e0475047c0483048a04910498049f04a604ad04b404bb04c204c904d004d704de04e504ec04f304fa05010508050f0516051d0524052b0532053905400547054e0555055c0563056a05710578057f0586058d0594059b05a205a905b005b705be05c505cc05d305da05e105e805ef05f605fd0604060b0612061906200627062e0635063c0643064a06510658065f0666066d0674067b0682068906900697069e06a506ac06b306ba06c106c806cf06d606dd06e406eb06f206f9070061ffff600255006110006002550061100160025500611002600255006110036002550061100460025500611005600255006110066002550061100760025500611008600255006110096002550061100a6002550061100b6002550061100c6002550061100d6002550061100e6002550061100f600255006110106002550061101160025500611012600255006110136002550061101460025500611015600255006110166002550061101760025500611018600255006110196002550061101a6002550061101b6002550061101c6002550061101d6002550061101e6002550061101f600255006110206002550061102160025500611022600255006110236002550061102460025500611025600255006110266002550061102760025500611028600255006110296002550061102a6002550061102b6002550061102c6002550061102d6002550061102e6002550061102f600255006110306002550061103160025500611032600255006110336002550061103460025500611035600255006110366002550061103760025500611038600255006110396002550061103a6002550061103b6002550061103c6002550061103d6002550061103e6002550061103f600255006110406002550061104160025500611042600255006110436002550061104460025500611045600255006110466002550061104760025500611048600255006110496002550061104a6002550061104b6002550061104c6002550061104d6002550061104e6002550061104f600255006110506002550061105160025500611052600255006110536002550061105460025500611055600255006110566002550061105760025500611058600255006110596002550061105a6002550061105b6002550061105c6002550061105d6002550061105e6002550061105f600255006110606002550061106160025500611062600255006110636002550061106460025500611065600255006110666002550061106760025500611068600255006110696002550061106a6002550061106b6002550061106c6002550061106d6002550061106e6002550061106f600255006110706002550061107160025500611072600255006110736002550061107460025500611075600255006110766002550061107760025500611078600255006110796002550061107a6002550061107b6002550061107c6002550061107d6002550061107e6002550061107f600255006110806002550061108160025500611082600255006110836002550061108460025500611085600255006110866002550061108760025500611088600255006110896002550061108a6002550061108b6002550061108c6002550061108d6002550061108e6002550061108f600255006110906002550061109160025500611092600255006110936002550061109460025500611095600255006110966002550061109760025500611098600255006110996002550061109a6002550061109b6002550061109c6002550061109d6002550061109e6002550061109f600255006110a0600255006110a1600255006110a2600255006110a3600255006110a4600255006110a5600255006110a6600255006110a7600255006110a8600255006110a9600255006110aa600255006110ab600255006110ac600255006110ad600255006110ae600255006110af600255006110b0600255006110b1600255006110b2600255006110b3600255006110b4600255006110b5600255006110b6600255006110b7600255006110b8600255006110b9600255006110ba600255006110bb600255006110bc600255006110bd600255006110be600255006110bf600255006110c0600255006110c1600255006110c2600255006110c3600255006110c4600255006110c5600255006110c6600255006110c7600255006110c8600255006110c9600255006110ca600255006110cb600255006110cc600255006110cd600255006110ce600255006110cf600255006110d0600255006110d1600255006110d2600255006110d3600255006110d4600255006110d5600255006110d6600255006110d7600255006110d8600255006110d9600255006110da600255006110db600255006110dc600255006110dd600255006110de600255006110df600255006110e0600255006110e1600255006110e2600255006110e3600255006110e4600255006110e5600255006110e6600255006110e7600255006110e8600255006110e9600255006110ea600255006110eb600255006110ec600255006110ed600255006110ee600255006110ef600255006110f0600255006110f1600255006110f2600255006110f3600255006110f4600255006110f5600255006110f6600255006110f7600255006110f8600255006110f9600255006110fa600255006110fb600255006110fc600255006110fd600255006110fe600255006110ff60025500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 090b # Code section 0 , 2315 bytes\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n 5f # [0] PUSH0\n 35 # [1] CALLDATALOAD\ne2ff0007000e0015001c0023002a00310038003f0046004d0054005b0062006900700077007e0085008c0093009a00a100a800af00b600bd00c400cb00d200d900e000e700ee00f500fc0103010a01110118011f0126012d0134013b0142014901500157015e0165016c0173017a01810188018f0196019d01a401ab01b201b901c001c701ce01d501dc01e301ea01f101f801ff0206020d0214021b0222022902300237023e0245024c0253025a02610268026f0276027d0284028b0292029902a002a702ae02b502bc02c302ca02d102d802df02e602ed02f402fb0302030903100317031e0325032c0333033a03410348034f0356035d0364036b0372037903800387038e0395039c03a303aa03b103b803bf03c603cd03d403db03e203e903f003f703fe0405040c0413041a04210428042f0436043d0444044b0452045904600467046e0475047c0483048a04910498049f04a604ad04b404bb04c204c904d004d704de04e504ec04f304fa05010508050f0516051d0524052b0532053905400547054e0555055c0563056a05710578057f0586058d0594059b05a205a905b005b705be05c505cc05d305da05e105e805ef05f605fd0604060b0612061906200627062e0635063c0643064a06510658065f0666066d0674067b0682068906900697069e06a506ac06b306ba06c106c806cf06d606dd06e406eb06f206f90700 # [2] RJUMPV(7,14,21,28,35,42,49,56,63,70,77,84,91,98,105,112,119,126,133,140,147,154,161,168,175,182,189,196,203,210,217,224,231,238,245,252,259,266,273,280,287,294,301,308,315,322,329,336,343,350,357,364,371,378,385,392,399,406,413,420,427,434,441,448,455,462,469,476,483,490,497,504,511,518,525,532,539,546,553,560,567,574,581,588,595,602,609,616,623,630,637,644,651,658,665,672,679,686,693,700,707,714,721,728,735,742,749,756,763,770,777,784,791,798,805,812,819,826,833,840,847,854,861,868,875,882,889,896,903,910,917,924,931,938,945,952,959,966,973,980,987,994,1001,1008,1015,1022,1029,1036,1043,1050,1057,1064,1071,1078,1085,1092,1099,1106,1113,1120,1127,1134,1141,1148,1155,1162,1169,1176,1183,1190,1197,1204,1211,1218,1225,1232,1239,1246,1253,1260,1267,1274,1281,1288,1295,1302,1309,1316,1323,1330,1337,1344,1351,1358,1365,1372,1379,1386,1393,1400,1407,1414,1421,1428,1435,1442,1449,1456,1463,1470,1477,1484,1491,1498,1505,1512,1519,1526,1533,1540,1547,1554,1561,1568,1575,1582,1589,1596,1603,1610,1617,1624,1631,1638,1645,1652,1659,1666,1673,1680,1687,1694,1701,1708,1715,1722,1729,1736,1743,1750,1757,1764,1771,1778,1785,1792)\n61ffff # [516] PUSH2(0xffff)\n 6002 # [519] PUSH1(2)\n 55 # [521] SSTORE\n 00 # [522] STOP\n611000 # [523] PUSH2(0x1000)\n 6002 # [526] PUSH1(2)\n 55 # [528] SSTORE\n 00 # [529] STOP\n611001 # [530] PUSH2(0x1001)\n 6002 # [533] PUSH1(2)\n 55 # [535] SSTORE\n 00 # [536] STOP\n611002 # [537] PUSH2(0x1002)\n 6002 # [540] PUSH1(2)\n 55 # [542] SSTORE\n 00 # [543] STOP\n611003 # [544] PUSH2(0x1003)\n 6002 # [547] PUSH1(2)\n 55 # [549] SSTORE\n 00 # [550] STOP\n611004 # [551] PUSH2(0x1004)\n 6002 # [554] PUSH1(2)\n 55 # [556] SSTORE\n 00 # [557] STOP\n611005 # [558] PUSH2(0x1005)\n 6002 # [561] PUSH1(2)\n 55 # [563] SSTORE\n 00 # [564] STOP\n611006 # [565] PUSH2(0x1006)\n 6002 # [568] PUSH1(2)\n 55 # [570] SSTORE\n 00 # [571] STOP\n611007 # [572] PUSH2(0x1007)\n 6002 # [575] PUSH1(2)\n 55 # [577] SSTORE\n 00 # [578] STOP\n611008 # [579] PUSH2(0x1008)\n 6002 # [582] PUSH1(2)\n 55 # [584] SSTORE\n 00 # [585] STOP\n611009 # [586] PUSH2(0x1009)\n 6002 # [589] PUSH1(2)\n 55 # [591] SSTORE\n 00 # [592] STOP\n61100a # [593] PUSH2(0x100a)\n 6002 # [596] PUSH1(2)\n 55 # [598] SSTORE\n 00 # [599] STOP\n61100b # [600] PUSH2(0x100b)\n 6002 # [603] PUSH1(2)\n 55 # [605] SSTORE\n 00 # [606] STOP\n61100c # [607] PUSH2(0x100c)\n 6002 # [610] PUSH1(2)\n 55 # [612] SSTORE\n 00 # [613] STOP\n61100d # [614] PUSH2(0x100d)\n 6002 # [617] PUSH1(2)\n 55 # [619] SSTORE\n 00 # [620] STOP\n61100e # [621] PUSH2(0x100e)\n 6002 # [624] PUSH1(2)\n 55 # [626] SSTORE\n 00 # [627] STOP\n61100f # [628] PUSH2(0x100f)\n 6002 # [631] PUSH1(2)\n 55 # [633] SSTORE\n 00 # [634] STOP\n611010 # [635] PUSH2(0x1010)\n 6002 # [638] PUSH1(2)\n 55 # [640] SSTORE\n 00 # [641] STOP\n611011 # [642] PUSH2(0x1011)\n 6002 # [645] PUSH1(2)\n 55 # [647] SSTORE\n 00 # [648] STOP\n611012 # [649] PUSH2(0x1012)\n 6002 # [652] PUSH1(2)\n 55 # [654] SSTORE\n 00 # [655] STOP\n611013 # [656] PUSH2(0x1013)\n 6002 # [659] PUSH1(2)\n 55 # [661] SSTORE\n 00 # [662] STOP\n611014 # [663] PUSH2(0x1014)\n 6002 # [666] PUSH1(2)\n 55 # [668] SSTORE\n 00 # [669] STOP\n611015 # [670] PUSH2(0x1015)\n 6002 # [673] PUSH1(2)\n 55 # [675] SSTORE\n 00 # [676] STOP\n611016 # [677] PUSH2(0x1016)\n 6002 # [680] PUSH1(2)\n 55 # [682] SSTORE\n 00 # [683] STOP\n611017 # [684] PUSH2(0x1017)\n 6002 # [687] PUSH1(2)\n 55 # [689] SSTORE\n 00 # [690] STOP\n611018 # [691] PUSH2(0x1018)\n 6002 # [694] PUSH1(2)\n 55 # [696] SSTORE\n 00 # [697] STOP\n611019 # [698] PUSH2(0x1019)\n 6002 # [701] PUSH1(2)\n 55 # [703] SSTORE\n 00 # [704] STOP\n61101a # [705] PUSH2(0x101a)\n 6002 # [708] PUSH1(2)\n 55 # [710] SSTORE\n 00 # [711] STOP\n61101b # [712] PUSH2(0x101b)\n 6002 # [715] PUSH1(2)\n 55 # [717] SSTORE\n 00 # [718] STOP\n61101c # [719] PUSH2(0x101c)\n 6002 # [722] PUSH1(2)\n 55 # [724] SSTORE\n 00 # [725] STOP\n61101d # [726] PUSH2(0x101d)\n 6002 # [729] PUSH1(2)\n 55 # [731] SSTORE\n 00 # [732] STOP\n61101e # [733] PUSH2(0x101e)\n 6002 # [736] PUSH1(2)\n 55 # [738] SSTORE\n 00 # [739] STOP\n61101f # [740] PUSH2(0x101f)\n 6002 # [743] PUSH1(2)\n 55 # [745] SSTORE\n 00 # [746] STOP\n611020 # [747] PUSH2(0x1020)\n 6002 # [750] PUSH1(2)\n 55 # [752] SSTORE\n 00 # [753] STOP\n611021 # [754] PUSH2(0x1021)\n 6002 # [757] PUSH1(2)\n 55 # [759] SSTORE\n 00 # [760] STOP\n611022 # [761] PUSH2(0x1022)\n 6002 # [764] PUSH1(2)\n 55 # [766] SSTORE\n 00 # [767] STOP\n611023 # [768] PUSH2(0x1023)\n 6002 # [771] PUSH1(2)\n 55 # [773] SSTORE\n 00 # [774] STOP\n611024 # [775] PUSH2(0x1024)\n 6002 # [778] PUSH1(2)\n 55 # [780] SSTORE\n 00 # [781] STOP\n611025 # [782] PUSH2(0x1025)\n 6002 # [785] PUSH1(2)\n 55 # [787] SSTORE\n 00 # [788] STOP\n611026 # [789] PUSH2(0x1026)\n 6002 # [792] PUSH1(2)\n 55 # [794] SSTORE\n 00 # [795] STOP\n611027 # [796] PUSH2(0x1027)\n 6002 # [799] PUSH1(2)\n 55 # [801] SSTORE\n 00 # [802] STOP\n611028 # [803] PUSH2(0x1028)\n 6002 # [806] PUSH1(2)\n 55 # [808] SSTORE\n 00 # [809] STOP\n611029 # [810] PUSH2(0x1029)\n 6002 # [813] PUSH1(2)\n 55 # [815] SSTORE\n 00 # [816] STOP\n61102a # [817] PUSH2(0x102a)\n 6002 # [820] PUSH1(2)\n 55 # [822] SSTORE\n 00 # [823] STOP\n61102b # [824] PUSH2(0x102b)\n 6002 # [827] PUSH1(2)\n 55 # [829] SSTORE\n 00 # [830] STOP\n61102c # [831] PUSH2(0x102c)\n 6002 # [834] PUSH1(2)\n 55 # [836] SSTORE\n 00 # [837] STOP\n61102d # [838] PUSH2(0x102d)\n 6002 # [841] PUSH1(2)\n 55 # [843] SSTORE\n 00 # [844] STOP\n61102e # [845] PUSH2(0x102e)\n 6002 # [848] PUSH1(2)\n 55 # [850] SSTORE\n 00 # [851] STOP\n61102f # [852] PUSH2(0x102f)\n 6002 # [855] PUSH1(2)\n 55 # [857] SSTORE\n 00 # [858] STOP\n611030 # [859] PUSH2(0x1030)\n 6002 # [862] PUSH1(2)\n 55 # [864] SSTORE\n 00 # [865] STOP\n611031 # [866] PUSH2(0x1031)\n 6002 # [869] PUSH1(2)\n 55 # [871] SSTORE\n 00 # [872] STOP\n611032 # [873] PUSH2(0x1032)\n 6002 # [876] PUSH1(2)\n 55 # [878] SSTORE\n 00 # [879] STOP\n611033 # [880] PUSH2(0x1033)\n 6002 # [883] PUSH1(2)\n 55 # [885] SSTORE\n 00 # [886] STOP\n611034 # [887] PUSH2(0x1034)\n 6002 # [890] PUSH1(2)\n 55 # [892] SSTORE\n 00 # [893] STOP\n611035 # [894] PUSH2(0x1035)\n 6002 # [897] PUSH1(2)\n 55 # [899] SSTORE\n 00 # [900] STOP\n611036 # [901] PUSH2(0x1036)\n 6002 # [904] PUSH1(2)\n 55 # [906] SSTORE\n 00 # [907] STOP\n611037 # [908] PUSH2(0x1037)\n 6002 # [911] PUSH1(2)\n 55 # [913] SSTORE\n 00 # [914] STOP\n611038 # [915] PUSH2(0x1038)\n 6002 # [918] PUSH1(2)\n 55 # [920] SSTORE\n 00 # [921] STOP\n611039 # [922] PUSH2(0x1039)\n 6002 # [925] PUSH1(2)\n 55 # [927] SSTORE\n 00 # [928] STOP\n61103a # [929] PUSH2(0x103a)\n 6002 # [932] PUSH1(2)\n 55 # [934] SSTORE\n 00 # [935] STOP\n61103b # [936] PUSH2(0x103b)\n 6002 # [939] PUSH1(2)\n 55 # [941] SSTORE\n 00 # [942] STOP\n61103c # [943] PUSH2(0x103c)\n 6002 # [946] PUSH1(2)\n 55 # [948] SSTORE\n 00 # [949] STOP\n61103d # [950] PUSH2(0x103d)\n 6002 # [953] PUSH1(2)\n 55 # [955] SSTORE\n 00 # [956] STOP\n61103e # [957] PUSH2(0x103e)\n 6002 # [960] PUSH1(2)\n 55 # [962] SSTORE\n 00 # [963] STOP\n61103f # [964] PUSH2(0x103f)\n 6002 # [967] PUSH1(2)\n 55 # [969] SSTORE\n 00 # [970] STOP\n611040 # [971] PUSH2(0x1040)\n 6002 # [974] PUSH1(2)\n 55 # [976] SSTORE\n 00 # [977] STOP\n611041 # [978] PUSH2(0x1041)\n 6002 # [981] PUSH1(2)\n 55 # [983] SSTORE\n 00 # [984] STOP\n611042 # [985] PUSH2(0x1042)\n 6002 # [988] PUSH1(2)\n 55 # [990] SSTORE\n 00 # [991] STOP\n611043 # [992] PUSH2(0x1043)\n 6002 # [995] PUSH1(2)\n 55 # [997] SSTORE\n 00 # [998] STOP\n611044 # [999] PUSH2(0x1044)\n 6002 # [1002] PUSH1(2)\n 55 # [1004] SSTORE\n 00 # [1005] STOP\n611045 # [1006] PUSH2(0x1045)\n 6002 # [1009] PUSH1(2)\n 55 # [1011] SSTORE\n 00 # [1012] STOP\n611046 # [1013] PUSH2(0x1046)\n 6002 # [1016] PUSH1(2)\n 55 # [1018] SSTORE\n 00 # [1019] STOP\n611047 # [1020] PUSH2(0x1047)\n 6002 # [1023] PUSH1(2)\n 55 # [1025] SSTORE\n 00 # [1026] STOP\n611048 # [1027] PUSH2(0x1048)\n 6002 # [1030] PUSH1(2)\n 55 # [1032] SSTORE\n 00 # [1033] STOP\n611049 # [1034] PUSH2(0x1049)\n 6002 # [1037] PUSH1(2)\n 55 # [1039] SSTORE\n 00 # [1040] STOP\n61104a # [1041] PUSH2(0x104a)\n 6002 # [1044] PUSH1(2)\n 55 # [1046] SSTORE\n 00 # [1047] STOP\n61104b # [1048] PUSH2(0x104b)\n 6002 # [1051] PUSH1(2)\n 55 # [1053] SSTORE\n 00 # [1054] STOP\n61104c # [1055] PUSH2(0x104c)\n 6002 # [1058] PUSH1(2)\n 55 # [1060] SSTORE\n 00 # [1061] STOP\n61104d # [1062] PUSH2(0x104d)\n 6002 # [1065] PUSH1(2)\n 55 # [1067] SSTORE\n 00 # [1068] STOP\n61104e # [1069] PUSH2(0x104e)\n 6002 # [1072] PUSH1(2)\n 55 # [1074] SSTORE\n 00 # [1075] STOP\n61104f # [1076] PUSH2(0x104f)\n 6002 # [1079] PUSH1(2)\n 55 # [1081] SSTORE\n 00 # [1082] STOP\n611050 # [1083] PUSH2(0x1050)\n 6002 # [1086] PUSH1(2)\n 55 # [1088] SSTORE\n 00 # [1089] STOP\n611051 # [1090] PUSH2(0x1051)\n 6002 # [1093] PUSH1(2)\n 55 # [1095] SSTORE\n 00 # [1096] STOP\n611052 # [1097] PUSH2(0x1052)\n 6002 # [1100] PUSH1(2)\n 55 # [1102] SSTORE\n 00 # [1103] STOP\n611053 # [1104] PUSH2(0x1053)\n 6002 # [1107] PUSH1(2)\n 55 # [1109] SSTORE\n 00 # [1110] STOP\n611054 # [1111] PUSH2(0x1054)\n 6002 # [1114] PUSH1(2)\n 55 # [1116] SSTORE\n 00 # [1117] STOP\n611055 # [1118] PUSH2(0x1055)\n 6002 # [1121] PUSH1(2)\n 55 # [1123] SSTORE\n 00 # [1124] STOP\n611056 # [1125] PUSH2(0x1056)\n 6002 # [1128] PUSH1(2)\n 55 # [1130] SSTORE\n 00 # [1131] STOP\n611057 # [1132] PUSH2(0x1057)\n 6002 # [1135] PUSH1(2)\n 55 # [1137] SSTORE\n 00 # [1138] STOP\n611058 # [1139] PUSH2(0x1058)\n 6002 # [1142] PUSH1(2)\n 55 # [1144] SSTORE\n 00 # [1145] STOP\n611059 # [1146] PUSH2(0x1059)\n 6002 # [1149] PUSH1(2)\n 55 # [1151] SSTORE\n 00 # [1152] STOP\n61105a # [1153] PUSH2(0x105a)\n 6002 # [1156] PUSH1(2)\n 55 # [1158] SSTORE\n 00 # [1159] STOP\n61105b # [1160] PUSH2(0x105b)\n 6002 # [1163] PUSH1(2)\n 55 # [1165] SSTORE\n 00 # [1166] STOP\n61105c # [1167] PUSH2(0x105c)\n 6002 # [1170] PUSH1(2)\n 55 # [1172] SSTORE\n 00 # [1173] STOP\n61105d # [1174] PUSH2(0x105d)\n 6002 # [1177] PUSH1(2)\n 55 # [1179] SSTORE\n 00 # [1180] STOP\n61105e # [1181] PUSH2(0x105e)\n 6002 # [1184] PUSH1(2)\n 55 # [1186] SSTORE\n 00 # [1187] STOP\n61105f # [1188] PUSH2(0x105f)\n 6002 # [1191] PUSH1(2)\n 55 # [1193] SSTORE\n 00 # [1194] STOP\n611060 # [1195] PUSH2(0x1060)\n 6002 # [1198] PUSH1(2)\n 55 # [1200] SSTORE\n 00 # [1201] STOP\n611061 # [1202] PUSH2(0x1061)\n 6002 # [1205] PUSH1(2)\n 55 # [1207] SSTORE\n 00 # [1208] STOP\n611062 # [1209] PUSH2(0x1062)\n 6002 # [1212] PUSH1(2)\n 55 # [1214] SSTORE\n 00 # [1215] STOP\n611063 # [1216] PUSH2(0x1063)\n 6002 # [1219] PUSH1(2)\n 55 # [1221] SSTORE\n 00 # [1222] STOP\n611064 # [1223] PUSH2(0x1064)\n 6002 # [1226] PUSH1(2)\n 55 # [1228] SSTORE\n 00 # [1229] STOP\n611065 # [1230] PUSH2(0x1065)\n 6002 # [1233] PUSH1(2)\n 55 # [1235] SSTORE\n 00 # [1236] STOP\n611066 # [1237] PUSH2(0x1066)\n 6002 # [1240] PUSH1(2)\n 55 # [1242] SSTORE\n 00 # [1243] STOP\n611067 # [1244] PUSH2(0x1067)\n 6002 # [1247] PUSH1(2)\n 55 # [1249] SSTORE\n 00 # [1250] STOP\n611068 # [1251] PUSH2(0x1068)\n 6002 # [1254] PUSH1(2)\n 55 # [1256] SSTORE\n 00 # [1257] STOP\n611069 # [1258] PUSH2(0x1069)\n 6002 # [1261] PUSH1(2)\n 55 # [1263] SSTORE\n 00 # [1264] STOP\n61106a # [1265] PUSH2(0x106a)\n 6002 # [1268] PUSH1(2)\n 55 # [1270] SSTORE\n 00 # [1271] STOP\n61106b # [1272] PUSH2(0x106b)\n 6002 # [1275] PUSH1(2)\n 55 # [1277] SSTORE\n 00 # [1278] STOP\n61106c # [1279] PUSH2(0x106c)\n 6002 # [1282] PUSH1(2)\n 55 # [1284] SSTORE\n 00 # [1285] STOP\n61106d # [1286] PUSH2(0x106d)\n 6002 # [1289] PUSH1(2)\n 55 # [1291] SSTORE\n 00 # [1292] STOP\n61106e # [1293] PUSH2(0x106e)\n 6002 # [1296] PUSH1(2)\n 55 # [1298] SSTORE\n 00 # [1299] STOP\n61106f # [1300] PUSH2(0x106f)\n 6002 # [1303] PUSH1(2)\n 55 # [1305] SSTORE\n 00 # [1306] STOP\n611070 # [1307] PUSH2(0x1070)\n 6002 # [1310] PUSH1(2)\n 55 # [1312] SSTORE\n 00 # [1313] STOP\n611071 # [1314] PUSH2(0x1071)\n 6002 # [1317] PUSH1(2)\n 55 # [1319] SSTORE\n 00 # [1320] STOP\n611072 # [1321] PUSH2(0x1072)\n 6002 # [1324] PUSH1(2)\n 55 # [1326] SSTORE\n 00 # [1327] STOP\n611073 # [1328] PUSH2(0x1073)\n 6002 # [1331] PUSH1(2)\n 55 # [1333] SSTORE\n 00 # [1334] STOP\n611074 # [1335] PUSH2(0x1074)\n 6002 # [1338] PUSH1(2)\n 55 # [1340] SSTORE\n 00 # [1341] STOP\n611075 # [1342] PUSH2(0x1075)\n 6002 # [1345] PUSH1(2)\n 55 # [1347] SSTORE\n 00 # [1348] STOP\n611076 # [1349] PUSH2(0x1076)\n 6002 # [1352] PUSH1(2)\n 55 # [1354] SSTORE\n 00 # [1355] STOP\n611077 # [1356] PUSH2(0x1077)\n 6002 # [1359] PUSH1(2)\n 55 # [1361] SSTORE\n 00 # [1362] STOP\n611078 # [1363] PUSH2(0x1078)\n 6002 # [1366] PUSH1(2)\n 55 # [1368] SSTORE\n 00 # [1369] STOP\n611079 # [1370] PUSH2(0x1079)\n 6002 # [1373] PUSH1(2)\n 55 # [1375] SSTORE\n 00 # [1376] STOP\n61107a # [1377] PUSH2(0x107a)\n 6002 # [1380] PUSH1(2)\n 55 # [1382] SSTORE\n 00 # [1383] STOP\n61107b # [1384] PUSH2(0x107b)\n 6002 # [1387] PUSH1(2)\n 55 # [1389] SSTORE\n 00 # [1390] STOP\n61107c # [1391] PUSH2(0x107c)\n 6002 # [1394] PUSH1(2)\n 55 # [1396] SSTORE\n 00 # [1397] STOP\n61107d # [1398] PUSH2(0x107d)\n 6002 # [1401] PUSH1(2)\n 55 # [1403] SSTORE\n 00 # [1404] STOP\n61107e # [1405] PUSH2(0x107e)\n 6002 # [1408] PUSH1(2)\n 55 # [1410] SSTORE\n 00 # [1411] STOP\n61107f # [1412] PUSH2(0x107f)\n 6002 # [1415] PUSH1(2)\n 55 # [1417] SSTORE\n 00 # [1418] STOP\n611080 # [1419] PUSH2(0x1080)\n 6002 # [1422] PUSH1(2)\n 55 # [1424] SSTORE\n 00 # [1425] STOP\n611081 # [1426] PUSH2(0x1081)\n 6002 # [1429] PUSH1(2)\n 55 # [1431] SSTORE\n 00 # [1432] STOP\n611082 # [1433] PUSH2(0x1082)\n 6002 # [1436] PUSH1(2)\n 55 # [1438] SSTORE\n 00 # [1439] STOP\n611083 # [1440] PUSH2(0x1083)\n 6002 # [1443] PUSH1(2)\n 55 # [1445] SSTORE\n 00 # [1446] STOP\n611084 # [1447] PUSH2(0x1084)\n 6002 # [1450] PUSH1(2)\n 55 # [1452] SSTORE\n 00 # [1453] STOP\n611085 # [1454] PUSH2(0x1085)\n 6002 # [1457] PUSH1(2)\n 55 # [1459] SSTORE\n 00 # [1460] STOP\n611086 # [1461] PUSH2(0x1086)\n 6002 # [1464] PUSH1(2)\n 55 # [1466] SSTORE\n 00 # [1467] STOP\n611087 # [1468] PUSH2(0x1087)\n 6002 # [1471] PUSH1(2)\n 55 # [1473] SSTORE\n 00 # [1474] STOP\n611088 # [1475] PUSH2(0x1088)\n 6002 # [1478] PUSH1(2)\n 55 # [1480] SSTORE\n 00 # [1481] STOP\n611089 # [1482] PUSH2(0x1089)\n 6002 # [1485] PUSH1(2)\n 55 # [1487] SSTORE\n 00 # [1488] STOP\n61108a # [1489] PUSH2(0x108a)\n 6002 # [1492] PUSH1(2)\n 55 # [1494] SSTORE\n 00 # [1495] STOP\n61108b # [1496] PUSH2(0x108b)\n 6002 # [1499] PUSH1(2)\n 55 # [1501] SSTORE\n 00 # [1502] STOP\n61108c # [1503] PUSH2(0x108c)\n 6002 # [1506] PUSH1(2)\n 55 # [1508] SSTORE\n 00 # [1509] STOP\n61108d # [1510] PUSH2(0x108d)\n 6002 # [1513] PUSH1(2)\n 55 # [1515] SSTORE\n 00 # [1516] STOP\n61108e # [1517] PUSH2(0x108e)\n 6002 # [1520] PUSH1(2)\n 55 # [1522] SSTORE\n 00 # [1523] STOP\n61108f # [1524] PUSH2(0x108f)\n 6002 # [1527] PUSH1(2)\n 55 # [1529] SSTORE\n 00 # [1530] STOP\n611090 # [1531] PUSH2(0x1090)\n 6002 # [1534] PUSH1(2)\n 55 # [1536] SSTORE\n 00 # [1537] STOP\n611091 # [1538] PUSH2(0x1091)\n 6002 # [1541] PUSH1(2)\n 55 # [1543] SSTORE\n 00 # [1544] STOP\n611092 # [1545] PUSH2(0x1092)\n 6002 # [1548] PUSH1(2)\n 55 # [1550] SSTORE\n 00 # [1551] STOP\n611093 # [1552] PUSH2(0x1093)\n 6002 # [1555] PUSH1(2)\n 55 # [1557] SSTORE\n 00 # [1558] STOP\n611094 # [1559] PUSH2(0x1094)\n 6002 # [1562] PUSH1(2)\n 55 # [1564] SSTORE\n 00 # [1565] STOP\n611095 # [1566] PUSH2(0x1095)\n 6002 # [1569] PUSH1(2)\n 55 # [1571] SSTORE\n 00 # [1572] STOP\n611096 # [1573] PUSH2(0x1096)\n 6002 # [1576] PUSH1(2)\n 55 # [1578] SSTORE\n 00 # [1579] STOP\n611097 # [1580] PUSH2(0x1097)\n 6002 # [1583] PUSH1(2)\n 55 # [1585] SSTORE\n 00 # [1586] STOP\n611098 # [1587] PUSH2(0x1098)\n 6002 # [1590] PUSH1(2)\n 55 # [1592] SSTORE\n 00 # [1593] STOP\n611099 # [1594] PUSH2(0x1099)\n 6002 # [1597] PUSH1(2)\n 55 # [1599] SSTORE\n 00 # [1600] STOP\n61109a # [1601] PUSH2(0x109a)\n 6002 # [1604] PUSH1(2)\n 55 # [1606] SSTORE\n 00 # [1607] STOP\n61109b # [1608] PUSH2(0x109b)\n 6002 # [1611] PUSH1(2)\n 55 # [1613] SSTORE\n 00 # [1614] STOP\n61109c # [1615] PUSH2(0x109c)\n 6002 # [1618] PUSH1(2)\n 55 # [1620] SSTORE\n 00 # [1621] STOP\n61109d # [1622] PUSH2(0x109d)\n 6002 # [1625] PUSH1(2)\n 55 # [1627] SSTORE\n 00 # [1628] STOP\n61109e # [1629] PUSH2(0x109e)\n 6002 # [1632] PUSH1(2)\n 55 # [1634] SSTORE\n 00 # [1635] STOP\n61109f # [1636] PUSH2(0x109f)\n 6002 # [1639] PUSH1(2)\n 55 # [1641] SSTORE\n 00 # [1642] STOP\n6110a0 # [1643] PUSH2(0x10a0)\n 6002 # [1646] PUSH1(2)\n 55 # [1648] SSTORE\n 00 # [1649] STOP\n6110a1 # [1650] PUSH2(0x10a1)\n 6002 # [1653] PUSH1(2)\n 55 # [1655] SSTORE\n 00 # [1656] STOP\n6110a2 # [1657] PUSH2(0x10a2)\n 6002 # [1660] PUSH1(2)\n 55 # [1662] SSTORE\n 00 # [1663] STOP\n6110a3 # [1664] PUSH2(0x10a3)\n 6002 # [1667] PUSH1(2)\n 55 # [1669] SSTORE\n 00 # [1670] STOP\n6110a4 # [1671] PUSH2(0x10a4)\n 6002 # [1674] PUSH1(2)\n 55 # [1676] SSTORE\n 00 # [1677] STOP\n6110a5 # [1678] PUSH2(0x10a5)\n 6002 # [1681] PUSH1(2)\n 55 # [1683] SSTORE\n 00 # [1684] STOP\n6110a6 # [1685] PUSH2(0x10a6)\n 6002 # [1688] PUSH1(2)\n 55 # [1690] SSTORE\n 00 # [1691] STOP\n6110a7 # [1692] PUSH2(0x10a7)\n 6002 # [1695] PUSH1(2)\n 55 # [1697] SSTORE\n 00 # [1698] STOP\n6110a8 # [1699] PUSH2(0x10a8)\n 6002 # [1702] PUSH1(2)\n 55 # [1704] SSTORE\n 00 # [1705] STOP\n6110a9 # [1706] PUSH2(0x10a9)\n 6002 # [1709] PUSH1(2)\n 55 # [1711] SSTORE\n 00 # [1712] STOP\n6110aa # [1713] PUSH2(0x10aa)\n 6002 # [1716] PUSH1(2)\n 55 # [1718] SSTORE\n 00 # [1719] STOP\n6110ab # [1720] PUSH2(0x10ab)\n 6002 # [1723] PUSH1(2)\n 55 # [1725] SSTORE\n 00 # [1726] STOP\n6110ac # [1727] PUSH2(0x10ac)\n 6002 # [1730] PUSH1(2)\n 55 # [1732] SSTORE\n 00 # [1733] STOP\n6110ad # [1734] PUSH2(0x10ad)\n 6002 # [1737] PUSH1(2)\n 55 # [1739] SSTORE\n 00 # [1740] STOP\n6110ae # [1741] PUSH2(0x10ae)\n 6002 # [1744] PUSH1(2)\n 55 # [1746] SSTORE\n 00 # [1747] STOP\n6110af # [1748] PUSH2(0x10af)\n 6002 # [1751] PUSH1(2)\n 55 # [1753] SSTORE\n 00 # [1754] STOP\n6110b0 # [1755] PUSH2(0x10b0)\n 6002 # [1758] PUSH1(2)\n 55 # [1760] SSTORE\n 00 # [1761] STOP\n6110b1 # [1762] PUSH2(0x10b1)\n 6002 # [1765] PUSH1(2)\n 55 # [1767] SSTORE\n 00 # [1768] STOP\n6110b2 # [1769] PUSH2(0x10b2)\n 6002 # [1772] PUSH1(2)\n 55 # [1774] SSTORE\n 00 # [1775] STOP\n6110b3 # [1776] PUSH2(0x10b3)\n 6002 # [1779] PUSH1(2)\n 55 # [1781] SSTORE\n 00 # [1782] STOP\n6110b4 # [1783] PUSH2(0x10b4)\n 6002 # [1786] PUSH1(2)\n 55 # [1788] SSTORE\n 00 # [1789] STOP\n6110b5 # [1790] PUSH2(0x10b5)\n 6002 # [1793] PUSH1(2)\n 55 # [1795] SSTORE\n 00 # [1796] STOP\n6110b6 # [1797] PUSH2(0x10b6)\n 6002 # [1800] PUSH1(2)\n 55 # [1802] SSTORE\n 00 # [1803] STOP\n6110b7 # [1804] PUSH2(0x10b7)\n 6002 # [1807] PUSH1(2)\n 55 # [1809] SSTORE\n 00 # [1810] STOP\n6110b8 # [1811] PUSH2(0x10b8)\n 6002 # [1814] PUSH1(2)\n 55 # [1816] SSTORE\n 00 # [1817] STOP\n6110b9 # [1818] PUSH2(0x10b9)\n 6002 # [1821] PUSH1(2)\n 55 # [1823] SSTORE\n 00 # [1824] STOP\n6110ba # [1825] PUSH2(0x10ba)\n 6002 # [1828] PUSH1(2)\n 55 # [1830] SSTORE\n 00 # [1831] STOP\n6110bb # [1832] PUSH2(0x10bb)\n 6002 # [1835] PUSH1(2)\n 55 # [1837] SSTORE\n 00 # [1838] STOP\n6110bc # [1839] PUSH2(0x10bc)\n 6002 # [1842] PUSH1(2)\n 55 # [1844] SSTORE\n 00 # [1845] STOP\n6110bd # [1846] PUSH2(0x10bd)\n 6002 # [1849] PUSH1(2)\n 55 # [1851] SSTORE\n 00 # [1852] STOP\n6110be # [1853] PUSH2(0x10be)\n 6002 # [1856] PUSH1(2)\n 55 # [1858] SSTORE\n 00 # [1859] STOP\n6110bf # [1860] PUSH2(0x10bf)\n 6002 # [1863] PUSH1(2)\n 55 # [1865] SSTORE\n 00 # [1866] STOP\n6110c0 # [1867] PUSH2(0x10c0)\n 6002 # [1870] PUSH1(2)\n 55 # [1872] SSTORE\n 00 # [1873] STOP\n6110c1 # [1874] PUSH2(0x10c1)\n 6002 # [1877] PUSH1(2)\n 55 # [1879] SSTORE\n 00 # [1880] STOP\n6110c2 # [1881] PUSH2(0x10c2)\n 6002 # [1884] PUSH1(2)\n 55 # [1886] SSTORE\n 00 # [1887] STOP\n6110c3 # [1888] PUSH2(0x10c3)\n 6002 # [1891] PUSH1(2)\n 55 # [1893] SSTORE\n 00 # [1894] STOP\n6110c4 # [1895] PUSH2(0x10c4)\n 6002 # [1898] PUSH1(2)\n 55 # [1900] SSTORE\n 00 # [1901] STOP\n6110c5 # [1902] PUSH2(0x10c5)\n 6002 # [1905] PUSH1(2)\n 55 # [1907] SSTORE\n 00 # [1908] STOP\n6110c6 # [1909] PUSH2(0x10c6)\n 6002 # [1912] PUSH1(2)\n 55 # [1914] SSTORE\n 00 # [1915] STOP\n6110c7 # [1916] PUSH2(0x10c7)\n 6002 # [1919] PUSH1(2)\n 55 # [1921] SSTORE\n 00 # [1922] STOP\n6110c8 # [1923] PUSH2(0x10c8)\n 6002 # [1926] PUSH1(2)\n 55 # [1928] SSTORE\n 00 # [1929] STOP\n6110c9 # [1930] PUSH2(0x10c9)\n 6002 # [1933] PUSH1(2)\n 55 # [1935] SSTORE\n 00 # [1936] STOP\n6110ca # [1937] PUSH2(0x10ca)\n 6002 # [1940] PUSH1(2)\n 55 # [1942] SSTORE\n 00 # [1943] STOP\n6110cb # [1944] PUSH2(0x10cb)\n 6002 # [1947] PUSH1(2)\n 55 # [1949] SSTORE\n 00 # [1950] STOP\n6110cc # [1951] PUSH2(0x10cc)\n 6002 # [1954] PUSH1(2)\n 55 # [1956] SSTORE\n 00 # [1957] STOP\n6110cd # [1958] PUSH2(0x10cd)\n 6002 # [1961] PUSH1(2)\n 55 # [1963] SSTORE\n 00 # [1964] STOP\n6110ce # [1965] PUSH2(0x10ce)\n 6002 # [1968] PUSH1(2)\n 55 # [1970] SSTORE\n 00 # [1971] STOP\n6110cf # [1972] PUSH2(0x10cf)\n 6002 # [1975] PUSH1(2)\n 55 # [1977] SSTORE\n 00 # [1978] STOP\n6110d0 # [1979] PUSH2(0x10d0)\n 6002 # [1982] PUSH1(2)\n 55 # [1984] SSTORE\n 00 # [1985] STOP\n6110d1 # [1986] PUSH2(0x10d1)\n 6002 # [1989] PUSH1(2)\n 55 # [1991] SSTORE\n 00 # [1992] STOP\n6110d2 # [1993] PUSH2(0x10d2)\n 6002 # [1996] PUSH1(2)\n 55 # [1998] SSTORE\n 00 # [1999] STOP\n6110d3 # [2000] PUSH2(0x10d3)\n 6002 # [2003] PUSH1(2)\n 55 # [2005] SSTORE\n 00 # [2006] STOP\n6110d4 # [2007] PUSH2(0x10d4)\n 6002 # [2010] PUSH1(2)\n 55 # [2012] SSTORE\n 00 # [2013] STOP\n6110d5 # [2014] PUSH2(0x10d5)\n 6002 # [2017] PUSH1(2)\n 55 # [2019] SSTORE\n 00 # [2020] STOP\n6110d6 # [2021] PUSH2(0x10d6)\n 6002 # [2024] PUSH1(2)\n 55 # [2026] SSTORE\n 00 # [2027] STOP\n6110d7 # [2028] PUSH2(0x10d7)\n 6002 # [2031] PUSH1(2)\n 55 # [2033] SSTORE\n 00 # [2034] STOP\n6110d8 # [2035] PUSH2(0x10d8)\n 6002 # [2038] PUSH1(2)\n 55 # [2040] SSTORE\n 00 # [2041] STOP\n6110d9 # [2042] PUSH2(0x10d9)\n 6002 # [2045] PUSH1(2)\n 55 # [2047] SSTORE\n 00 # [2048] STOP\n6110da # [2049] PUSH2(0x10da)\n 6002 # [2052] PUSH1(2)\n 55 # [2054] SSTORE\n 00 # [2055] STOP\n6110db # [2056] PUSH2(0x10db)\n 6002 # [2059] PUSH1(2)\n 55 # [2061] SSTORE\n 00 # [2062] STOP\n6110dc # [2063] PUSH2(0x10dc)\n 6002 # [2066] PUSH1(2)\n 55 # [2068] SSTORE\n 00 # [2069] STOP\n6110dd # [2070] PUSH2(0x10dd)\n 6002 # [2073] PUSH1(2)\n 55 # [2075] SSTORE\n 00 # [2076] STOP\n6110de # [2077] PUSH2(0x10de)\n 6002 # [2080] PUSH1(2)\n 55 # [2082] SSTORE\n 00 # [2083] STOP\n6110df # [2084] PUSH2(0x10df)\n 6002 # [2087] PUSH1(2)\n 55 # [2089] SSTORE\n 00 # [2090] STOP\n6110e0 # [2091] PUSH2(0x10e0)\n 6002 # [2094] PUSH1(2)\n 55 # [2096] SSTORE\n 00 # [2097] STOP\n6110e1 # [2098] PUSH2(0x10e1)\n 6002 # [2101] PUSH1(2)\n 55 # [2103] SSTORE\n 00 # [2104] STOP\n6110e2 # [2105] PUSH2(0x10e2)\n 6002 # [2108] PUSH1(2)\n 55 # [2110] SSTORE\n 00 # [2111] STOP\n6110e3 # [2112] PUSH2(0x10e3)\n 6002 # [2115] PUSH1(2)\n 55 # [2117] SSTORE\n 00 # [2118] STOP\n6110e4 # [2119] PUSH2(0x10e4)\n 6002 # [2122] PUSH1(2)\n 55 # [2124] SSTORE\n 00 # [2125] STOP\n6110e5 # [2126] PUSH2(0x10e5)\n 6002 # [2129] PUSH1(2)\n 55 # [2131] SSTORE\n 00 # [2132] STOP\n6110e6 # [2133] PUSH2(0x10e6)\n 6002 # [2136] PUSH1(2)\n 55 # [2138] SSTORE\n 00 # [2139] STOP\n6110e7 # [2140] PUSH2(0x10e7)\n 6002 # [2143] PUSH1(2)\n 55 # [2145] SSTORE\n 00 # [2146] STOP\n6110e8 # [2147] PUSH2(0x10e8)\n 6002 # [2150] PUSH1(2)\n 55 # [2152] SSTORE\n 00 # [2153] STOP\n6110e9 # [2154] PUSH2(0x10e9)\n 6002 # [2157] PUSH1(2)\n 55 # [2159] SSTORE\n 00 # [2160] STOP\n6110ea # [2161] PUSH2(0x10ea)\n 6002 # [2164] PUSH1(2)\n 55 # [2166] SSTORE\n 00 # [2167] STOP\n6110eb # [2168] PUSH2(0x10eb)\n 6002 # [2171] PUSH1(2)\n 55 # [2173] SSTORE\n 00 # [2174] STOP\n6110ec # [2175] PUSH2(0x10ec)\n 6002 # [2178] PUSH1(2)\n 55 # [2180] SSTORE\n 00 # [2181] STOP\n6110ed # [2182] PUSH2(0x10ed)\n 6002 # [2185] PUSH1(2)\n 55 # [2187] SSTORE\n 00 # [2188] STOP\n6110ee # [2189] PUSH2(0x10ee)\n 6002 # [2192] PUSH1(2)\n 55 # [2194] SSTORE\n 00 # [2195] STOP\n6110ef # [2196] PUSH2(0x10ef)\n 6002 # [2199] PUSH1(2)\n 55 # [2201] SSTORE\n 00 # [2202] STOP\n6110f0 # [2203] PUSH2(0x10f0)\n 6002 # [2206] PUSH1(2)\n 55 # [2208] SSTORE\n 00 # [2209] STOP\n6110f1 # [2210] PUSH2(0x10f1)\n 6002 # [2213] PUSH1(2)\n 55 # [2215] SSTORE\n 00 # [2216] STOP\n6110f2 # [2217] PUSH2(0x10f2)\n 6002 # [2220] PUSH1(2)\n 55 # [2222] SSTORE\n 00 # [2223] STOP\n6110f3 # [2224] PUSH2(0x10f3)\n 6002 # [2227] PUSH1(2)\n 55 # [2229] SSTORE\n 00 # [2230] STOP\n6110f4 # [2231] PUSH2(0x10f4)\n 6002 # [2234] PUSH1(2)\n 55 # [2236] SSTORE\n 00 # [2237] STOP\n6110f5 # [2238] PUSH2(0x10f5)\n 6002 # [2241] PUSH1(2)\n 55 # [2243] SSTORE\n 00 # [2244] STOP\n6110f6 # [2245] PUSH2(0x10f6)\n 6002 # [2248] PUSH1(2)\n 55 # [2250] SSTORE\n 00 # [2251] STOP\n6110f7 # [2252] PUSH2(0x10f7)\n 6002 # [2255] PUSH1(2)\n 55 # [2257] SSTORE\n 00 # [2258] STOP\n6110f8 # [2259] PUSH2(0x10f8)\n 6002 # [2262] PUSH1(2)\n 55 # [2264] SSTORE\n 00 # [2265] STOP\n6110f9 # [2266] PUSH2(0x10f9)\n 6002 # [2269] PUSH1(2)\n 55 # [2271] SSTORE\n 00 # [2272] STOP\n6110fa # [2273] PUSH2(0x10fa)\n 6002 # [2276] PUSH1(2)\n 55 # [2278] SSTORE\n 00 # [2279] STOP\n6110fb # [2280] PUSH2(0x10fb)\n 6002 # [2283] PUSH1(2)\n 55 # [2285] SSTORE\n 00 # [2286] STOP\n6110fc # [2287] PUSH2(0x10fc)\n 6002 # [2290] PUSH1(2)\n 55 # [2292] SSTORE\n 00 # [2293] STOP\n6110fd # [2294] PUSH2(0x10fd)\n 6002 # [2297] PUSH1(2)\n 55 # [2299] SSTORE\n 00 # [2300] STOP\n6110fe # [2301] PUSH2(0x10fe)\n 6002 # [2304] PUSH1(2)\n 55 # [2306] SSTORE\n 00 # [2307] STOP\n6110ff # [2308] PUSH2(0x10ff)\n 6002 # [2311] PUSH1(2)\n 55 # [2313] SSTORE\n 00 # [2314] STOP\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json new file mode 100644 index 00000000000..08a7df4965b --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xEF0001010004020001001304000000008000026000e20200030000fff65b5b00600160015500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 0013 # Code section 0 , 19 bytes\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n 6000 # [0] PUSH1(0)\ne20200030000fff6 # [2] RJUMPV(3,0,-10)\n 5b # [10] NOOP\n 5b # [11] NOOP\n 00 # [12] STOP\n 6001 # [13] PUSH1(1)\n 6001 # [15] PUSH1(1)\n 55 # [17] SSTORE\n 00 # [18] STOP\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json new file mode 100644 index 00000000000..e277ea73e2b --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xef000101000402000100130300010043040000000080000436600060003736600060006000ec0060005500ef0001010004020001000b03000100200400000000800003366000600037366000ee00ef0001010004020001000d0400400000800002d10000600055d1002060015500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 0013 # Code section 0 , 19 bytes\n030001 # Total subcontainers ( 1 )\n 0043 # Sub container 0, 67 byte\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0004 # max stack: 4\n # Code section 0 - in=0 out=non-returning height=4\n 36 # [0] CALLDATASIZE\n 6000 # [1] PUSH1(0)\n 6000 # [3] PUSH1(0)\n 37 # [5] CALLDATACOPY\n 36 # [6] CALLDATASIZE\n 6000 # [7] PUSH1(0)\n 6000 # [9] PUSH1(0)\n 6000 # [11] PUSH1(0)\n ec00 # [13] EOFCREATE(0)\n 6000 # [15] PUSH1(0)\n 55 # [17] SSTORE\n 00 # [18] STOP\n # Subcontainer 0 starts here\n ef0001 # Magic and Version ( 1 )\n 010004 # Types length ( 4 )\n 020001 # Total code sections ( 1 )\n 000b # Code section 0 , 11 bytes\n 030001 # Total subcontainers ( 1 )\n 0020 # Sub container 0, 32 byte\n 040000 # Data section length( 0 ) \n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0003 # max stack: 3\n # Code section 0 - in=0 out=non-returning height=3\n 36 # [0] CALLDATASIZE\n 6000 # [1] PUSH1(0)\n 6000 # [3] PUSH1(0)\n 37 # [5] CALLDATACOPY\n 36 # [6] CALLDATASIZE\n 6000 # [7] PUSH1(0)\n ee00 # [9] RETURNCONTRACT(0)\n # Subcontainer 0.0 starts here\n ef0001 # Magic and Version ( 1 )\n 010004 # Types length ( 4 )\n 020001 # Total code sections ( 1 )\n 000d # Code section 0 , 13 bytes\n 040040 # Data section length( 64 ) (actual size 0) \n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n d10000 # [0] DATALOADN(0x0000)\n 6000 # [3] PUSH1(0)\n 55 # [5] SSTORE\n d10020 # [6] DATALOADN(0x0020)\n 6001 # [9] PUSH1(1)\n 55 # [11] SSTORE\n 00 # [12] STOP\n # Data section (empty)\n # Subcontainer 0.0 ends\n # Data section (empty)\n # Subcontainer 0 ends\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json index de44541641d..ad87a8238f4 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json @@ -99,6 +99,6 @@ {"pc":81,"op":72,"gas":"0x79bc22","gasCost":"0x2","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b"],"depth":1,"refund":0,"opName":"BASEFEE"}, {"pc":82,"op":8,"gas":"0x79bc20","gasCost":"0x8","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b","0x10"],"depth":1,"refund":0,"opName":"ADDMOD"}, {"pc":83,"op":62,"gas":"0x79bc18","gasCost":"0x0","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0xb94f5374fce5edbc8e2a8697c15331677e6ebf1b"],"depth":1,"refund":0,"opName":"RETURNDATACOPY","error":"Out of bounds"}, - {"output":"","gasUsed":"0x7a1200","test":"00000936-mixed-1","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xd14c10ed22a1cfb642e374be985ac581c39f3969bd59249e0405aca3beb47a47","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false} + {"output":"","gasUsed":"0x7a1200","test":"00000936-mixed-1","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xd14c10ed22a1cfb642e374be985ac581c39f3969bd59249e0405aca3beb47a47","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"INVALID_RETURN_DATA_BUFFER_ACCESS"} ] } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json new file mode 100644 index 00000000000..4e8529872d2 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json @@ -0,0 +1,86 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "create-eof": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": null, + "data": [ + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0xdbbe" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Prague": [ + { + "hash": "0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3", + "logs": "0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ], + "Cancun": [ + { + "hash": "0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"pc":0,"section":0,"op":95,"gas":"0x794068","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":1,"section":0,"op":53,"gas":"0x794066","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"CALLDATALOAD"}, + {"pc":2,"section":0,"op":95,"gas":"0x794063","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":3,"section":0,"op":95,"gas":"0x794061","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":4,"section":0,"op":161,"gas":"0x79405f","gasCost":"0x2ee","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0","0x0"],"depth":1,"refund":0,"opName":"LOG1"}, + {"pc":5,"section":0,"op":95,"gas":"0x793d71","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":6,"section":0,"op":95,"gas":"0x793d6f","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":7,"section":0,"op":238,"immediate":"0x00","gas":"0x793d6d","gasCost":"0x0","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"RETURNCONTRACT"}, + {"output":"","gasUsed":"0xe433","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3","postLogsHash":"0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940","pass":true}, + {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json new file mode 100644 index 00000000000..dc786d61362 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json @@ -0,0 +1,78 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "create-eof": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": null, + "data": [ + "ef00011100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0xdbbe" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Prague": [ + { + "hash": "0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3", + "logs": "0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ], + "Cancun": [ + { + "hash": "0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"output":"","gasUsed":"0xd198","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x2a9c58298ba5d4ec86ca682b9fcc9ff67c3fc44dbd39f85a2f9b74bfe4e5178e","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"Invalid EOF Layout: Expected kind 1 but read kind 17"}, + {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json new file mode 100644 index 00000000000..bba2c851031 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json @@ -0,0 +1,25 @@ +{ + "cli": [ + "--notime", + "--json", + "--create", + "--code", + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "pragueeof" + ], + "stdin": "", + "stdout": [ + {"pc":0,"section":0,"op":95,"gas":"0x2540be400","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":1,"section":0,"op":53,"gas":"0x2540be3fe","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"CALLDATALOAD"}, + {"pc":2,"section":0,"op":95,"gas":"0x2540be3fb","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":3,"section":0,"op":95,"gas":"0x2540be3f9","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":4,"section":0,"op":161,"gas":"0x2540be3f7","gasCost":"0x2ee","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0","0x0"],"depth":1,"refund":0,"opName":"LOG1"}, + {"pc":5,"section":0,"op":95,"gas":"0x2540be109","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":6,"section":0,"op":95,"gas":"0x2540be107","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":7,"section":0,"op":238,"immediate":"0x00","gas":"0x2540be105","gasCost":"0x0","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"RETURNCONTRACT"}, + {"gasUser":"0x129b","gasTotal":"0x129b","output":"0x"} + ] +} \ No newline at end of file diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index 3558b5b7617..c023dcd699e 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -129,6 +129,21 @@ def generalstateRegressionReferenceTests = tasks.register("generalstateRegressio ) } +def eofReferenceTests = tasks.register("eofReferenceTests") { + final referenceTestsPath = "src/reference-test/external-resources/EOFTests" + final generatedTestsPath = "$buildDir/generated/sources/reference-test/$name/java" + inputs.files fileTree(referenceTestsPath), + fileTree(generatedTestsPath) + outputs.files generatedTestsPath + generateTestFiles( + fileTree(referenceTestsPath), + file("src/reference-test/templates/EOFReferenceTest.java.template"), + "EOFTests", + "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/eof", + "EOFReferenceTest" + ) +} + sourceSets { referenceTest { java { @@ -140,7 +155,8 @@ sourceSets { eipStateReferenceTests, executionSpecTests, generalstateReferenceTests, - generalstateRegressionReferenceTests + generalstateRegressionReferenceTests, + eofReferenceTests } resources { srcDirs 'src/reference-test/resources', @@ -247,24 +263,20 @@ def generateTestFiles(FileTree jsonPath, File templateFile, String pathstrip, St mkdir(destination) def referenceTestTemplate = templateFile.text - // This is how many json files to include in each test file - def fileSets = jsonPath.getFiles().collate(5) - - fileSets.eachWithIndex { fileSet, idx -> - def paths = [] - fileSet.each { testJsonFile -> - def parentFile = testJsonFile.getParentFile() - def parentPathFile = parentFile.getPath().substring(parentFile.getPath().indexOf(pathstrip)) - if (!testJsonFile.getName().toString().startsWith(".") && !excludedPath.contains(parentPathFile)) { - def pathFile = testJsonFile.getPath() - paths << pathFile.substring(pathFile.indexOf(pathstrip)) - } + def paths = [] + jsonPath.getFiles().forEach { testJsonFile -> + def parentFile = testJsonFile.getParentFile() + def parentPathFile = parentFile.getPath().substring(parentFile.getPath().indexOf(pathstrip)) + if (!testJsonFile.getName().toString().startsWith(".") && !excludedPath.contains(parentPathFile)) { + def pathFile = testJsonFile.getPath() + paths << pathFile.substring(pathFile.indexOf(pathstrip)) } + } + paths.collate(5).eachWithIndex { tests, idx -> def testFile = file(destination + "/" + namePrefix + "_" + idx + ".java") - - def allPaths = '"' + paths.join('", "') + '"' + def allPaths = '"' + tests.join('",\n "') + '"' def testFileContents = referenceTestTemplate .replaceAll("%%TESTS_FILE%%", allPaths) diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java new file mode 100644 index 00000000000..e67ec2091f0 --- /dev/null +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.referencetests; + +import java.util.NavigableMap; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class EOFTestCaseSpec { + + public record TestVector( + @JsonProperty("code") String code, + @JsonProperty("results") NavigableMap results) {} + + public record TestResult( + @JsonProperty("exception") String exception, @JsonProperty("result") boolean result) { + public static TestResult TEST_RESULT_PASSED = new TestResult(null, true); + + public static TestResult failed(final String exception) { + return new TestResult(exception, false); + } + + public static TestResult passed() { + return TEST_RESULT_PASSED; + } + } + + NavigableMap vector; + + @JsonCreator + public EOFTestCaseSpec(@JsonProperty("vectors") final NavigableMap vector) { + this.vector = vector; + } + + public NavigableMap getVector() { + return vector; + } +} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java index 5c650ecaaec..2ebf5f79969 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java @@ -86,7 +86,7 @@ public static ReferenceTestProtocolSchedules create(final StubGenesisConfigOptio builder.put( "CancunToPragueAtTime15k", createSchedule(genesisStub.clone().cancunTime(0).pragueTime(15000))); - builder.put("Prague", createSchedule(genesisStub.clone().pragueTime(0))); + builder.put("Prague", createSchedule(genesisStub.clone().pragueEOFTime(0))); builder.put("Future_EIPs", createSchedule(genesisStub.clone().futureEipsTime(0))); builder.put("Experimental_EIPs", createSchedule(genesisStub.clone().experimentalEipsTime(0))); return new ReferenceTestProtocolSchedules(builder.build()); diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java index 3334c926eec..9748d2aa030 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java @@ -111,7 +111,7 @@ public StateTestVersionedTransaction( this.maxFeePerGas = Optional.ofNullable(maxFeePerGas).map(Wei::fromHexString).orElse(null); this.maxPriorityFeePerGas = Optional.ofNullable(maxPriorityFeePerGas).map(Wei::fromHexString).orElse(null); - this.to = to.isEmpty() ? null : Address.fromHexString(to); + this.to = (to == null || to.isEmpty()) ? null : Address.fromHexString(to); SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); this.keys = diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java new file mode 100644 index 00000000000..cf7ce66b076 --- /dev/null +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java @@ -0,0 +1,142 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.eof; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; +import org.hyperledger.besu.evm.code.CodeV1Validation; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.testutil.JsonTestParameters; + +public class EOFReferenceTestTools { + private static final List EIPS_TO_RUN; + + static { + final String eips = + System.getProperty("test.ethereum.eof.eips", "Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); + EIPS_TO_RUN = Arrays.asList(eips.split(",")); + } + + private static final JsonTestParameters params = + JsonTestParameters.create(EOFTestCaseSpec.class, EOFTestCaseSpec.TestResult.class) + .generator( + (testName, fullPath, eofSpec, collector) -> { + final Path path = Path.of(fullPath).getParent().getFileName(); + final String prefix = path + "/" + testName + "-"; + for (final Map.Entry entry : + eofSpec.getVector().entrySet()) { + final String name = entry.getKey(); + final Bytes code = Bytes.fromHexString(entry.getValue().code()); + for (final var result : entry.getValue().results().entrySet()) { + final String eip = result.getKey(); + final boolean runTest = EIPS_TO_RUN.contains(eip); + collector.add( + prefix + eip + '[' + name + ']', + fullPath, + eip, + code, + result.getValue(), + runTest); + } + } + }); + + static { + if (EIPS_TO_RUN.isEmpty()) { + params.ignoreAll(); + } + + // TXCREATE still in tests, but has been removed + params.ignore("EOF1_undefined_opcodes_186"); + } + + private EOFReferenceTestTools() { + // utility class + } + + // + public static Collection generateTestParametersForConfig(final String[] filePath) { + return params.generate(filePath); + } + + public static void executeTest( + final String fork, final Bytes code, final EOFTestCaseSpec.TestResult expected) { + EvmSpecVersion evmVersion = EvmSpecVersion.fromName(fork); + assertThat(evmVersion).isNotNull(); + + // hardwire in the magic byte transaction checks + if (evmVersion.getMaxEofVersion() < 1) { + assertThat(expected.exception()).isEqualTo("EOF_InvalidCode"); + } else { + EOFLayout layout = EOFLayout.parseEOF(code); + + if (layout.isValid()) { + Code parsedCode = CodeFactory.createCode(code, evmVersion.getMaxEofVersion()); + assertThat(parsedCode.isValid()) + .withFailMessage( + () -> + EOFLayout.parseEOF(code).prettyPrint() + + "\nExpected exception :" + + expected.exception() + + " actual exception :" + + (parsedCode.isValid() + ? null + : ((CodeInvalid) parsedCode).getInvalidReason())) + .isEqualTo(expected.result()); + if (parsedCode instanceof CodeV1 codeV1) { + var deepValidate = CodeV1Validation.validate(codeV1.getEofLayout()); + assertThat(deepValidate) + .withFailMessage( + () -> + codeV1.prettyPrint() + + "\nExpected exception :" + + expected.exception() + + " actual exception :" + + (parsedCode.isValid() ? null : deepValidate)) + .isNull(); + } + + if (expected.result()) { + System.out.println(code); + System.out.println(layout.writeContainer(null)); + assertThat(code) + .withFailMessage("Container round trip failed") + .isEqualTo(layout.writeContainer(null)); + } + } else { + assertThat(layout.isValid()) + .withFailMessage( + () -> + "Expected exception - " + + expected.exception() + + " actual exception - " + + (layout.isValid() ? null : layout.invalidReason())) + .isEqualTo(expected.result()); + } + } + } +} diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java index dc32da71889..5512923c613 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java @@ -53,9 +53,9 @@ public class BlockchainReferenceTestTools { final String networks = System.getProperty( "test.ethereum.blockchain.eips", - "FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5," + "FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5,CancunToPragueAtTime15k" + "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix,Istanbul,Berlin," - + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Bogota,CancunToPragueAtTime15k"); + + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); NETWORKS_TO_RUN = Arrays.asList(networks.split(",")); } @@ -75,21 +75,22 @@ public class BlockchainReferenceTestTools { // Consumes a huge amount of memory params.ignore("static_Call1MB1024Calldepth_d1g0v0_\\w+"); - params.ignore("ShanghaiLove_.*"); + params.ignore("ShanghaiLove_"); // Absurd amount of gas, doesn't run in parallel params.ignore("randomStatetest94_\\w+"); // Don't do time-consuming tests - params.ignore("CALLBlake2f_MaxRounds.*"); - params.ignore("loopMul_*"); + params.ignore("CALLBlake2f_MaxRounds"); + params.ignore("loopMul_"); // Inconclusive fork choice rule, since in merge CL should be choosing forks and setting the // chain head. // Perfectly valid test pre-merge. - params.ignore("UncleFromSideChain_(Merge|Paris|Shanghai|Cancun|Prague|Osaka|Bogota)"); + params.ignore( + "UncleFromSideChain_(Merge|Paris|Shanghai|Cancun|Prague|Osaka|Amsterdam|Bogota|Polis|Bangkok)"); - // EOF tests are written against an older version of the spec + // EOF tests don't have Prague stuff like deopsits right now params.ignore("/stEOF/"); // None of the Prague tests have withdrawls and deposits handling diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java index 20cc4710dcc..f948a5b24e3 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java @@ -66,7 +66,7 @@ private static ProtocolSpec protocolSpec(final String name) { System.getProperty( "test.ethereum.state.eips", "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix,Istanbul,Berlin," - + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Bogota"); + + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); EIPS_TO_RUN = Arrays.asList(eips.split(",")); } diff --git a/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template index f8b99e8a735..387fb0cc189 100644 --- a/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template +++ b/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template @@ -16,20 +16,20 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** The blockchain test operation testing framework entry point. */ public class %%TESTS_NAME%% { - private static final String[] TEST_CONFIG_FILE_DIR_PATH = new String[] {%%TESTS_FILE%%}; + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; public static Stream getTestParametersForConfig() { - return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> - Arguments.of(params[0], params[1], params[2]) - ); + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void execution( - final String name, - final BlockchainReferenceTestCaseSpec spec, - final boolean runTest) { + final String name, final BlockchainReferenceTestCaseSpec spec, final boolean runTest) { assumeTrue(runTest, "Test " + name + " was ignored"); executeTest(spec); } diff --git a/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template new file mode 100644 index 00000000000..5f5cafd6760 --- /dev/null +++ b/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template @@ -0,0 +1,42 @@ +package org.hyperledger.besu.ethereum.vm.eof; + +import static org.hyperledger.besu.ethereum.eof.EOFReferenceTestTools.executeTest; +import static org.hyperledger.besu.ethereum.eof.EOFReferenceTestTools.generateTestParametersForConfig; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** The general state test operation testing framework entry point. */ +public class %%TESTS_NAME%% { + + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; + + public static Stream getTestParametersForConfig() { + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(Arguments::of); + } + + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void execution( + final String name, + final String fork, + final Bytes code, + final EOFTestCaseSpec.TestResult results, + final boolean runTest) { + assumeTrue(runTest, "Test " + name + " was ignored"); + executeTest(fork, code, results); + } +} diff --git a/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template index 4b93b877334..3d5976aff2b 100644 --- a/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template +++ b/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template @@ -17,20 +17,20 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** The general state test operation testing framework entry point. */ public class %%TESTS_NAME%% { - private static final String[] TEST_CONFIG_FILE_DIR_PATH = new String[] {%%TESTS_FILE%%}; + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; public static Stream getTestParametersForConfig() { - return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> - Arguments.of(params[0], params[1], params[2]) - ); + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void execution( - final String name, - final GeneralStateTestCaseEipSpec spec, - final boolean runTest) { + final String name, final GeneralStateTestCaseEipSpec spec, final boolean runTest) { assumeTrue(runTest, "Test " + name + " was ignored"); executeTest(spec); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/Code.java b/evm/src/main/java/org/hyperledger/besu/evm/Code.java index 3ccb87c3a10..bcec2872498 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/Code.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/Code.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.code.CodeSection; +import java.util.Optional; + import org.apache.tuweni.bytes.Bytes; /** Represents EVM code associated with an account. */ @@ -30,6 +32,13 @@ public interface Code { */ int getSize(); + /** + * Size of the data in bytes. This is for the data only, + * + * @return size of code in bytes. + */ + int getDataSize(); + /** * Get the bytes for the entire container, for example what EXTCODECOPY would want. For V0 it is * the same as getCodeBytes, for V1 it is the entire container, not just the data section. @@ -82,4 +91,63 @@ public interface Code { * @return The version of hte ode. */ int getEofVersion(); + + /** + * Returns the count of subcontainers, or zero if there are none or if the code version does not + * support subcontainers. + * + * @return The subcontainer count or zero if not supported; + */ + int getSubcontainerCount(); + + /** + * Returns the subcontainer at the selected index. If the container doesn't exist or is invalid, + * an empty result is returned. Legacy code always returns empty. + * + * @param index the index in the container to return + * @param auxData any Auxiliary data to append to the subcontainer code. If fetching an initcode + * container, pass null. + * @return Either the subcontainer, or empty. + */ + Optional getSubContainer(final int index, final Bytes auxData); + + /** + * Loads data from the appropriate data section + * + * @param offset Where within the data section to start copying + * @param length how many bytes to copy + * @return A slice of the code containing the requested data + */ + Bytes getData(final int offset, final int length); + + /** + * Read a signed 16-bit big-endian integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 16-bit signed integer. + */ + int readBigEndianI16(final int startIndex); + + /** + * Read an unsigned 16 bit big-endian integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 16-bit unsigned integer. + */ + int readBigEndianU16(final int startIndex); + + /** + * Read an unsigned 8-bit integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 8-bit unsigned integer. + */ + int readU8(final int startIndex); + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @return The pretty printed code + */ + String prettyPrint(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index debdb5e9b14..03348d9ab78 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -365,6 +365,16 @@ public Code getCode(final Hash codeHash, final Bytes codeBytes) { * @return the code */ public Code getCodeUncached(final Bytes codeBytes) { - return CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion(), false); + return CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion()); + } + + /** + * Gets code for creation. Skips code cache and allows for extra data after EOF contracts. + * + * @param codeBytes the code bytes + * @return the code + */ + public Code getCodeForCreation(final Bytes codeBytes) { + return CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion(), false, true); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java index 3743c82c485..e543f672df0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java @@ -50,10 +50,18 @@ public enum EvmSpecVersion { CANCUN(0, true, "Cancun", "Finalized"), /** Prague evm spec version. */ PRAGUE(0, false, "Prague", "In Development"), + /** PragueEOF evm spec version. */ + PRAGUE_EOF(1, false, "PragueEOF", "Prague + EOF. In Development"), /** Osaka evm spec version. */ - OSAKA(0, false, "Osaka", "Placeholder"), + OSAKA(1, false, "Osaka", "Placeholder"), + /** Amstedam evm spec version. */ + AMSTERDAM(1, false, "Amsterdam", "Placeholder"), /** Bogota evm spec version. */ - BOGOTA(0, false, "Bogota", "Placeholder"), + BOGOTA(1, false, "Bogota", "Placeholder"), + /** Polis evm spec version. */ + POLIS(1, false, "Polis", "Placeholder"), + /** Bogota evm spec version. */ + BANGKOK(1, false, "Bangkok", "Placeholder"), /** Development fork for unscheduled EIPs */ FUTURE_EIPS(1, false, "Future_EIPs", "Development, for accepted and unscheduled EIPs"), /** Development fork for EIPs not accepted to Mainnet */ @@ -146,6 +154,10 @@ public void maybeWarnVersion() { * @return the EVM spec version for that fork, or null if no fork matched. */ public static EvmSpecVersion fromName(final String name) { + // TODO remove once PragueEOF settles + if ("prague".equalsIgnoreCase(name)) { + return EvmSpecVersion.PRAGUE_EOF; + } for (var version : EvmSpecVersion.values()) { if (version.name().equalsIgnoreCase(name)) { return version; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index adc26009eaf..11ce3546a8a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; @@ -53,15 +54,25 @@ import org.hyperledger.besu.evm.operation.CoinbaseOperation; import org.hyperledger.besu.evm.operation.Create2Operation; import org.hyperledger.besu.evm.operation.CreateOperation; +import org.hyperledger.besu.evm.operation.DataCopyOperation; +import org.hyperledger.besu.evm.operation.DataLoadNOperation; +import org.hyperledger.besu.evm.operation.DataLoadOperation; +import org.hyperledger.besu.evm.operation.DataSizeOperation; import org.hyperledger.besu.evm.operation.DelegateCallOperation; import org.hyperledger.besu.evm.operation.DifficultyOperation; import org.hyperledger.besu.evm.operation.DivOperation; +import org.hyperledger.besu.evm.operation.DupNOperation; import org.hyperledger.besu.evm.operation.DupOperation; +import org.hyperledger.besu.evm.operation.EOFCreateOperation; import org.hyperledger.besu.evm.operation.EqOperation; +import org.hyperledger.besu.evm.operation.ExchangeOperation; import org.hyperledger.besu.evm.operation.ExpOperation; +import org.hyperledger.besu.evm.operation.ExtCallOperation; import org.hyperledger.besu.evm.operation.ExtCodeCopyOperation; import org.hyperledger.besu.evm.operation.ExtCodeHashOperation; import org.hyperledger.besu.evm.operation.ExtCodeSizeOperation; +import org.hyperledger.besu.evm.operation.ExtDelegateCallOperation; +import org.hyperledger.besu.evm.operation.ExtStaticCallOperation; import org.hyperledger.besu.evm.operation.GasLimitOperation; import org.hyperledger.besu.evm.operation.GasOperation; import org.hyperledger.besu.evm.operation.GasPriceOperation; @@ -69,6 +80,7 @@ import org.hyperledger.besu.evm.operation.InvalidOperation; import org.hyperledger.besu.evm.operation.IsZeroOperation; import org.hyperledger.besu.evm.operation.JumpDestOperation; +import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.JumpiOperation; import org.hyperledger.besu.evm.operation.Keccak256Operation; @@ -96,7 +108,9 @@ import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.operation.ReturnContractOperation; import org.hyperledger.besu.evm.operation.ReturnDataCopyOperation; +import org.hyperledger.besu.evm.operation.ReturnDataLoadOperation; import org.hyperledger.besu.evm.operation.ReturnDataSizeOperation; import org.hyperledger.besu.evm.operation.ReturnOperation; import org.hyperledger.besu.evm.operation.RevertOperation; @@ -115,6 +129,7 @@ import org.hyperledger.besu.evm.operation.StaticCallOperation; import org.hyperledger.besu.evm.operation.StopOperation; import org.hyperledger.besu.evm.operation.SubOperation; +import org.hyperledger.besu.evm.operation.SwapNOperation; import org.hyperledger.besu.evm.operation.SwapOperation; import org.hyperledger.besu.evm.operation.TLoadOperation; import org.hyperledger.besu.evm.operation.TStoreOperation; @@ -952,6 +967,107 @@ public static void registerPragueOperations( // TODO add EOF operations here once PragueEOF is collapsed into Prague } + /** + * PragueEOF evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF(final EvmConfiguration evmConfiguration) { + return pragueEOF(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * PragueEOF evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return pragueEOF(new PragueEOFGasCalculator(), chainId, evmConfiguration); + } + + /** + * PragueEOF evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + pragueEOFOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.PRAGUE_EOF); + } + + /** + * Operation registry for PragueEOF's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry pragueEOFOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerPragueEOFOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register PragueEOF's operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerPragueEOFOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerPragueOperations(registry, gasCalculator, chainID); + + // EIP-663 Unlimited Swap and Dup + registry.put(new DupNOperation(gasCalculator)); + registry.put(new SwapNOperation(gasCalculator)); + registry.put(new ExchangeOperation(gasCalculator)); + + // EIP-4200 relative jump + registry.put(new RelativeJumpOperation(gasCalculator)); + registry.put(new RelativeJumpIfOperation(gasCalculator)); + registry.put(new RelativeJumpVectorOperation(gasCalculator)); + + // EIP-4750 EOF Code Sections + registry.put(new CallFOperation(gasCalculator)); + registry.put(new RetFOperation(gasCalculator)); + + // EIP-6209 JUMPF Instruction + registry.put(new JumpFOperation(gasCalculator)); + + // EIP-7069 Revamped EOF Call + registry.put(new ExtCallOperation(gasCalculator)); + registry.put(new ExtDelegateCallOperation(gasCalculator)); + registry.put(new ExtStaticCallOperation(gasCalculator)); + registry.put(new ReturnDataLoadOperation(gasCalculator)); + + // EIP-7480 EOF Data Section Access + registry.put(new DataLoadOperation(gasCalculator)); + registry.put(new DataLoadNOperation(gasCalculator)); + registry.put(new DataSizeOperation(gasCalculator)); + registry.put(new DataCopyOperation(gasCalculator)); + + // EIP-7620 EOF Create and Return Contract operation + registry.put(new EOFCreateOperation(gasCalculator)); + registry.put(new ReturnContractOperation(gasCalculator)); + } + /** * Osaka evm. * @@ -1017,7 +1133,75 @@ public static void registerOsakaOperations( final OperationRegistry registry, final GasCalculator gasCalculator, final BigInteger chainID) { - registerPragueOperations(registry, gasCalculator, chainID); + registerPragueEOFOperations(registry, gasCalculator, chainID); + } + + /** + * Amsterdam evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam(final EvmConfiguration evmConfiguration) { + return amsterdam(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Amsterdam evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return amsterdam(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Amsterdam evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + amsterdamOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.AMSTERDAM); + } + + /** + * Operation registry for amsterdam's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry amsterdamOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerAmsterdamOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register amsterdam operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerAmsterdamOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerOsakaOperations(registry, gasCalculator, chainID); } /** @@ -1085,7 +1269,143 @@ public static void registerBogotaOperations( final OperationRegistry registry, final GasCalculator gasCalculator, final BigInteger chainID) { - registerOsakaOperations(registry, gasCalculator, chainID); + registerAmsterdamOperations(registry, gasCalculator, chainID); + } + + /** + * Polis evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis(final EvmConfiguration evmConfiguration) { + return polis(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Polis evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return polis(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Polis evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + polisOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.POLIS); + } + + /** + * Operation registry for Polis's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry polisOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerPolisOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register polis operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerPolisOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerBogotaOperations(registry, gasCalculator, chainID); + } + + /** + * Bangkok evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok(final EvmConfiguration evmConfiguration) { + return bangkok(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Bangkok evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return bangkok(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Bangkok evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + bangkokOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.BANGKOK); + } + + /** + * Operation registry for bangkok's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry bangkokOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerBangkokOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register bangkok operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerBangkokOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerPolisOperations(registry, gasCalculator, chainID); } /** @@ -1154,13 +1474,6 @@ public static void registerFutureEipsOperations( final GasCalculator gasCalculator, final BigInteger chainID) { registerBogotaOperations(registry, gasCalculator, chainID); - - // "big" EOF - registry.put(new RelativeJumpOperation(gasCalculator)); - registry.put(new RelativeJumpIfOperation(gasCalculator)); - registry.put(new RelativeJumpVectorOperation(gasCalculator)); - registry.put(new CallFOperation(gasCalculator)); - registry.put(new RetFOperation(gasCalculator)); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java index a85754fb5bd..f9a85a20b70 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java @@ -14,8 +14,13 @@ */ package org.hyperledger.besu.evm.code; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE; + import org.hyperledger.besu.evm.Code; +import javax.annotation.Nonnull; + +import com.google.errorprone.annotations.InlineMe; import org.apache.tuweni.bytes.Bytes; /** The Code factory. */ @@ -33,24 +38,57 @@ private CodeFactory() { * * @param bytes the bytes * @param maxEofVersion the max eof version - * @param inCreateOperation the in create operation + * @return the code + */ + public static Code createCode(final Bytes bytes, final int maxEofVersion) { + return createCode(bytes, maxEofVersion, false, false); + } + + /** + * Create Code. + * + * @param bytes the bytes + * @param maxEofVersion the max eof version + * @param legacyCreation Allow some corner cases. `EF` and not `EF00` code + * @deprecated use the no boolean or two boolean variant + * @return the code + */ + @Deprecated(since = "24.4.1") + @InlineMe( + replacement = "CodeFactory.createCode(bytes, maxEofVersion, legacyCreation, false)", + imports = "org.hyperledger.besu.evm.code.CodeFactory") + public static Code createCode( + final Bytes bytes, final int maxEofVersion, final boolean legacyCreation) { + return createCode(bytes, maxEofVersion, legacyCreation, false); + } + + /** + * Create Code. + * + * @param bytes the bytes + * @param maxEofVersion the max eof version + * @param legacyCreation Allow some corner cases. `EF` and not `EF00` code + * @param createTransaction This is in a create transaction, allow dangling data * @return the code */ public static Code createCode( - final Bytes bytes, final int maxEofVersion, final boolean inCreateOperation) { + final Bytes bytes, + final int maxEofVersion, + final boolean legacyCreation, + final boolean createTransaction) { if (maxEofVersion == 0) { return new CodeV0(bytes); } else if (maxEofVersion == 1) { int codeSize = bytes.size(); if (codeSize > 0 && bytes.get(0) == EOF_LEAD_BYTE) { - if (codeSize == 1 && !inCreateOperation) { + if (codeSize == 1 && !legacyCreation) { return new CodeV0(bytes); } if (codeSize < 3) { return new CodeInvalid(bytes, "EOF Container too short"); } if (bytes.get(1) != 0) { - if (inCreateOperation) { + if (legacyCreation) { // because some 0xef code made it to mainnet, this is only an error at contract create return new CodeInvalid(bytes, "Incorrect second byte"); } else { @@ -62,22 +100,11 @@ public static Code createCode( return new CodeInvalid(bytes, "Unsupported EOF Version: " + version); } - final EOFLayout layout = EOFLayout.parseEOF(bytes); - if (!layout.isValid()) { - return new CodeInvalid(bytes, "Invalid EOF Layout: " + layout.getInvalidReason()); - } - - final String codeValidationError = CodeV1Validation.validateCode(layout); - if (codeValidationError != null) { - return new CodeInvalid(bytes, "EOF Code Invalid : " + codeValidationError); - } - - final String stackValidationError = CodeV1Validation.validateStack(layout); - if (stackValidationError != null) { - return new CodeInvalid(bytes, "EOF Code Invalid : " + stackValidationError); + final EOFLayout layout = EOFLayout.parseEOF(bytes, !createTransaction); + if (createTransaction) { + layout.containerMode().set(INITCODE); } - - return new CodeV1(layout); + return createCode(layout, createTransaction); } else { return new CodeV0(bytes); } @@ -85,4 +112,18 @@ public static Code createCode( return new CodeInvalid(bytes, "Unsupported max code version " + maxEofVersion); } } + + @Nonnull + static Code createCode(final EOFLayout layout, final boolean createTransaction) { + if (!layout.isValid()) { + return new CodeInvalid(layout.container(), "Invalid EOF Layout: " + layout.invalidReason()); + } + + final String validationError = CodeV1Validation.validate(layout); + if (validationError != null) { + return new CodeInvalid(layout.container(), "EOF Code Invalid : " + validationError); + } + + return new CodeV1(layout); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java index 9b92613f4b5..688be5a3cfd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.internal.Words; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Suppliers; @@ -59,6 +61,11 @@ public int getSize() { return codeBytes.size(); } + @Override + public int getDataSize() { + return 0; + } + @Override public Bytes getBytes() { return codeBytes; @@ -91,6 +98,41 @@ public int getCodeSectionCount() { @Override public int getEofVersion() { - return -1; + return Integer.MAX_VALUE; + } + + @Override + public int getSubcontainerCount() { + return 0; + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData) { + return Optional.empty(); + } + + @Override + public Bytes getData(final int offset, final int length) { + return Bytes.EMPTY; + } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, codeBytes.toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, codeBytes.toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return codeBytes.toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + return codeBytes.toHexString(); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java index cf28a26e22e..7a9982bf2d2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java @@ -36,6 +36,9 @@ public final class CodeSection { /** The byte offset from the beginning of the container that the section starts at */ final int entryPoint; + /** Is this a returing code section (i.e. contains RETF or JUMPF into a returning section)? */ + final boolean returning; + /** * Instantiates a new Code section. * @@ -53,7 +56,13 @@ public CodeSection( final int entryPoint) { this.length = length; this.inputs = inputs; - this.outputs = outputs; + if (outputs == 0x80) { + this.outputs = 0; + returning = false; + } else { + this.outputs = outputs; + returning = true; + } this.maxStackHeight = maxStackHeight; this.entryPoint = entryPoint; } @@ -85,6 +94,15 @@ public int getOutputs() { return outputs; } + /** + * Does this code seciton have a RETF return anywhere? + * + * @return returning + */ + public boolean isReturning() { + return returning; + } + /** * Gets max stack height. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java index 5281d0aa616..31a49ba15aa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java @@ -16,8 +16,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.JumpDestOperation; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.MoreObjects; @@ -57,15 +59,14 @@ public class CodeV0 implements Code { * Returns true if the object is equal to this; otherwise false. * * @param other The object to compare this with. - * @return True if the object is equal to this; otherwise false. + * @return True if the object is equal to this, otherwise false. */ @Override public boolean equals(final Object other) { if (other == null) return false; if (other == this) return true; - if (!(other instanceof CodeV0)) return false; + if (!(other instanceof CodeV0 that)) return false; - final CodeV0 that = (CodeV0) other; return this.bytes.equals(that.bytes); } @@ -84,6 +85,11 @@ public int getSize() { return bytes.size(); } + @Override + public int getDataSize() { + return 0; + } + @Override public Bytes getBytes() { return bytes; @@ -137,6 +143,21 @@ public int getEofVersion() { return 0; } + @Override + public int getSubcontainerCount() { + return 0; + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData) { + return Optional.empty(); + } + + @Override + public Bytes getData(final int offset, final int length) { + return Bytes.EMPTY; + } + /** * Calculate jump destination. * @@ -295,4 +316,24 @@ long[] calculateJumpDests() { } return bitmap; } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, bytes.toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, bytes.toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return bytes.toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + return bytes.toHexString(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java index 948fdd6128d..2bea400ed7f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java @@ -18,12 +18,17 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.internal.Words; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; /** The CodeV1. */ public class CodeV1 implements Code { @@ -34,16 +39,16 @@ public class CodeV1 implements Code { /** * Instantiates a new CodeV1. * - * @param layout the layout + * @param eofLayout the layout */ - CodeV1(final EOFLayout layout) { - this.eofLayout = layout; - this.codeHash = Suppliers.memoize(() -> Hash.hash(eofLayout.getContainer())); + CodeV1(final EOFLayout eofLayout) { + this.eofLayout = eofLayout; + this.codeHash = Suppliers.memoize(() -> Hash.hash(eofLayout.container())); } @Override public int getSize() { - return eofLayout.getContainer().size(); + return eofLayout.container().size(); } @Override @@ -60,7 +65,7 @@ public int getCodeSectionCount() { @Override public Bytes getBytes() { - return eofLayout.getContainer(); + return eofLayout.container(); } @Override @@ -80,7 +85,35 @@ public boolean isValid() { @Override public int getEofVersion() { - return eofLayout.getVersion(); + return eofLayout.version(); + } + + @Override + public int getSubcontainerCount() { + return eofLayout.getSubcontainerCount(); + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData) { + EOFLayout subcontainerLayout = eofLayout.getSubcontainer(index); + if (auxData != null && !auxData.isEmpty()) { + Bytes subcontainerWithAuxData = subcontainerLayout.writeContainer(auxData); + if (subcontainerWithAuxData == null) { + return Optional.empty(); + } + subcontainerLayout = EOFLayout.parseEOF(subcontainerWithAuxData); + } else { + // if no auxdata is added we must validate data is not truncated separately + if (subcontainerLayout.dataLength() != subcontainerLayout.data().size()) { + return Optional.empty(); + } + } + + Code subContainerCode = CodeFactory.createCode(subcontainerLayout, auxData == null); + + return subContainerCode.isValid() && subContainerCode.getEofVersion() > 0 + ? Optional.of(subContainerCode) + : Optional.empty(); } @Override @@ -95,4 +128,56 @@ public boolean equals(final Object o) { public int hashCode() { return Objects.hash(codeHash, eofLayout); } + + @Override + public Bytes getData(final int offset, final int length) { + Bytes data = eofLayout.data(); + int dataLen = data.size(); + if (offset > dataLen) { + return Bytes.EMPTY; + } else if ((offset + length) > dataLen) { + byte[] result = new byte[length]; + MutableBytes mbytes = MutableBytes.wrap(result); + data.slice(offset).copyTo(mbytes, 0); + return Bytes.wrap(result); + } else { + return data.slice(offset, length); + } + } + + @Override + public int getDataSize() { + return eofLayout.data().size(); + } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, eofLayout.container().toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, eofLayout.container().toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return eofLayout.container().toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + StringWriter sw = new StringWriter(); + eofLayout.prettyPrint(new PrintWriter(sw, true), "", ""); + return sw.toString(); + } + + /** + * The EOFLayout object for the code + * + * @return the EOFLayout object for the parsed code + */ + public EOFLayout getEofLayout() { + return eofLayout; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java index b6f6173d7ec..b52f1dfebbd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java @@ -14,553 +14,80 @@ */ package org.hyperledger.besu.evm.code; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.String.format; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.RUNTIME; +import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES; import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; import org.hyperledger.besu.evm.operation.CallFOperation; +import org.hyperledger.besu.evm.operation.DataLoadNOperation; +import org.hyperledger.besu.evm.operation.DupNOperation; +import org.hyperledger.besu.evm.operation.EOFCreateOperation; +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.InvalidOperation; +import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.PushOperation; import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.operation.ReturnContractOperation; +import org.hyperledger.besu.evm.operation.ReturnOperation; +import org.hyperledger.besu.evm.operation.RevertOperation; +import org.hyperledger.besu.evm.operation.StopOperation; +import org.hyperledger.besu.evm.operation.SwapNOperation; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.BitSet; +import java.util.List; +import java.util.Queue; +import javax.annotation.Nullable; import org.apache.tuweni.bytes.Bytes; /** Code V1 Validation */ public final class CodeV1Validation { + static final int MAX_STACK_HEIGHT = 1024; + private CodeV1Validation() { // to prevent instantiation } - static final byte INVALID = 0x01; - static final byte VALID = 0x02; - static final byte TERMINAL = 0x04; - static final byte VALID_AND_TERMINAL = VALID | TERMINAL; - static final byte[] OPCODE_ATTRIBUTES = { - VALID_AND_TERMINAL, // 0x00 STOP - VALID, // 0x01 - ADD - VALID, // 0x02 - MUL - VALID, // 0x03 - SUB - VALID, // 0x04 - DIV - VALID, // 0x05 - SDIV - VALID, // 0x06 - MOD - VALID, // 0x07 - SMOD - VALID, // 0x08 - ADDMOD - VALID, // 0x09 - MULMOD - VALID, // 0x0a - EXP - VALID, // 0x0b - SIGNEXTEND - INVALID, // 0x0c - INVALID, // 0x0d - INVALID, // 0x0e - INVALID, // 0x0f - VALID, // 0x10 - LT - VALID, // 0x11 - GT - VALID, // 0x12 - SLT - VALID, // 0x13 - SGT - VALID, // 0x14 - EQ - VALID, // 0x15 - ISZERO - VALID, // 0x16 - AND - VALID, // 0x17 - OR - VALID, // 0x18 - XOR - VALID, // 0x19 - NOT - VALID, // 0x1a - BYTE - VALID, // 0x1b - SHL - VALID, // 0x1c - SHR - VALID, // 0x1d - SAR - INVALID, // 0x1e - INVALID, // 0x1f - VALID, // 0x20 - SHA3 - INVALID, // 0x21 - INVALID, // 0x22 - INVALID, // 0x23 - INVALID, // 0x24 - INVALID, // 0x25 - INVALID, // 0x26 - INVALID, // 0x27 - INVALID, // 0x28 - INVALID, // 0x29 - INVALID, // 0x2a - INVALID, // 0x2b - INVALID, // 0x2c - INVALID, // 0x2d - INVALID, // 0x2e - INVALID, // 0x2f - VALID, // 0x30 - ADDRESS - VALID, // 0x31 - BALANCE - VALID, // 0x32 - ORIGIN - VALID, // 0x33 - CALLER - VALID, // 0x34 - CALLVALUE - VALID, // 0x35 - CALLDATALOAD - VALID, // 0x36 - CALLDATASIZE - VALID, // 0x37 - CALLDATACOPY - VALID, // 0x38 - CODESIZE - VALID, // 0x39 - CODECOPY - VALID, // 0x3a - GASPRICE - VALID, // 0x3b - EXTCODESIZE - VALID, // 0x3c - EXTCODECOPY - VALID, // 0x3d - RETURNDATASIZE - VALID, // 0x3e - RETURNDATACOPY - VALID, // 0x3f - EXTCODEHASH - VALID, // 0x40 - BLOCKHASH - VALID, // 0x41 - COINBASE - VALID, // 0x42 - TIMESTAMP - VALID, // 0x43 - NUMBER - VALID, // 0x44 - PREVRANDAO (née DIFFICULTY) - VALID, // 0x45 - GASLIMIT - VALID, // 0x46 - CHAINID - VALID, // 0x47 - SELFBALANCE - VALID, // 0x48 - BASEFEE - INVALID, // 0x49 - INVALID, // 0x4a - INVALID, // 0x4b - INVALID, // 0x4c - INVALID, // 0x4d - INVALID, // 0x4e - INVALID, // 0x4f - VALID, // 0x50 - POP - VALID, // 0x51 - MLOAD - VALID, // 0x52 - MSTORE - VALID, // 0x53 - MSTORE8 - VALID, // 0x54 - SLOAD - VALID, // 0x55 - SSTORE - INVALID, // 0x56 - JUMP - INVALID, // 0x57 - JUMPI - INVALID, // 0x58 - PC - VALID, // 0x59 - MSIZE - VALID, // 0x5a - GAS - VALID, // 0x5b - NOOOP (née JUMPDEST) - VALID, // 0X5c - TLOAD - VALID, // 0X5d - TSTORE - VALID, // 0X5e - MCOPY - VALID, // 0X5f - PUSH0 - VALID, // 0x60 - PUSH1 - VALID, // 0x61 - PUSH2 - VALID, // 0x62 - PUSH3 - VALID, // 0x63 - PUSH4 - VALID, // 0x64 - PUSH5 - VALID, // 0x65 - PUSH6 - VALID, // 0x66 - PUSH7 - VALID, // 0x67 - PUSH8 - VALID, // 0x68 - PUSH9 - VALID, // 0x69 - PUSH10 - VALID, // 0x6a - PUSH11 - VALID, // 0x6b - PUSH12 - VALID, // 0x6c - PUSH13 - VALID, // 0x6d - PUSH14 - VALID, // 0x6e - PUSH15 - VALID, // 0x6f - PUSH16 - VALID, // 0x70 - PUSH17 - VALID, // 0x71 - PUSH18 - VALID, // 0x72 - PUSH19 - VALID, // 0x73 - PUSH20 - VALID, // 0x74 - PUSH21 - VALID, // 0x75 - PUSH22 - VALID, // 0x76 - PUSH23 - VALID, // 0x77 - PUSH24 - VALID, // 0x78 - PUSH25 - VALID, // 0x79 - PUSH26 - VALID, // 0x7a - PUSH27 - VALID, // 0x7b - PUSH28 - VALID, // 0x7c - PUSH29 - VALID, // 0x7d - PUSH30 - VALID, // 0x7e - PUSH31 - VALID, // 0x7f - PUSH32 - VALID, // 0x80 - DUP1 - VALID, // 0x81 - DUP2 - VALID, // 0x82 - DUP3 - VALID, // 0x83 - DUP4 - VALID, // 0x84 - DUP5 - VALID, // 0x85 - DUP6 - VALID, // 0x86 - DUP7 - VALID, // 0x87 - DUP8 - VALID, // 0x88 - DUP9 - VALID, // 0x89 - DUP10 - VALID, // 0x8a - DUP11 - VALID, // 0x8b - DUP12 - VALID, // 0x8c - DUP13 - VALID, // 0x8d - DUP14 - VALID, // 0x8e - DUP15 - VALID, // 0x8f - DUP16 - VALID, // 0x90 - SWAP1 - VALID, // 0x91 - SWAP2 - VALID, // 0x92 - SWAP3 - VALID, // 0x93 - SWAP4 - VALID, // 0x94 - SWAP5 - VALID, // 0x95 - SWAP6 - VALID, // 0x96 - SWAP7 - VALID, // 0x97 - SWAP8 - VALID, // 0x98 - SWAP9 - VALID, // 0x99 - SWAP10 - VALID, // 0x9a - SWAP11 - VALID, // 0x9b - SWAP12 - VALID, // 0x9c - SWAP13 - VALID, // 0x9d - SWAP14 - VALID, // 0x9e - SWAP15 - VALID, // 0x9f - SWAP16 - VALID, // 0xa0 - LOG0 - VALID, // 0xa1 - LOG1 - VALID, // 0xa2 - LOG2 - VALID, // 0xa3 - LOG3 - VALID, // 0xa4 - LOG4 - INVALID, // 0xa5 - INVALID, // 0xa6 - INVALID, // 0xa7 - INVALID, // 0xa8 - INVALID, // 0xa9 - INVALID, // 0xaa - INVALID, // 0xab - INVALID, // 0xac - INVALID, // 0xad - INVALID, // 0xae - INVALID, // 0xaf - INVALID, // 0xb0 - INVALID, // 0xb1 - INVALID, // 0xb2 - INVALID, // 0xb3 - INVALID, // 0xb4 - INVALID, // 0xb5 - INVALID, // 0xb6 - INVALID, // 0xb7 - INVALID, // 0xb8 - INVALID, // 0xb9 - INVALID, // 0xba - INVALID, // 0xbb - INVALID, // 0xbc - INVALID, // 0xbd - INVALID, // 0xbe - INVALID, // 0xbf - INVALID, // 0xc0 - INVALID, // 0xc1 - INVALID, // 0xc2 - INVALID, // 0xc3 - INVALID, // 0xc4 - INVALID, // 0xc5 - INVALID, // 0xc6 - INVALID, // 0xc7 - INVALID, // 0xc8 - INVALID, // 0xc9 - INVALID, // 0xca - INVALID, // 0xcb - INVALID, // 0xcc - INVALID, // 0xcd - INVALID, // 0xce - INVALID, // 0xcf - INVALID, // 0xd0 - INVALID, // 0xd1 - INVALID, // 0xd2 - INVALID, // 0xd3 - INVALID, // 0xd4 - INVALID, // 0xd5 - INVALID, // 0xd6 - INVALID, // 0xd7 - INVALID, // 0xd8 - INVALID, // 0xd9 - INVALID, // 0xda - INVALID, // 0xdb - INVALID, // 0xdc - INVALID, // 0xdd - INVALID, // 0xde - INVALID, // 0xef - VALID_AND_TERMINAL, // 0xe0 - RJUMP - VALID, // 0xe1 - RJUMPI - VALID, // 0xe2 - RJUMPV - VALID, // 0xe3 - CALLF - VALID_AND_TERMINAL, // 0xe4 - RETF - INVALID, // 0xe5 - INVALID, // 0xe6 - INVALID, // 0xe7 - INVALID, // 0xe8 - INVALID, // 0xe9 - INVALID, // 0xea - INVALID, // 0xeb - INVALID, // 0xec - INVALID, // 0xed - INVALID, // 0xee - INVALID, // 0xef - VALID, // 0xf0 - CREATE - VALID, // 0xf1 - CALL - INVALID, // 0xf2 - CALLCODE - VALID_AND_TERMINAL, // 0xf3 - RETURN - VALID, // 0xf4 - DELEGATECALL - VALID, // 0xf5 - CREATE2 - INVALID, // 0xf6 - INVALID, // 0xf7 - INVALID, // 0xf8 - INVALID, // 0xf9 - VALID, // 0xfa - STATICCALL - INVALID, // 0xfb - INVALID, // 0xfc - VALID_AND_TERMINAL, // 0xfd - REVERT - VALID_AND_TERMINAL, // 0xfe - INVALID - INVALID, // 0xff - SELFDESTRUCT - }; - static final int MAX_STACK_HEIGHT = 1024; - // java17 move to record - // [0] - stack input consumed - // [1] - stack outputs added - // [2] - PC advance - static final byte[][] OPCODE_STACK_VALIDATION = { - {0, 0, -1}, // 0x00 - STOP - {2, 1, 1}, // 0x01 - ADD - {2, 1, 1}, // 0x02 - MUL - {2, 1, 1}, // 0x03 - SUB - {2, 1, 1}, // 0x04 - DIV - {2, 1, 1}, // 0x05 - SDIV - {2, 1, 1}, // 0x06 - MOD - {2, 1, 1}, // 0x07 - SMOD - {3, 1, 1}, // 0x08 - ADDMOD - {3, 1, 1}, // 0x09 - MULMOD - {2, 1, 1}, // 0x0a - EXP - {2, 1, 1}, // 0x0b - SIGNEXTEND - {0, 0, 0}, // 0x0c - {0, 0, 0}, // 0x0d - {0, 0, 0}, // 0x0e - {0, 0, 0}, // 0x0f - {2, 1, 1}, // 0x10 - LT - {2, 1, 1}, // 0x11 - GT - {2, 1, 1}, // 0x12 - SLT - {2, 1, 1}, // 0x13 - SGT - {2, 1, 1}, // 0x14 - EQ - {1, 1, 1}, // 0x15 - ISZERO - {2, 1, 1}, // 0x16 - AND - {2, 1, 1}, // 0x17 - OR - {2, 1, 1}, // 0x18 - XOR - {1, 1, 1}, // 0x19 - NOT - {2, 1, 1}, // 0x1a - BYTE - {2, 1, 1}, // 0x1b - SHL - {2, 1, 1}, // 0x1c - SHR - {2, 1, 1}, // 0x1d - SAR - {0, 0, 0}, // 0x1e - {0, 0, 0}, // 0x1f - {2, 1, 1}, // 0x20 - SHA3 - {0, 0, 0}, // 0x21 - {0, 0, 0}, // 0x22 - {0, 0, 0}, // 0x23 - {0, 0, 0}, // 0x24 - {0, 0, 0}, // 0x25 - {0, 0, 0}, // 0x26 - {0, 0, 0}, // 0x27 - {0, 0, 0}, // 0x28 - {0, 0, 0}, // 0x29 - {0, 0, 0}, // 0x2a - {0, 0, 0}, // 0x2b - {0, 0, 0}, // 0x2c - {0, 0, 0}, // 0x2d - {0, 0, 0}, // 0x2e - {0, 0, 0}, // 0x2f - {0, 1, 1}, // 0x30 - ADDRESS - {1, 1, 1}, // 0x31 - BALANCE - {0, 1, 1}, // 0x32 - ORIGIN - {0, 1, 1}, // 0x33 - CALLER - {0, 1, 1}, // 0x34 - CALLVALUE - {1, 1, 1}, // 0x35 - CALLDATALOAD - {0, 1, 1}, // 0x36 - CALLDATASIZE - {3, 0, 1}, // 0x37 - CALLDATACOPY - {0, 1, 1}, // 0x38 - CODESIZE - {3, 0, 1}, // 0x39 - CODECOPY - {0, 1, 1}, // 0x3a - GASPRICE - {1, 1, 1}, // 0x3b - EXTCODESIZE - {4, 0, 1}, // 0x3c - EXTCODECOPY - {0, 1, 1}, // 0x3d - RETURNDATASIZE - {3, 0, 1}, // 0x3e - RETURNDATACOPY - {1, 1, 1}, // 0x3f - EXTCODEHASH - {1, 1, 1}, // 0x40 - BLOCKHASH - {0, 1, 1}, // 0x41 - COINBASE - {0, 1, 1}, // 0x42 - TIMESTAMP - {0, 1, 1}, // 0x43 - NUMBER - {0, 1, 1}, // 0x44 - PREVRANDAO (née DIFFICULTY) - {0, 1, 1}, // 0x45 - GASLIMIT - {0, 1, 1}, // 0x46 - CHAINID - {0, 1, 1}, // 0x47 - SELFBALANCE - {0, 1, 1}, // 0x48 - BASEFEE - {0, 0, 0}, // 0x49 - {0, 0, 0}, // 0x4a - {0, 0, 0}, // 0x4b - {0, 0, 0}, // 0x4c - {0, 0, 0}, // 0x4d - {0, 0, 0}, // 0x4e - {0, 0, 0}, // 0x4f - {1, 0, 1}, // 0x50 - POP - {1, 1, 1}, // 0x51 - MLOAD - {2, 0, 1}, // 0x52 - MSTORE - {2, 0, 1}, // 0x53 - MSTORE8 - {1, 1, 1}, // 0x54 - SLOAD - {2, 0, 1}, // 0x55 - SSTORE - {0, 0, 0}, // 0x56 - JUMP - {0, 0, 0}, // 0x57 - JUMPI - {0, 0, 0}, // 0x58 - PC - {0, 1, 1}, // 0x59 - MSIZE - {0, 1, 1}, // 0x5a - GAS - {0, 0, 1}, // 0x5b - NOOP (née JUMPDEST) - {1, 1, 1}, // 0x5c - TLOAD - {2, 0, 1}, // 0x5d - TSTORE - {4, 0, 1}, // 0x5e - MCOPY - {0, 1, 1}, // 0x5f - PUSH0 - {0, 1, 2}, // 0x60 - PUSH1 - {0, 1, 3}, // 0x61 - PUSH2 - {0, 1, 4}, // 0x62 - PUSH3 - {0, 1, 5}, // 0x63 - PUSH4 - {0, 1, 6}, // 0x64 - PUSH5 - {0, 1, 7}, // 0x65 - PUSH6 - {0, 1, 8}, // 0x66 - PUSH7 - {0, 1, 9}, // 0x67 - PUSH8 - {0, 1, 10}, // 0x68 - PUSH9 - {0, 1, 11}, // 0x69 - PUSH10 - {0, 1, 12}, // 0x6a - PUSH11 - {0, 1, 13}, // 0x6b - PUSH12 - {0, 1, 14}, // 0x6c - PUSH13 - {0, 1, 15}, // 0x6d - PUSH14 - {0, 1, 16}, // 0x6e - PUSH15 - {0, 1, 17}, // 0x6f - PUSH16 - {0, 1, 18}, // 0x70 - PUSH17 - {0, 1, 19}, // 0x71 - PUSH18 - {0, 1, 20}, // 0x72 - PUSH19 - {0, 1, 21}, // 0x73 - PUSH20 - {0, 1, 22}, // 0x74 - PUSH21 - {0, 1, 23}, // 0x75 - PUSH22 - {0, 1, 24}, // 0x76 - PUSH23 - {0, 1, 25}, // 0x77 - PUSH24 - {0, 1, 26}, // 0x78 - PUSH25 - {0, 1, 27}, // 0x79 - PUSH26 - {0, 1, 28}, // 0x7a - PUSH27 - {0, 1, 29}, // 0x7b - PUSH28 - {0, 1, 30}, // 0x7c - PUSH29 - {0, 1, 31}, // 0x7d - PUSH30 - {0, 1, 32}, // 0x7e - PUSH31 - {0, 1, 33}, // 0x7f - PUSH32 - {1, 2, 1}, // 0x80 - DUP1 - {2, 3, 1}, // 0x81 - DUP2 - {3, 4, 1}, // 0x82 - DUP3 - {4, 5, 1}, // 0x83 - DUP4 - {5, 6, 1}, // 0x84 - DUP5 - {6, 7, 1}, // 0x85 - DUP6 - {7, 8, 1}, // 0x86 - DUP7 - {8, 9, 1}, // 0x87 - DUP8 - {9, 10, 1}, // 0x88 - DUP9 - {10, 11, 1}, // 0x89 - DUP10 - {11, 12, 1}, // 0x8a - DUP11 - {12, 13, 1}, // 0x8b - DUP12 - {13, 14, 1}, // 0x8c - DUP13 - {14, 15, 1}, // 0x8d - DUP14 - {15, 16, 1}, // 0x8e - DUP15 - {16, 17, 1}, // 0x8f - DUP16 - {2, 2, 1}, // 0x90 - SWAP1 - {3, 3, 1}, // 0x91 - SWAP2 - {4, 4, 1}, // 0x92 - SWAP3 - {5, 5, 1}, // 0x93 - SWAP4 - {6, 6, 1}, // 0x94 - SWAP5 - {7, 7, 1}, // 0x95 - SWAP6 - {8, 8, 1}, // 0x96 - SWAP7 - {9, 9, 1}, // 0x97 - SWAP8 - {10, 10, 1}, // 0x98 - SWAP9 - {11, 11, 1}, // 0x99 - SWAP10 - {12, 12, 1}, // 0x9a - SWAP11 - {13, 13, 1}, // 0x9b - SWAP12 - {14, 14, 1}, // 0x9c - SWAP13 - {15, 15, 1}, // 0x9d - SWAP14 - {16, 16, 1}, // 0x9e - SWAP15 - {17, 17, 1}, // 0x9f - SWAP16 - {2, 0, 1}, // 0xa0 - LOG0 - {3, 0, 1}, // 0xa1 - LOG1 - {4, 0, 1}, // 0xa2 - LOG2 - {5, 0, 1}, // 0xa3 - LOG3 - {6, 0, 1}, // 0xa4 - LOG4 - {0, 0, 0}, // 0xa5 - {0, 0, 0}, // 0xa6 - {0, 0, 0}, // 0xa7 - {0, 0, 0}, // 0xa8 - {0, 0, 0}, // 0xa9 - {0, 0, 0}, // 0xaa - {0, 0, 0}, // 0xab - {0, 0, 0}, // 0xac - {0, 0, 0}, // 0xad - {0, 0, 0}, // 0xae - {0, 0, 0}, // 0xaf - {0, 0, 0}, // 0xb0 - {0, 0, 0}, // 0xb1 - {0, 0, 0}, // 0xb2 - {0, 0, 0}, // 0xb3 - {0, 0, 0}, // 0xb4 - {0, 0, 0}, // 0xb5 - {0, 0, 0}, // 0xb6 - {0, 0, 0}, // 0xb7 - {0, 0, 0}, // 0xb8 - {0, 0, 0}, // 0xb9 - {0, 0, 0}, // 0xba - {0, 0, 0}, // 0xbb - {0, 0, 0}, // 0xbc - {0, 0, 0}, // 0xbd - {0, 0, 0}, // 0xbe - {0, 0, 0}, // 0xbf - {0, 0, 0}, // 0xc0 - {0, 0, 0}, // 0xc1 - {0, 0, 0}, // 0xc2 - {0, 0, 0}, // 0xc3 - {0, 0, 0}, // 0xc4 - {0, 0, 0}, // 0xc5 - {0, 0, 0}, // 0xc6 - {0, 0, 0}, // 0xc7 - {0, 0, 0}, // 0xc8 - {0, 0, 0}, // 0xc9 - {0, 0, 0}, // 0xca - {0, 0, 0}, // 0xcb - {0, 0, 0}, // 0xcc - {0, 0, 0}, // 0xcd - {0, 0, 0}, // 0xce - {0, 0, 0}, // 0xcf - {0, 0, 0}, // 0xd0 - {0, 0, 0}, // 0xd1 - {0, 0, 0}, // 0xd2 - {0, 0, 0}, // 0xd3 - {0, 0, 0}, // 0xd4 - {0, 0, 0}, // 0xd5 - {0, 0, 0}, // 0xd6 - {0, 0, 0}, // 0xd7 - {0, 0, 0}, // 0xd8 - {0, 0, 0}, // 0xd9 - {0, 0, 0}, // 0xda - {0, 0, 0}, // 0xdb - {0, 0, 0}, // 0xdc - {0, 0, 0}, // 0xdd - {0, 0, 0}, // 0xde - {0, 0, 0}, // 0xef - {0, 0, -3}, // 0xe0 - RJUMP - {1, 0, 3}, // 0xe1 - RJUMPI - {1, 0, 2}, // 0xe2 - RJUMPV - {0, 0, 3}, // 0xe3 - CALLF - {0, 0, -1}, // 0xe4 - RETF - {0, 0, 0}, // 0xe5 - JUMPF - {0, 0, 0}, // 0xe6 - {0, 0, 0}, // 0xe7 - {0, 0, 0}, // 0xe8 - {0, 0, 0}, // 0xe9 - {0, 0, 0}, // 0xea - {0, 0, 0}, // 0xeb - {0, 0, 0}, // 0xec - {0, 0, 0}, // 0xed - {0, 0, 0}, // 0xee - {0, 0, 0}, // 0xef - {3, 1, 1}, // 0xf0 - CREATE - {7, 1, 1}, // 0xf1 - CALL - {0, 0, 0}, // 0xf2 - CALLCODE - {2, 0, -1}, // 0xf3 - RETURN - {6, 1, 1}, // 0xf4 - DELEGATECALL - {4, 1, 1}, // 0xf5 - CREATE2 - {0, 0, 0}, // 0xf6 - {0, 0, 0}, // 0xf7 - {0, 0, 0}, // 0xf8 - {0, 0, 0}, // 0xf9 - {6, 1, 1}, // 0xfa - STATICCALL - {0, 0, 0}, // 0xfb - {0, 0, 0}, // 0xfc - {2, 0, -1}, // 0xfd - REVERT - {0, 0, -1}, // 0xfe - INVALID - {0, 0, 0}, // 0xff - SELFDESTRUCT - }; + /** + * Validates the code and stack for the EOF Layout, with optional deep consideration of the + * containers. + * + * @param layout The parsed EOFLayout of the code + * @return either null, indicating no error, or a String describing the validation error. + */ + public static String validate(final EOFLayout layout) { + Queue workList = new ArrayDeque<>(layout.getSubcontainerCount()); + workList.add(layout); + + while (!workList.isEmpty()) { + EOFLayout container = workList.poll(); + workList.addAll(List.of(container.subContainers())); + + final String codeValidationError = CodeV1Validation.validateCode(container); + if (codeValidationError != null) { + return codeValidationError; + } + + final String stackValidationError = CodeV1Validation.validateStack(container); + if (stackValidationError != null) { + return stackValidationError; + } + } + + return null; + } /** * Validate Code @@ -569,12 +96,13 @@ private CodeV1Validation() { * @return validation code, null otherwise. */ public static String validateCode(final EOFLayout eofLayout) { - int sectionCount = eofLayout.getCodeSectionCount(); - for (int i = 0; i < sectionCount; i++) { - CodeSection cs = eofLayout.getCodeSection(i); + if (!eofLayout.isValid()) { + return "Invalid EOF container - " + eofLayout.invalidReason(); + } + for (CodeSection cs : eofLayout.codeSections()) { var validation = CodeV1Validation.validateCode( - eofLayout.getContainer().slice(cs.getEntryPoint(), cs.getLength()), sectionCount); + eofLayout.container().slice(cs.getEntryPoint(), cs.getLength()), cs, eofLayout); if (validation != null) { return validation; } @@ -588,71 +116,228 @@ public static String validateCode(final EOFLayout eofLayout) { * @param code the code section code * @return null if valid, otherwise a string containing an error reason. */ - static String validateCode(final Bytes code, final int sectionCount) { + static String validateCode( + final Bytes code, final CodeSection thisCodeSection, final EOFLayout eofLayout) { final int size = code.size(); final BitSet rjumpdests = new BitSet(size); final BitSet immediates = new BitSet(size); final byte[] rawCode = code.toArrayUnsafe(); - int attribute = INVALID; + OpcodeInfo opcodeInfo = V1_OPCODES[0xfe]; int pos = 0; + EOFContainerMode eofContainerMode = eofLayout.containerMode().get(); + boolean hasReturningOpcode = false; while (pos < size) { final int operationNum = rawCode[pos] & 0xff; - attribute = OPCODE_ATTRIBUTES[operationNum]; - if ((attribute & INVALID) == INVALID) { + opcodeInfo = V1_OPCODES[operationNum]; + if (!opcodeInfo.valid()) { // undefined instruction - return String.format("Invalid Instruction 0x%02x", operationNum); + return format("Invalid Instruction 0x%02x", operationNum); } pos += 1; int pcPostInstruction = pos; - if (operationNum > PushOperation.PUSH_BASE && operationNum <= PushOperation.PUSH_MAX) { - final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; - pcPostInstruction += multiByteDataLen; - } else if (operationNum == RelativeJumpOperation.OPCODE - || operationNum == RelativeJumpIfOperation.OPCODE) { - if (pos + 2 > size) { - return "Truncated relative jump offset"; - } - pcPostInstruction += 2; - final int offset = readBigEndianI16(pos, rawCode); - final int rjumpdest = pcPostInstruction + offset; - if (rjumpdest < 0 || rjumpdest >= size) { - return "Relative jump destination out of bounds"; - } - rjumpdests.set(rjumpdest); - } else if (operationNum == RelativeJumpVectorOperation.OPCODE) { - if (pos + 1 > size) { - return "Truncated jump table"; - } - final int jumpTableSize = RelativeJumpVectorOperation.getVectorSize(code, pos); - if (jumpTableSize == 0) { - return "Empty jump table"; - } - pcPostInstruction += 1 + 2 * jumpTableSize; - if (pcPostInstruction > size) { - return "Truncated jump table"; - } - for (int offsetPos = pos + 1; offsetPos < pcPostInstruction; offsetPos += 2) { - final int offset = readBigEndianI16(offsetPos, rawCode); + switch (operationNum) { + case StopOperation.OPCODE, ReturnOperation.OPCODE: + if (eofContainerMode == null) { + eofContainerMode = RUNTIME; + eofLayout.containerMode().set(RUNTIME); + } else if (!eofContainerMode.equals(RUNTIME)) { + return format( + "%s is only a valid opcode in containers used for runtime operations.", + opcodeInfo.name()); + } + break; + case PushOperation.PUSH_BASE, + PushOperation.PUSH_BASE + 1, + PushOperation.PUSH_BASE + 2, + PushOperation.PUSH_BASE + 3, + PushOperation.PUSH_BASE + 4, + PushOperation.PUSH_BASE + 5, + PushOperation.PUSH_BASE + 6, + PushOperation.PUSH_BASE + 7, + PushOperation.PUSH_BASE + 8, + PushOperation.PUSH_BASE + 9, + PushOperation.PUSH_BASE + 10, + PushOperation.PUSH_BASE + 11, + PushOperation.PUSH_BASE + 12, + PushOperation.PUSH_BASE + 13, + PushOperation.PUSH_BASE + 14, + PushOperation.PUSH_BASE + 15, + PushOperation.PUSH_BASE + 16, + PushOperation.PUSH_BASE + 17, + PushOperation.PUSH_BASE + 18, + PushOperation.PUSH_BASE + 19, + PushOperation.PUSH_BASE + 20, + PushOperation.PUSH_BASE + 21, + PushOperation.PUSH_BASE + 22, + PushOperation.PUSH_BASE + 23, + PushOperation.PUSH_BASE + 24, + PushOperation.PUSH_BASE + 25, + PushOperation.PUSH_BASE + 26, + PushOperation.PUSH_BASE + 27, + PushOperation.PUSH_BASE + 28, + PushOperation.PUSH_BASE + 29, + PushOperation.PUSH_BASE + 30, + PushOperation.PUSH_BASE + 31, + PushOperation.PUSH_BASE + 32: + final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; + pcPostInstruction += multiByteDataLen; + break; + case DataLoadNOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated DataLoadN offset"; + } + pcPostInstruction += 2; + final int dataLoadOffset = readBigEndianU16(pos, rawCode); + // only verfy the last byte of the load is within the minimum data + if (dataLoadOffset > eofLayout.dataLength() - 32) { + return "DataLoadN loads data past minimum data length"; + } + break; + case RelativeJumpOperation.OPCODE, RelativeJumpIfOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated relative jump offset"; + } + pcPostInstruction += 2; + final int offset = readBigEndianI16(pos, rawCode); final int rjumpdest = pcPostInstruction + offset; if (rjumpdest < 0 || rjumpdest >= size) { return "Relative jump destination out of bounds"; } rjumpdests.set(rjumpdest); - } - } else if (operationNum == CallFOperation.OPCODE) { - if (pos + 2 > size) { - return "Truncated CALLF"; - } - int section = readBigEndianU16(pos, rawCode); - if (section >= sectionCount) { - return "CALLF to non-existent section - " + Integer.toHexString(section); - } - pcPostInstruction += 2; + break; + case RelativeJumpVectorOperation.OPCODE: + pcPostInstruction += 1; + if (pcPostInstruction > size) { + return "Truncated jump table"; + } + int jumpBasis = pcPostInstruction; + final int jumpTableSize = RelativeJumpVectorOperation.getVectorSize(code, pos); + pcPostInstruction += 2 * jumpTableSize; + if (pcPostInstruction > size) { + return "Truncated jump table"; + } + for (int offsetPos = jumpBasis; offsetPos < pcPostInstruction; offsetPos += 2) { + final int rjumpvOffset = readBigEndianI16(offsetPos, rawCode); + final int rjumpvDest = pcPostInstruction + rjumpvOffset; + if (rjumpvDest < 0 || rjumpvDest >= size) { + return "Relative jump destination out of bounds"; + } + rjumpdests.set(rjumpvDest); + } + break; + case CallFOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated CALLF"; + } + int section = readBigEndianU16(pos, rawCode); + if (section >= eofLayout.getCodeSectionCount()) { + return "CALLF to non-existent section - " + Integer.toHexString(section); + } + if (!eofLayout.getCodeSection(section).returning) { + return "CALLF to non-returning section - " + Integer.toHexString(section); + } + pcPostInstruction += 2; + break; + case RetFOperation.OPCODE: + hasReturningOpcode = true; + break; + case JumpFOperation.OPCODE: + if (pos + 2 > size) { + return "Truncated JUMPF"; + } + int targetSection = readBigEndianU16(pos, rawCode); + if (targetSection >= eofLayout.getCodeSectionCount()) { + return "JUMPF to non-existent section - " + Integer.toHexString(targetSection); + } + CodeSection targetCodeSection = eofLayout.getCodeSection(targetSection); + if (targetCodeSection.isReturning() + && thisCodeSection.getOutputs() < targetCodeSection.getOutputs()) { + return format( + "JUMPF targeting a returning code section %2x with more outputs %d than current section's outputs %d", + targetSection, targetCodeSection.getOutputs(), thisCodeSection.getOutputs()); + } + hasReturningOpcode |= eofLayout.getCodeSection(targetSection).isReturning(); + pcPostInstruction += 2; + break; + case EOFCreateOperation.OPCODE: + if (pos + 1 > size) { + return format( + "Dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + int subcontainerNum = rawCode[pos] & 0xff; + if (subcontainerNum >= eofLayout.getSubcontainerCount()) { + return format( + "%s refers to non-existent subcontainer %d at pc=%d", + opcodeInfo.name(), subcontainerNum, pos - opcodeInfo.pcAdvance()); + } + EOFLayout subContainer = eofLayout.getSubcontainer(subcontainerNum); + var subcontainerMode = subContainer.containerMode().get(); + if (subcontainerMode == null) { + subContainer.containerMode().set(INITCODE); + } else if (subcontainerMode == RUNTIME) { + return format( + "subcontainer %d cannot be used both as initcode and runtime", subcontainerNum); + } + if (subContainer.dataLength() != subContainer.data().size()) { + return format( + "A subcontainer used for %s has a truncated data section, expected %d and is %d.", + V1_OPCODES[operationNum].name(), + subContainer.dataLength(), + subContainer.data().size()); + } + pcPostInstruction += 1; + break; + case ReturnContractOperation.OPCODE: + if (eofContainerMode == null) { + eofContainerMode = INITCODE; + eofLayout.containerMode().set(INITCODE); + } else if (!eofContainerMode.equals(INITCODE)) { + return format( + "%s is only a valid opcode in containers used for initcode", opcodeInfo.name()); + } + if (pos + 1 > size) { + return format( + "Dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + int returnedContractNum = rawCode[pos] & 0xff; + if (returnedContractNum >= eofLayout.getSubcontainerCount()) { + return format( + "%s refers to non-existent subcontainer %d at pc=%d", + opcodeInfo.name(), returnedContractNum, pos - opcodeInfo.pcAdvance()); + } + EOFLayout returnedContract = eofLayout.getSubcontainer(returnedContractNum); + var returnedContractMode = returnedContract.containerMode().get(); + if (returnedContractMode == null) { + returnedContract.containerMode().set(RUNTIME); + } else if (returnedContractMode.equals(INITCODE)) { + return format( + "subcontainer %d cannot be used both as initcode and runtime", returnedContractNum); + } + pcPostInstruction += 1; + break; + default: + // a few opcodes have potentially dangling immediates + if (opcodeInfo.pcAdvance() > 1) { + pcPostInstruction += opcodeInfo.pcAdvance() - 1; + if (pcPostInstruction >= size) { + return format( + "Dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + } + break; } immediates.set(pos, pcPostInstruction); pos = pcPostInstruction; } - if ((attribute & TERMINAL) != TERMINAL) { + if (thisCodeSection.isReturning() != hasReturningOpcode) { + return thisCodeSection.isReturning() + ? "No RETF or qualifying JUMPF" + : "Non-returing section has RETF or JUMPF into returning section"; + } + if (!opcodeInfo.terminal()) { return "No terminating instruction"; } if (rjumpdests.intersects(immediates)) { @@ -661,12 +346,20 @@ static String validateCode(final Bytes code, final int sectionCount) { return null; } + @Nullable static String validateStack(final EOFLayout eofLayout) { - for (int i = 0; i < eofLayout.getCodeSectionCount(); i++) { - var validation = CodeV1Validation.validateStack(i, eofLayout); + WorkList workList = new WorkList(eofLayout.getCodeSectionCount()); + workList.put(0); + int sectionToValidatie = workList.take(); + while (sectionToValidatie >= 0) { + var validation = CodeV1Validation.validateStack(sectionToValidatie, eofLayout, workList); if (validation != null) { return validation; } + sectionToValidatie = workList.take(); + } + if (!workList.isComplete()) { + return format("Unreachable code section %d", workList.getFirstUnmarkedItem()); } return null; } @@ -679,122 +372,279 @@ static String validateStack(final EOFLayout eofLayout) { * * @param codeSectionToValidate The index of code to validate in the code sections * @param eofLayout The EOF container to validate + * @param workList The list of code sections needing validation * @return null if valid, otherwise an error string providing the validation error. */ - public static String validateStack(final int codeSectionToValidate, final EOFLayout eofLayout) { + @Nullable + static String validateStack( + final int codeSectionToValidate, final EOFLayout eofLayout, final WorkList workList) { + if (!eofLayout.isValid()) { + return "EOF Layout invalid - " + eofLayout.invalidReason(); + } try { CodeSection toValidate = eofLayout.getCodeSection(codeSectionToValidate); byte[] code = - eofLayout.getContainer().slice(toValidate.entryPoint, toValidate.length).toArrayUnsafe(); + eofLayout.container().slice(toValidate.entryPoint, toValidate.length).toArrayUnsafe(); int codeLength = code.length; - int[] stackHeights = new int[codeLength]; - Arrays.fill(stackHeights, -1); - - int thisWork = 0; - int maxWork = 1; - int[][] workList = new int[codeLength][2]; + int[] stack_min = new int[codeLength]; + int[] stack_max = new int[codeLength]; + Arrays.fill(stack_min, 1025); + Arrays.fill(stack_max, -1); int initialStackHeight = toValidate.getInputs(); int maxStackHeight = initialStackHeight; - stackHeights[0] = initialStackHeight; - workList[0][1] = initialStackHeight; + stack_min[0] = initialStackHeight; + stack_max[0] = initialStackHeight; int unusedBytes = codeLength; - while (thisWork < maxWork) { - int currentPC = workList[thisWork][0]; - int currentStackHeight = workList[thisWork][1]; - if (thisWork > 0 && stackHeights[currentPC] >= 0) { - // we've been here, validate the jump is what is expected - if (stackHeights[currentPC] != currentStackHeight) { - return String.format( - "Jump into code stack height (%d) does not match previous value (%d)", - stackHeights[currentPC], currentStackHeight); - } else { - thisWork++; - continue; - } - } else { - stackHeights[currentPC] = currentStackHeight; - } + int currentPC = 0; + int currentMin = initialStackHeight; + int currentMax = initialStackHeight; - while (currentPC < codeLength) { - int thisOp = code[currentPC] & 0xff; + while (currentPC < codeLength) { + int thisOp = code[currentPC] & 0xff; - byte[] stackInfo = OPCODE_STACK_VALIDATION[thisOp]; - int stackInputs; - int stackOutputs; - int pcAdvance = stackInfo[2]; - if (thisOp == CallFOperation.OPCODE) { + OpcodeInfo opcodeInfo = V1_OPCODES[thisOp]; + int stackInputs; + int stackOutputs; + int sectionStackUsed; + int pcAdvance = opcodeInfo.pcAdvance(); + switch (thisOp) { + case CallFOperation.OPCODE: int section = readBigEndianU16(currentPC + 1, code); - stackInputs = eofLayout.getCodeSection(section).getInputs(); - stackOutputs = eofLayout.getCodeSection(section).getOutputs(); - } else { - stackInputs = stackInfo[0]; - stackOutputs = stackInfo[1]; - } + workList.put(section); + CodeSection codeSection = eofLayout.getCodeSection(section); + stackInputs = codeSection.getInputs(); + stackOutputs = codeSection.getOutputs(); + sectionStackUsed = codeSection.getMaxStackHeight(); + break; + case DupNOperation.OPCODE: + int depth = code[currentPC + 1] & 0xff; + stackInputs = depth + 1; + stackOutputs = depth + 2; + sectionStackUsed = 0; + break; + case SwapNOperation.OPCODE: + int swapDepth = 2 + (code[currentPC + 1] & 0xff); + stackInputs = swapDepth; + stackOutputs = swapDepth; + sectionStackUsed = 0; + break; + case ExchangeOperation.OPCODE: + int imm = code[currentPC + 1] & 0xff; + int exchangeDepth = (imm >> 4) + (imm & 0xf) + 3; + stackInputs = exchangeDepth; + stackOutputs = exchangeDepth; + sectionStackUsed = 0; + break; + default: + stackInputs = opcodeInfo.inputs(); + stackOutputs = opcodeInfo.outputs(); + sectionStackUsed = 0; + } - if (stackInputs > currentStackHeight) { - return String.format( - "Operation 0x%02X requires stack of %d but only has %d items", - thisOp, stackInputs, currentStackHeight); - } + int nextPC; + if (!opcodeInfo.valid()) { + return format("Invalid Instruction 0x%02x", thisOp); + } + nextPC = currentPC + pcAdvance; - currentStackHeight = currentStackHeight - stackInputs + stackOutputs; - if (currentStackHeight > MAX_STACK_HEIGHT) { - return "Stack height exceeds 1024"; - } + if (nextPC > codeLength) { + return format( + "Dangling immediate argument for opcode 0x%x at PC %d in code section %d.", + thisOp, currentPC - pcAdvance, codeSectionToValidate); + } + if (stack_max[currentPC] < 0) { + return format( + "Code that was not forward referenced in section 0x%x pc %d", + codeSectionToValidate, currentPC); + } - maxStackHeight = Math.max(maxStackHeight, currentStackHeight); + if (stackInputs > currentMin) { + return format( + "Operation 0x%02X requires stack of %d but may only have %d items", + thisOp, stackInputs, currentMin); + } + + int stackDelta = stackOutputs - stackInputs; + currentMax = currentMax + stackDelta; + currentMin = currentMin + stackDelta; + if (currentMax + sectionStackUsed - stackOutputs > MAX_STACK_HEIGHT) { + return "Stack height exceeds 1024"; + } - if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) { - // no `& 0xff` on high byte because this is one case we want sign extension - int rvalue = readBigEndianI16(currentPC + 1, code); - workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight}; - maxWork++; - } else if (thisOp == RelativeJumpVectorOperation.OPCODE) { + unusedBytes -= pcAdvance; + maxStackHeight = max(maxStackHeight, currentMax); + + switch (thisOp) { + case RelativeJumpOperation.OPCODE: + int jValue = readBigEndianI16(currentPC + 1, code); + int targetPC = nextPC + jValue; + if (targetPC > currentPC) { + stack_min[targetPC] = min(stack_min[targetPC], currentMin); + stack_max[targetPC] = max(stack_max[targetPC], currentMax); + } else { + if (stack_min[targetPC] != currentMin) { + return format( + "Stack minimum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPC, stack_min[currentPC], currentMax); + } + if (stack_max[targetPC] != currentMax) { + return format( + "Stack maximum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPC, stack_max[currentPC], currentMax); + } + } + + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + case RelativeJumpIfOperation.OPCODE: + stack_max[nextPC] = max(stack_max[nextPC], currentMax); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); + int jiValue = readBigEndianI16(currentPC + 1, code); + int targetPCi = nextPC + jiValue; + if (targetPCi > currentPC) { + stack_min[targetPCi] = min(stack_min[targetPCi], currentMin); + stack_max[targetPCi] = max(stack_max[targetPCi], currentMax); + } else { + if (stack_min[targetPCi] != currentMin) { + return format( + "Stack minimum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCi, stack_min[currentPC], currentMin); + } + if (stack_max[targetPCi] != currentMax) { + return format( + "Stack maximum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCi, stack_max[currentPC], currentMax); + } + } + break; + case RelativeJumpVectorOperation.OPCODE: int immediateDataSize = (code[currentPC + 1] & 0xff) * 2; - unusedBytes -= immediateDataSize; - int tableEnd = immediateDataSize + currentPC + 2; + unusedBytes -= immediateDataSize + 2; + int tableEnd = immediateDataSize + currentPC + 4; + nextPC = tableEnd; + stack_max[nextPC] = max(stack_max[nextPC], currentMax); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); for (int i = currentPC + 2; i < tableEnd; i += 2) { - int rvalue = readBigEndianI16(i, code); - workList[maxWork] = new int[] {tableEnd + rvalue, currentStackHeight}; - maxWork++; + int vValue = readBigEndianI16(i, code); + int targetPCv = tableEnd + vValue; + if (targetPCv > currentPC) { + stack_min[targetPCv] = min(stack_min[targetPCv], currentMin); + stack_max[targetPCv] = max(stack_max[targetPCv], currentMax); + } else { + if (stack_min[targetPCv] != currentMin) { + return format( + "Stack minimum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCv, stack_min[currentPC], currentMin); + } + if (stack_max[targetPCv] != currentMax) { + return format( + "Stack maximum violation on backwards jump from %d to %d, %d != %d", + currentPC, targetPCv, stack_max[currentPC], currentMax); + } + } } - currentPC = tableEnd - 2; - } else if (thisOp == RetFOperation.OPCODE) { + break; + case RetFOperation.OPCODE: int returnStackItems = toValidate.getOutputs(); - if (currentStackHeight != returnStackItems) { - return String.format( - "Section return (RETF) calculated height 0x%x does not match configured height 0x%x", - currentStackHeight, returnStackItems); + if (currentMin != currentMax) { + return format( + "RETF in section %d has a stack range (%d/%d)and must have only one stack value", + codeSectionToValidate, currentMin, currentMax); + } + if (stack_min[currentPC] != returnStackItems + || stack_min[currentPC] != stack_max[currentPC]) { + return format( + "RETF in section %d calculated height %d does not match configured return stack %d, min height %d, and max height %d", + codeSectionToValidate, + currentMin, + returnStackItems, + stack_min[currentPC], + stack_max[currentPC]); + } + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + case JumpFOperation.OPCODE: + int jumpFTargetSectionNum = readBigEndianI16(currentPC + 1, code); + workList.put(jumpFTargetSectionNum); + CodeSection targetCs = eofLayout.getCodeSection(jumpFTargetSectionNum); + if (currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs() + > MAX_STACK_HEIGHT) { + return format( + "JUMPF at section %d pc %d would exceed maximum stack with %d items", + codeSectionToValidate, + currentPC, + currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs()); + } + if (targetCs.isReturning()) { + if (currentMin != currentMax) { + return format( + "JUMPF at section %d pc %d has a variable stack height %d/%d", + codeSectionToValidate, currentPC, currentMin, currentMax); + } + if (currentMax != toValidate.outputs + targetCs.inputs - targetCs.outputs) { + return format( + "JUMPF at section %d pc %d has incompatible stack height for returning section %d (%d != %d + %d - %d)", + codeSectionToValidate, + currentPC, + jumpFTargetSectionNum, + currentMax, + toValidate.outputs, + targetCs.inputs, + targetCs.outputs); + } + } else { + if (currentMin < targetCs.getInputs()) { + return format( + "JUMPF at section %d pc %d has insufficient minimum stack height for non returning section %d (%d != %d)", + codeSectionToValidate, + currentPC, + jumpFTargetSectionNum, + currentMin, + targetCs.inputs); + } + } + // fall through for terminal op handling + case StopOperation.OPCODE, + ReturnContractOperation.OPCODE, + ReturnOperation.OPCODE, + RevertOperation.OPCODE, + InvalidOperation.OPCODE: + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + default: + // Ordinary operations, update stack for next operation + if (nextPC < codeLength) { + currentMax = max(stack_max[nextPC], currentMax); + stack_max[nextPC] = currentMax; + currentMin = min(stack_min[nextPC], currentMin); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); } - } - if (pcAdvance < 0) { - unusedBytes += pcAdvance; break; - } else if (pcAdvance == 0) { - return String.format("Invalid Instruction 0x%02x", thisOp); - } - - currentPC += pcAdvance; - if (currentPC >= stackHeights.length) { - return String.format( - "Dangling immediate argument for opcode 0x%x at PC %d in code section %d.", - currentStackHeight, codeLength - pcAdvance, codeSectionToValidate); - } - stackHeights[currentPC] = currentStackHeight; - unusedBytes -= pcAdvance; } - - thisWork++; + currentPC = nextPC; } + if (maxStackHeight != toValidate.maxStackHeight) { - return String.format( + return format( "Calculated max stack height (%d) does not match reported stack height (%d)", maxStackHeight, toValidate.maxStackHeight); } if (unusedBytes != 0) { - return String.format("Dead code detected in section %d", codeSectionToValidate); + return format("Dead code detected in section %d", codeSectionToValidate); } return null; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java index 8e5853120ce..95ad7f95dd2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java @@ -14,45 +14,97 @@ */ package org.hyperledger.besu.evm.code; +import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES; + +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; + import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; import org.apache.tuweni.bytes.Bytes; -/** The EOF layout. */ -public class EOFLayout { +/** + * The EOF layout. + * + * @param container The literal EOF bytes fo the whole container + * @param version The parsed version id. zero if unparseable. + * @param codeSections The parsed Code sections. Null if invalid. + * @param subContainers The parsed subcontainers. Null if invalid. + * @param dataLength The length of the data as reported by the container. For subcontainers this may + * be larger than the data in the data field. Zero if invalid. + * @param data The data hard coded in the container. Empty if invalid. + * @param invalidReason If the raw container is invalid, the reason it is invalid. Null if valid. + * @param containerMode The mode of the container (runtime or initcode, if known) + */ +public record EOFLayout( + Bytes container, + int version, + CodeSection[] codeSections, + EOFLayout[] subContainers, + int dataLength, + Bytes data, + String invalidReason, + AtomicReference containerMode) { + + enum EOFContainerMode { + UNKNOWN, + INITCODE, + RUNTIME + } + + /** The EOF prefix byte as a (signed) java byte. */ + public static final byte EOF_PREFIX_BYTE = (byte) 0xEF; - /** The Section Terminator. */ + /** header terminator */ static final int SECTION_TERMINATOR = 0x00; - /** The Section types. */ + /** type data (stack heights, inputs/outputs) */ static final int SECTION_TYPES = 0x01; - /** The Section code. */ + /** code */ static final int SECTION_CODE = 0x02; - /** The Section data. */ - static final int SECTION_DATA = 0x03; + /** sub-EOF subContainers for create */ + static final int SECTION_CONTAINER = 0x03; + + /** data */ + static final int SECTION_DATA = 0x04; /** The Max supported section. */ static final int MAX_SUPPORTED_VERSION = 1; - private final Bytes container; - private final int version; - private final CodeSection[] codeSections; - private final String invalidReason; - - private EOFLayout(final Bytes container, final int version, final CodeSection[] codeSections) { - this.container = container; - this.version = version; - this.codeSections = codeSections; - this.invalidReason = null; + private EOFLayout( + final Bytes container, + final int version, + final CodeSection[] codeSections, + final EOFLayout[] containers, + final int dataSize, + final Bytes data) { + this( + container, + version, + codeSections, + containers, + dataSize, + data, + null, + new AtomicReference<>(null)); } private EOFLayout(final Bytes container, final int version, final String invalidReason) { - this.container = container; - this.version = version; - this.codeSections = null; - this.invalidReason = invalidReason; + this( + container, version, null, null, 0, Bytes.EMPTY, invalidReason, new AtomicReference<>(null)); } private static EOFLayout invalidLayout( @@ -71,6 +123,13 @@ private static String readKind(final ByteArrayInputStream inputStream, final int return null; } + private static int peekKind(final ByteArrayInputStream inputStream) { + inputStream.mark(1); + int kind = inputStream.read(); + inputStream.reset(); + return kind; + } + /** * Parse EOF. * @@ -78,6 +137,18 @@ private static String readKind(final ByteArrayInputStream inputStream, final int * @return the eof layout */ public static EOFLayout parseEOF(final Bytes container) { + return parseEOF(container, true); + } + + /** + * Parse EOF. + * + * @param container the container + * @param strictSize Require the container to fill all bytes, a validation error will result if + * strict and excess data is in the container + * @return the eof layout + */ + public static EOFLayout parseEOF(final Bytes container, final boolean strictSize) { final ByteArrayInputStream inputStream = new ByteArrayInputStream(container.toArrayUnsafe()); if (inputStream.available() < 3) { @@ -100,7 +171,7 @@ public static EOFLayout parseEOF(final Bytes container) { return invalidLayout(container, version, error); } int typesLength = readUnsignedShort(inputStream); - if (typesLength <= 0) { + if (typesLength <= 0 || typesLength % 4 != 0) { return invalidLayout(container, version, "Invalid Types section size"); } @@ -136,6 +207,37 @@ public static EOFLayout parseEOF(final Bytes container) { codeSectionSizes[i] = size; } + int containerSectionCount; + int[] containerSectionSizes; + if (peekKind(inputStream) == SECTION_CONTAINER) { + error = readKind(inputStream, SECTION_CONTAINER); + if (error != null) { + return invalidLayout(container, version, error); + } + containerSectionCount = readUnsignedShort(inputStream); + if (containerSectionCount <= 0) { + return invalidLayout(container, version, "Invalid container section count"); + } + if (containerSectionCount > 256) { + return invalidLayout( + container, + version, + "Too many container sections - 0x" + Integer.toHexString(containerSectionCount)); + } + containerSectionSizes = new int[containerSectionCount]; + for (int i = 0; i < containerSectionCount; i++) { + int size = readUnsignedShort(inputStream); + if (size <= 0) { + return invalidLayout( + container, version, "Invalid container section size for section " + i); + } + containerSectionSizes[i] = size; + } + } else { + containerSectionCount = 0; + containerSectionSizes = new int[0]; + } + error = readKind(inputStream, SECTION_DATA); if (error != null) { return invalidLayout(container, version, error); @@ -172,6 +274,12 @@ public static EOFLayout parseEOF(final Bytes container) { + 3 // data section header + 1 // padding + (codeSectionCount * 4); // type data + if (containerSectionCount > 0) { + pos += + 3 // subcontainer header + + (containerSectionCount * 2); // subcontainer sizes + } + for (int i = 0; i < codeSectionCount; i++) { int codeSectionSize = codeSectionSizes[i]; if (inputStream.skip(codeSectionSize) != codeSectionSize) { @@ -197,17 +305,52 @@ public static EOFLayout parseEOF(final Bytes container) { } codeSections[i] = new CodeSection(codeSectionSize, typeData[i][0], typeData[i][1], typeData[i][2], pos); + if (i == 0 && typeData[0][1] != 0x80) { + return invalidLayout( + container, + version, + "Code section at zero expected non-returning flag, but had return stack of " + + typeData[0][1]); + } pos += codeSectionSize; } - if (inputStream.skip(dataSize) != dataSize) { - return invalidLayout(container, version, "Incomplete data section"); + EOFLayout[] subContainers = new EOFLayout[containerSectionCount]; + for (int i = 0; i < containerSectionCount; i++) { + int subcontianerSize = containerSectionSizes[i]; + if (subcontianerSize != inputStream.skip(subcontianerSize)) { + return invalidLayout(container, version, "incomplete subcontainer"); + } + Bytes subcontainer = container.slice(pos, subcontianerSize); + pos += subcontianerSize; + EOFLayout subLayout = EOFLayout.parseEOF(subcontainer); + if (!subLayout.isValid()) { + String invalidSubReason = subLayout.invalidReason; + return invalidLayout( + container, + version, + invalidSubReason.contains("invalid subcontainer") + ? invalidSubReason + : "invalid subcontainer - " + invalidSubReason); + } + subContainers[i] = subLayout; } + + long loadedDataCount = inputStream.skip(dataSize); + Bytes data = container.slice(pos, (int) loadedDataCount); + + Bytes completeContainer; if (inputStream.read() != -1) { - return invalidLayout(container, version, "Dangling data after end of all sections"); + if (strictSize) { + return invalidLayout(container, version, "Dangling data after end of all sections"); + } else { + completeContainer = container.slice(0, pos + dataSize); + } + } else { + completeContainer = container; } - return new EOFLayout(container, version, codeSections); + return new EOFLayout(completeContainer, version, codeSections, subContainers, dataSize, data); } /** @@ -224,24 +367,6 @@ static int readUnsignedShort(final ByteArrayInputStream inputStream) { } } - /** - * Gets container. - * - * @return the container - */ - public Bytes getContainer() { - return container; - } - - /** - * Gets version. - * - * @return the version - */ - public int getVersion() { - return version; - } - /** * Get code section count. * @@ -262,12 +387,22 @@ public CodeSection getCodeSection(final int i) { } /** - * Gets invalid reason. + * Get sub container section count. + * + * @return the sub container count + */ + public int getSubcontainerCount() { + return subContainers == null ? 0 : subContainers.length; + } + + /** + * Get code sections. * - * @return the invalid reason + * @param i the index + * @return the Code section */ - public String getInvalidReason() { - return invalidReason; + public EOFLayout getSubcontainer(final int i) { + return subContainers[i]; } /** @@ -278,4 +413,313 @@ public String getInvalidReason() { public boolean isValid() { return invalidReason == null; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof EOFLayout eofLayout)) return false; + return version == eofLayout.version + && container.equals(eofLayout.container) + && Arrays.equals(codeSections, eofLayout.codeSections) + && Arrays.equals(subContainers, eofLayout.subContainers) + && Objects.equals(invalidReason, eofLayout.invalidReason); + } + + @Override + public int hashCode() { + int result = Objects.hash(container, version, invalidReason); + result = 31 * result + Arrays.hashCode(codeSections); + result = 31 * result + Arrays.hashCode(subContainers); + return result; + } + + @Override + public String toString() { + return "EOFLayout{" + + "container=" + + container + + ", version=" + + version + + ", codeSections=" + + (codeSections == null ? "null" : Arrays.asList(codeSections).toString()) + + ", containers=" + + (subContainers == null ? "null" : Arrays.asList(subContainers).toString()) + + ", invalidReason='" + + invalidReason + + '\'' + + '}'; + } + + /** + * Re-writes the container with optional auxiliary data. + * + * @param auxData the auxiliary data + * @return Null if there was an error (validation or otherwise) , or the bytes of the re-written + * container. + */ + @Nullable + public Bytes writeContainer(@Nullable final Bytes auxData) { + // do not write invalid containers + if (invalidReason != null) { + return null; + } + + try { + ByteArrayOutputStream baos = + new ByteArrayOutputStream(container.size() + dataLength - data.size()); + DataOutputStream out = new DataOutputStream(baos); + + // EOF header + out.writeByte(EOF_PREFIX_BYTE); + out.writeByte(0); + out.writeByte(version); + + // Types header + out.writeByte(SECTION_TYPES); + out.writeShort(codeSections.length * 4); + + // Code header + out.writeByte(SECTION_CODE); + out.writeShort(codeSections.length); + for (CodeSection cs : codeSections) { + out.writeShort(cs.length); + } + + // Subcontainers header + if (subContainers != null && subContainers.length > 0) { + out.writeByte(SECTION_CONTAINER); + out.writeShort(subContainers.length); + for (EOFLayout container : subContainers) { + out.writeShort(container.container.size()); + } + } + + // Data header + out.writeByte(SECTION_DATA); + if (auxData == null) { + out.writeShort(dataLength); + } else { + int newSize = data.size() + auxData.size(); + if (newSize < dataLength) { + // aux data must cover claimed data lengths. + return null; + } + out.writeShort(newSize); + } + + // header end + out.writeByte(0); + + // Types information + for (CodeSection cs : codeSections) { + out.writeByte(cs.inputs); + if (cs.returning) { + out.writeByte(cs.outputs); + } else { + out.writeByte(0x80); + } + out.writeShort(cs.maxStackHeight); + } + + // Code sections + for (CodeSection cs : codeSections) { + out.write(container.slice(cs.entryPoint, cs.length).toArray()); + } + + // Subcontainers + if (subContainers != null) { + for (EOFLayout container : subContainers) { + out.write(container.container.toArrayUnsafe()); + } + } + + // data + out.write(data.toArrayUnsafe()); + if (auxData != null) { + out.write(auxData.toArrayUnsafe()); + } + + return Bytes.wrap(baos.toByteArray()); + } catch (IOException ioe) { + // ByteArrayOutputStream should never throw, so something has gone very wrong. Wrap as + // runtime + // and re-throw. + throw new RuntimeException(ioe); + } + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @return The pretty printed code + */ + public String prettyPrint() { + StringWriter sw = new StringWriter(); + prettyPrint(new PrintWriter(sw, true), "", ""); + return sw.toString(); + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @param out the print writer to pretty print to + */ + public void prettyPrint(final PrintWriter out) { + out.println("0x # EOF"); + prettyPrint(out, "", ""); + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @param out the print writer to pretty print to + * @param prefix The prefix to prepend to all output lines (useful for nested subconntainers) + * @param subcontainerPrefix The prefix to add to subcontainer names. + */ + public void prettyPrint( + final PrintWriter out, final String prefix, final String subcontainerPrefix) { + + if (!isValid()) { + out.print(prefix); + out.println("# Invalid EOF"); + out.print(prefix); + out.println("# " + invalidReason); + out.println(container); + } + + out.print(prefix); + out.printf("ef00%02x # Magic and Version ( %1$d )%n", version); + out.print(prefix); + out.printf("01%04x # Types length ( %1$d )%n", codeSections.length * 4); + out.print(prefix); + out.printf("02%04x # Total code sections ( %1$d )%n", codeSections.length); + for (int i = 0; i < codeSections.length; i++) { + out.print(prefix); + out.printf(" %04x # Code section %d , %1$d bytes%n", getCodeSection(i).getLength(), i); + } + if (subContainers.length > 0) { + out.print(prefix); + out.printf("03%04x # Total subcontainers ( %1$d )%n", subContainers.length); + for (int i = 0; i < subContainers.length; i++) { + out.print(prefix); + out.printf(" %04x # Sub container %d, %1$d byte%n", subContainers[i].container.size(), i); + } + } + out.print(prefix); + out.printf("04%04x # Data section length( %1$d )", dataLength); + if (dataLength != data.size()) { + out.printf(" (actual size %d)", data.size()); + } + out.print(prefix); + out.printf("%n"); + out.print(prefix); + out.printf(" 00 # Terminator (end of header)%n"); + for (int i = 0; i < codeSections.length; i++) { + CodeSection cs = getCodeSection(i); + out.print(prefix); + out.printf(" # Code section %d types%n", i); + out.print(prefix); + out.printf(" %02x # %1$d inputs %n", cs.getInputs()); + out.print(prefix); + out.printf( + " %02x # %d outputs %s%n", + cs.isReturning() ? cs.getOutputs() : 0x80, + cs.getOutputs(), + cs.isReturning() ? "" : " (Non-returning function)"); + out.print(prefix); + out.printf(" %04x # max stack: %1$d%n", cs.getMaxStackHeight()); + } + for (int i = 0; i < codeSections.length; i++) { + CodeSection cs = getCodeSection(i); + out.print(prefix); + out.printf( + " # Code section %d - in=%d out=%s height=%d%n", + i, cs.inputs, cs.isReturning() ? cs.outputs : "non-returning", cs.maxStackHeight); + byte[] byteCode = container.slice(cs.getEntryPoint(), cs.getLength()).toArray(); + int pc = 0; + while (pc < byteCode.length) { + out.print(prefix); + OpcodeInfo ci = V1_OPCODES[byteCode[pc] & 0xff]; + + if (ci.opcode() == RelativeJumpVectorOperation.OPCODE) { + int tableSize = byteCode[pc + 1] & 0xff; + out.printf("%02x%02x", byteCode[pc], byteCode[pc + 1]); + for (int j = 0; j <= tableSize; j++) { + out.printf("%02x%02x", byteCode[pc + j * 2 + 2], byteCode[pc + j * 2 + 3]); + } + out.printf(" # [%d] %s(", pc, ci.name()); + for (int j = 0; j <= tableSize; j++) { + if (j != 0) { + out.print(','); + } + int b0 = byteCode[pc + j * 2 + 2]; // we want the sign extension, so no `& 0xff` + int b1 = byteCode[pc + j * 2 + 3] & 0xff; + out.print(b0 << 8 | b1); + } + pc += tableSize * 2 + 4; + out.print(")\n"); + } else if (ci.opcode() == RelativeJumpOperation.OPCODE + || ci.opcode() == RelativeJumpIfOperation.OPCODE) { + int b0 = byteCode[pc + 1] & 0xff; + int b1 = byteCode[pc + 2] & 0xff; + short delta = (short) (b0 << 8 | b1); + out.printf("%02x%02x%02x # [%d] %s(%d)", byteCode[pc], b0, b1, pc, ci.name(), delta); + pc += 3; + out.printf("%n"); + } else if (ci.opcode() == ExchangeOperation.OPCODE) { + int imm = byteCode[pc + 1] & 0xff; + out.printf( + " %02x%02x # [%d] %s(%d, %d)", + byteCode[pc], imm, pc, ci.name(), imm >> 4, imm & 0x0F); + pc += 2; + out.printf("%n"); + } else { + int advance = ci.pcAdvance(); + if (advance == 1) { + out.print(" "); + } else if (advance == 2) { + out.print(" "); + } + out.printf("%02x", byteCode[pc]); + for (int j = 1; j < advance; j++) { + out.printf("%02x", byteCode[pc + j]); + } + out.printf(" # [%d] %s", pc, ci.name()); + if (advance == 2) { + out.printf("(%d)", byteCode[pc + 1] & 0xff); + } else if (advance > 2) { + out.print("(0x"); + for (int j = 1; j < advance; j++) { + out.printf("%02x", byteCode[pc + j]); + } + out.print(")"); + } + out.printf("%n"); + pc += advance; + } + } + } + + for (int i = 0; i < subContainers.length; i++) { + var subContainer = subContainers[i]; + out.print(prefix); + out.printf(" # Subcontainer %s%d starts here%n", subcontainerPrefix, i); + + subContainer.prettyPrint(out, prefix + " ", subcontainerPrefix + i + "."); + out.print(prefix); + out.printf(" # Subcontainer %s%d ends%n", subcontainerPrefix, i); + } + + out.print(prefix); + if (data.isEmpty()) { + out.print(" # Data section (empty)\n"); + } else { + out.printf(" # Data section length ( %1$d )", dataLength); + if (dataLength != data.size()) { + out.printf(" actual length ( %d )", data.size()); + } + out.printf("%n%s %s%n", prefix, data.toUnprefixedHexString()); + } + out.flush(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java b/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java new file mode 100644 index 00000000000..27289f91203 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java @@ -0,0 +1,336 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.code; + +import com.google.common.base.Preconditions; + +/** + * Information about opcodes. Currently merges Legacy and EOFv1 + * + * @param name formal name of the opcode, such as STOP + * @param opcode the number of the opcode + * @param valid Is this a valid opcode (from an EOFV1 perspective) + * @param terminal Is this opcode terminal? (i.e. can it end a code section) + * @param inputs How many stack inputs are required/consumed? + * @param outputs How many stack items will be output? + * @param stackDelta What is the net difference in stack height from this operation + * @param pcAdvance How far should the PC advance (0 for terminal only, 1 for most, 2+ for opcodes + * with immediates) + */ +public record OpcodeInfo( + String name, + int opcode, + boolean valid, + boolean terminal, + int inputs, + int outputs, + int stackDelta, + int pcAdvance) { + static OpcodeInfo unallocatedOpcode(final int opcode) { + return new OpcodeInfo("-", opcode, false, false, 0, 0, 0, 1); + } + + static OpcodeInfo invalidOpcode(final String name, final int opcode) { + return new OpcodeInfo(name, opcode, false, false, 0, 0, 0, 1); + } + + static OpcodeInfo terminalOpcode( + final String name, + final int opcode, + final int inputs, + final int outputs, + final int pcAdvance) { + return new OpcodeInfo(name, opcode, true, true, inputs, outputs, outputs - inputs, pcAdvance); + } + + static OpcodeInfo validOpcode( + final String name, + final int opcode, + final int inputs, + final int outputs, + final int pcAdvance) { + return new OpcodeInfo(name, opcode, true, false, inputs, outputs, outputs - inputs, pcAdvance); + } + + /** + * Gets the opcode info for a specific opcode + * + * @param i opcode + * @return the OpcodeInfo object describing that opcode + */ + public static OpcodeInfo getOpcode(final int i) { + Preconditions.checkArgument(i >= 0 && i <= 255); + return V1_OPCODES[i]; + } + + static final OpcodeInfo[] V1_OPCODES = { + OpcodeInfo.terminalOpcode("STOP", 0x00, 0, 0, 1), + OpcodeInfo.validOpcode("ADD", 0x01, 2, 1, 1), + OpcodeInfo.validOpcode("MUL", 0x02, 2, 1, 1), + OpcodeInfo.validOpcode("SUB", 0x03, 2, 1, 1), + OpcodeInfo.validOpcode("DIV", 0x04, 2, 1, 1), + OpcodeInfo.validOpcode("SDIV", 0x05, 2, 1, 1), + OpcodeInfo.validOpcode("MOD", 0x06, 2, 1, 1), + OpcodeInfo.validOpcode("SMOD", 0x07, 2, 1, 1), + OpcodeInfo.validOpcode("ADDMOD", 0x08, 3, 1, 1), + OpcodeInfo.validOpcode("MULMOD", 0x09, 3, 1, 1), + OpcodeInfo.validOpcode("EXP", 0x0a, 2, 1, 1), + OpcodeInfo.validOpcode("SIGNEXTEND", 0x0b, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x0c), + OpcodeInfo.unallocatedOpcode(0x0d), + OpcodeInfo.unallocatedOpcode(0x0e), + OpcodeInfo.unallocatedOpcode(0x0f), + OpcodeInfo.validOpcode("LT", 0x10, 2, 1, 1), + OpcodeInfo.validOpcode("GT", 0x11, 2, 1, 1), + OpcodeInfo.validOpcode("SLT", 0x12, 2, 1, 1), + OpcodeInfo.validOpcode("SGT", 0x13, 2, 1, 1), + OpcodeInfo.validOpcode("EQ", 0x14, 2, 1, 1), + OpcodeInfo.validOpcode("ISZERO", 0x15, 1, 1, 1), + OpcodeInfo.validOpcode("AND", 0x16, 2, 1, 1), + OpcodeInfo.validOpcode("OR", 0x17, 2, 1, 1), + OpcodeInfo.validOpcode("XOR", 0x18, 2, 1, 1), + OpcodeInfo.validOpcode("NOT", 0x19, 1, 1, 1), + OpcodeInfo.validOpcode("BYTE", 0x1a, 2, 1, 1), + OpcodeInfo.validOpcode("SHL", 0x1b, 2, 1, 1), + OpcodeInfo.validOpcode("SHR", 0x1c, 2, 1, 1), + OpcodeInfo.validOpcode("SAR", 0x1d, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x1e), + OpcodeInfo.unallocatedOpcode(0x1f), + OpcodeInfo.validOpcode("SHA3", 0x20, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x21), + OpcodeInfo.unallocatedOpcode(0x22), + OpcodeInfo.unallocatedOpcode(0x23), + OpcodeInfo.unallocatedOpcode(0x24), + OpcodeInfo.unallocatedOpcode(0x25), + OpcodeInfo.unallocatedOpcode(0x26), + OpcodeInfo.unallocatedOpcode(0x27), + OpcodeInfo.unallocatedOpcode(0x28), + OpcodeInfo.unallocatedOpcode(0x29), + OpcodeInfo.unallocatedOpcode(0x2a), + OpcodeInfo.unallocatedOpcode(0x2b), + OpcodeInfo.unallocatedOpcode(0x2c), + OpcodeInfo.unallocatedOpcode(0x2d), + OpcodeInfo.unallocatedOpcode(0x2e), + OpcodeInfo.unallocatedOpcode(0x2f), + OpcodeInfo.validOpcode("ADDRESS", 0x30, 0, 1, 1), + OpcodeInfo.validOpcode("BALANCE", 0x31, 1, 1, 1), + OpcodeInfo.validOpcode("ORIGIN", 0x32, 0, 1, 1), + OpcodeInfo.validOpcode("CALLER", 0x33, 0, 1, 1), + OpcodeInfo.validOpcode("CALLVALUE", 0x34, 0, 1, 1), + OpcodeInfo.validOpcode("CALLDATALOAD", 0x35, 1, 1, 1), + OpcodeInfo.validOpcode("CALLDATASIZE", 0x36, 0, 1, 1), + OpcodeInfo.validOpcode("CALLDATACOPY", 0x37, 3, 0, 1), + OpcodeInfo.invalidOpcode("CODESIZE", 0x38), + OpcodeInfo.invalidOpcode("CODECOPY", 0x39), + OpcodeInfo.validOpcode("GASPRICE", 0x3a, 0, 1, 1), + OpcodeInfo.invalidOpcode("EXTCODESIZE", 0x3b), + OpcodeInfo.invalidOpcode("EXTCODECOPY", 0x3c), + OpcodeInfo.validOpcode("RETURNDATASIZE", 0x3d, 0, 1, 1), + OpcodeInfo.validOpcode("RETURNDATACOPY", 0x3e, 3, 0, 1), + OpcodeInfo.invalidOpcode("EXTCODEHASH", 0x3f), + OpcodeInfo.validOpcode("BLOCKHASH", 0x40, 1, 1, 1), + OpcodeInfo.validOpcode("COINBASE", 0x41, 0, 1, 1), + OpcodeInfo.validOpcode("TIMESTAMP", 0x42, 0, 1, 1), + OpcodeInfo.validOpcode("NUMBER", 0x43, 0, 1, 1), + OpcodeInfo.validOpcode("PREVRANDAO", 0x44, 0, 1, 1), // was DIFFICULTY + OpcodeInfo.validOpcode("GASLIMIT", 0x45, 0, 1, 1), + OpcodeInfo.validOpcode("CHAINID", 0x46, 0, 1, 1), + OpcodeInfo.validOpcode("SELFBALANCE", 0x47, 0, 1, 1), + OpcodeInfo.validOpcode("BASEFEE", 0x48, 0, 1, 1), + OpcodeInfo.validOpcode("BLOBAHASH", 0x49, 1, 1, 1), + OpcodeInfo.validOpcode("BLOBBASEFEE", 0x4a, 0, 1, 1), + OpcodeInfo.unallocatedOpcode(0x4b), + OpcodeInfo.unallocatedOpcode(0x4c), + OpcodeInfo.unallocatedOpcode(0x4d), + OpcodeInfo.unallocatedOpcode(0x4e), + OpcodeInfo.unallocatedOpcode(0x4f), + OpcodeInfo.validOpcode("POP", 0x50, 1, 0, 1), + OpcodeInfo.validOpcode("MLOAD", 0x51, 1, 1, 1), + OpcodeInfo.validOpcode("MSTORE", 0x52, 2, 0, 1), + OpcodeInfo.validOpcode("MSTORE8", 0x53, 2, 0, 1), + OpcodeInfo.validOpcode("SLOAD", 0x54, 1, 1, 1), + OpcodeInfo.validOpcode("SSTORE", 0x55, 2, 0, 1), + OpcodeInfo.invalidOpcode("JUMP", 0x56), + OpcodeInfo.invalidOpcode("JUMPI", 0x57), + OpcodeInfo.invalidOpcode("PC", 0x58), + OpcodeInfo.validOpcode("MSIZE", 0x59, 0, 1, 1), + OpcodeInfo.invalidOpcode("GAS", 0x5a), + OpcodeInfo.validOpcode("NOOP", 0x5b, 0, 0, 1), // was JUMPDEST + OpcodeInfo.validOpcode("TLOAD", 0x5c, 1, 1, 1), + OpcodeInfo.validOpcode("TSTORE", 0x5d, 2, 0, 1), + OpcodeInfo.validOpcode("MCOPY", 0x5e, 3, 0, 1), + OpcodeInfo.validOpcode("PUSH0", 0x5f, 0, 1, 1), + OpcodeInfo.validOpcode("PUSH1", 0x60, 0, 1, 2), + OpcodeInfo.validOpcode("PUSH2", 0x61, 0, 1, 3), + OpcodeInfo.validOpcode("PUSH3", 0x62, 0, 1, 4), + OpcodeInfo.validOpcode("PUSH4", 0x63, 0, 1, 5), + OpcodeInfo.validOpcode("PUSH5", 0x64, 0, 1, 6), + OpcodeInfo.validOpcode("PUSH6", 0x65, 0, 1, 7), + OpcodeInfo.validOpcode("PUSH7", 0x66, 0, 1, 8), + OpcodeInfo.validOpcode("PUSH8", 0x67, 0, 1, 9), + OpcodeInfo.validOpcode("PUSH9", 0x68, 0, 1, 10), + OpcodeInfo.validOpcode("PUSH10", 0x69, 0, 1, 11), + OpcodeInfo.validOpcode("PUSH11", 0x6a, 0, 1, 12), + OpcodeInfo.validOpcode("PUSH12", 0x6b, 0, 1, 13), + OpcodeInfo.validOpcode("PUSH13", 0x6c, 0, 1, 14), + OpcodeInfo.validOpcode("PUSH14", 0x6d, 0, 1, 15), + OpcodeInfo.validOpcode("PUSH15", 0x6e, 0, 1, 16), + OpcodeInfo.validOpcode("PUSH16", 0x6f, 0, 1, 17), + OpcodeInfo.validOpcode("PUSH17", 0x70, 0, 1, 18), + OpcodeInfo.validOpcode("PUSH18", 0x71, 0, 1, 19), + OpcodeInfo.validOpcode("PUSH19", 0x72, 0, 1, 20), + OpcodeInfo.validOpcode("PUSH20", 0x73, 0, 1, 21), + OpcodeInfo.validOpcode("PUSH21", 0x74, 0, 1, 22), + OpcodeInfo.validOpcode("PUSH22", 0x75, 0, 1, 23), + OpcodeInfo.validOpcode("PUSH23", 0x76, 0, 1, 24), + OpcodeInfo.validOpcode("PUSH24", 0x77, 0, 1, 25), + OpcodeInfo.validOpcode("PUSH25", 0x78, 0, 1, 26), + OpcodeInfo.validOpcode("PUSH26", 0x79, 0, 1, 27), + OpcodeInfo.validOpcode("PUSH27", 0x7a, 0, 1, 28), + OpcodeInfo.validOpcode("PUSH28", 0x7b, 0, 1, 29), + OpcodeInfo.validOpcode("PUSH29", 0x7c, 0, 1, 30), + OpcodeInfo.validOpcode("PUSH30", 0x7d, 0, 1, 31), + OpcodeInfo.validOpcode("PUSH31", 0x7e, 0, 1, 32), + OpcodeInfo.validOpcode("PUSH32", 0x7f, 0, 1, 33), + OpcodeInfo.validOpcode("DUP1", 0x80, 1, 2, 1), + OpcodeInfo.validOpcode("DUP2", 0x81, 2, 3, 1), + OpcodeInfo.validOpcode("DUP3", 0x82, 3, 4, 1), + OpcodeInfo.validOpcode("DUP4", 0x83, 4, 5, 1), + OpcodeInfo.validOpcode("DUP5", 0x84, 5, 6, 1), + OpcodeInfo.validOpcode("DUP6", 0x85, 6, 7, 1), + OpcodeInfo.validOpcode("DUP7", 0x86, 7, 8, 1), + OpcodeInfo.validOpcode("DUP8", 0x87, 8, 9, 1), + OpcodeInfo.validOpcode("DUP9", 0x88, 9, 10, 1), + OpcodeInfo.validOpcode("DUP10", 0x89, 10, 11, 1), + OpcodeInfo.validOpcode("DUP11", 0x8a, 11, 12, 1), + OpcodeInfo.validOpcode("DUP12", 0x8b, 12, 13, 1), + OpcodeInfo.validOpcode("DUP13", 0x8c, 13, 14, 1), + OpcodeInfo.validOpcode("DUP14", 0x8d, 14, 15, 1), + OpcodeInfo.validOpcode("DUP15", 0x8e, 15, 16, 1), + OpcodeInfo.validOpcode("DUP16", 0x8f, 16, 17, 1), + OpcodeInfo.validOpcode("SWAP1", 0x90, 2, 2, 1), + OpcodeInfo.validOpcode("SWAP2", 0x91, 3, 3, 1), + OpcodeInfo.validOpcode("SWAP3", 0x92, 4, 4, 1), + OpcodeInfo.validOpcode("SWAP4", 0x93, 5, 5, 1), + OpcodeInfo.validOpcode("SWAP5", 0x94, 6, 6, 1), + OpcodeInfo.validOpcode("SWAP6", 0x95, 7, 7, 1), + OpcodeInfo.validOpcode("SWAP7", 0x96, 8, 8, 1), + OpcodeInfo.validOpcode("SWAP8", 0x97, 9, 9, 1), + OpcodeInfo.validOpcode("SWAP9", 0x98, 10, 10, 1), + OpcodeInfo.validOpcode("SWAP10", 0x99, 11, 11, 1), + OpcodeInfo.validOpcode("SWAP11", 0x9a, 12, 12, 1), + OpcodeInfo.validOpcode("SWAP12", 0x9b, 13, 13, 1), + OpcodeInfo.validOpcode("SWAP13", 0x9c, 14, 14, 1), + OpcodeInfo.validOpcode("SWAP14", 0x9d, 15, 15, 1), + OpcodeInfo.validOpcode("SWAP15", 0x9e, 16, 16, 1), + OpcodeInfo.validOpcode("SWAP16", 0x9f, 17, 17, 1), + OpcodeInfo.validOpcode("LOG0", 0xa0, 2, 0, 1), + OpcodeInfo.validOpcode("LOG1", 0xa1, 3, 0, 1), + OpcodeInfo.validOpcode("LOG2", 0xa2, 4, 0, 1), + OpcodeInfo.validOpcode("LOG3", 0xa3, 5, 0, 1), + OpcodeInfo.validOpcode("LOG4", 0xa4, 6, 0, 1), + OpcodeInfo.unallocatedOpcode(0xa5), + OpcodeInfo.unallocatedOpcode(0xa6), + OpcodeInfo.unallocatedOpcode(0xa7), + OpcodeInfo.unallocatedOpcode(0xa8), + OpcodeInfo.unallocatedOpcode(0xa9), + OpcodeInfo.unallocatedOpcode(0xaa), + OpcodeInfo.unallocatedOpcode(0xab), + OpcodeInfo.unallocatedOpcode(0xac), + OpcodeInfo.unallocatedOpcode(0xad), + OpcodeInfo.unallocatedOpcode(0xae), + OpcodeInfo.unallocatedOpcode(0xaf), + OpcodeInfo.unallocatedOpcode(0xb0), + OpcodeInfo.unallocatedOpcode(0xb1), + OpcodeInfo.unallocatedOpcode(0xb2), + OpcodeInfo.unallocatedOpcode(0xb3), + OpcodeInfo.unallocatedOpcode(0xb4), + OpcodeInfo.unallocatedOpcode(0xb5), + OpcodeInfo.unallocatedOpcode(0xb6), + OpcodeInfo.unallocatedOpcode(0xb7), + OpcodeInfo.unallocatedOpcode(0xb8), + OpcodeInfo.unallocatedOpcode(0xb9), + OpcodeInfo.unallocatedOpcode(0xba), + OpcodeInfo.unallocatedOpcode(0xbb), + OpcodeInfo.unallocatedOpcode(0xbc), + OpcodeInfo.unallocatedOpcode(0xbd), + OpcodeInfo.unallocatedOpcode(0xbe), + OpcodeInfo.unallocatedOpcode(0xbf), + OpcodeInfo.unallocatedOpcode(0xc0), + OpcodeInfo.unallocatedOpcode(0xc1), + OpcodeInfo.unallocatedOpcode(0xc2), + OpcodeInfo.unallocatedOpcode(0xc3), + OpcodeInfo.unallocatedOpcode(0xc4), + OpcodeInfo.unallocatedOpcode(0xc5), + OpcodeInfo.unallocatedOpcode(0xc6), + OpcodeInfo.unallocatedOpcode(0xc7), + OpcodeInfo.unallocatedOpcode(0xc8), + OpcodeInfo.unallocatedOpcode(0xc9), + OpcodeInfo.unallocatedOpcode(0xca), + OpcodeInfo.unallocatedOpcode(0xcb), + OpcodeInfo.unallocatedOpcode(0xcc), + OpcodeInfo.unallocatedOpcode(0xcd), + OpcodeInfo.unallocatedOpcode(0xce), + OpcodeInfo.unallocatedOpcode(0xcf), + OpcodeInfo.validOpcode("DATALOAD", 0xd0, 1, 1, 1), + OpcodeInfo.validOpcode("DATALOADN", 0xd1, 0, 1, 3), + OpcodeInfo.validOpcode("DATASIZE", 0xd2, 0, 1, 1), + OpcodeInfo.validOpcode("DATACOPY", 0xd3, 3, 0, 1), + OpcodeInfo.unallocatedOpcode(0xd4), + OpcodeInfo.unallocatedOpcode(0xd5), + OpcodeInfo.unallocatedOpcode(0xd6), + OpcodeInfo.unallocatedOpcode(0xd7), + OpcodeInfo.unallocatedOpcode(0xd8), + OpcodeInfo.unallocatedOpcode(0xd9), + OpcodeInfo.unallocatedOpcode(0xda), + OpcodeInfo.unallocatedOpcode(0xdb), + OpcodeInfo.unallocatedOpcode(0xdc), + OpcodeInfo.unallocatedOpcode(0xdd), + OpcodeInfo.unallocatedOpcode(0xde), + OpcodeInfo.unallocatedOpcode(0xdf), + OpcodeInfo.terminalOpcode("RJUMP", 0xe0, 0, 0, 3), + OpcodeInfo.validOpcode("RJUMPI", 0xe1, 1, 0, 3), + OpcodeInfo.validOpcode("RJUMPV", 0xe2, 1, 0, 2), + OpcodeInfo.validOpcode("CALLF", 0xe3, 0, 0, 3), + OpcodeInfo.terminalOpcode("RETF", 0xe4, 0, 0, 1), + OpcodeInfo.terminalOpcode("JUMPF", 0xe5, 0, 0, 3), + OpcodeInfo.validOpcode("DUPN", 0xe6, 0, 1, 2), + OpcodeInfo.validOpcode("SWAPN", 0xe7, 0, 0, 2), + OpcodeInfo.validOpcode("EXCHANGE", 0xe8, 0, 0, 2), + OpcodeInfo.unallocatedOpcode(0xe9), + OpcodeInfo.unallocatedOpcode(0xea), + OpcodeInfo.unallocatedOpcode(0xeb), + OpcodeInfo.validOpcode("EOFCREATE", 0xec, 4, 1, 2), + OpcodeInfo.unallocatedOpcode(0xed), + OpcodeInfo.terminalOpcode("RETURNCONTRACT", 0xee, 2, 1, 2), + OpcodeInfo.unallocatedOpcode(0xef), + OpcodeInfo.invalidOpcode("CREATE", 0xf0), + OpcodeInfo.invalidOpcode("CALL", 0xf1), + OpcodeInfo.invalidOpcode("CALLCODE", 0xf2), + OpcodeInfo.terminalOpcode("RETURN", 0xf3, 2, 0, 1), + OpcodeInfo.invalidOpcode("DELEGATECALL", 0xf4), + OpcodeInfo.invalidOpcode("CREATE2", 0xf5), + OpcodeInfo.unallocatedOpcode(0xf6), + OpcodeInfo.validOpcode("RETURNDATALOAD", 0xf7, 1, 1, 1), + OpcodeInfo.validOpcode("EXTCALL", 0xf8, 4, 1, 1), + OpcodeInfo.validOpcode("EXTDELEGATECALL", 0xf9, 3, 1, 1), + OpcodeInfo.invalidOpcode("STATICCALL", 0xfa), + OpcodeInfo.validOpcode("EXTSTATICCALL", 0xfb, 3, 1, 1), + OpcodeInfo.unallocatedOpcode(0xfc), + OpcodeInfo.terminalOpcode("REVERT", 0xfd, 2, 0, 1), + OpcodeInfo.terminalOpcode("INVALID", 0xfe, 0, 0, 1), + OpcodeInfo.invalidOpcode("SELFDESTRUCT", 0xff), + }; +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java b/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java new file mode 100644 index 00000000000..0e30b35df8c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java @@ -0,0 +1,95 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.code; + +/** + * A work list, allowing a DAG to be evaluated while detecting disconnected sections. + * + *

When an item is marked if it has not been marked it is added to the work list. `take()` + * returns the fist item that has not yet been returned from a take, or `-1` if no items are + * available. Items are added by calling `put(int)`, which is idempotent. Items can be put several + * times but will only be taken once. + * + *

`isComplete()` checks if all items have been taken. `getFirstUnmarkedItem()` is used when + * reporting errors to identify an unconnected item. + */ +class WorkList { + boolean[] marked; + int[] items; + int nextIndex; + int listEnd; + + /** + * Create a work list of the appropriate size. The list is empty. + * + * @param size number of possible items + */ + WorkList(final int size) { + marked = new boolean[size]; + items = new int[size]; + nextIndex = 0; + listEnd = -1; + } + + /** + * Take the next item, if available + * + * @return the item number, or -1 if no items are available. + */ + int take() { + if (nextIndex > listEnd) { + return -1; + } + int result = items[nextIndex]; + nextIndex++; + return result; + } + + /** + * Have all items been taken? + * + * @return true if all items were marked and then taken + */ + boolean isComplete() { + return nextIndex >= items.length; + } + + /** + * Put an item in the work list. This is idempotent, an item will only be added on the first call. + * + * @param item the item to add to the list. + */ + void put(final int item) { + if (!marked[item]) { + listEnd++; + items[listEnd] = item; + marked[item] = true; + } + } + + /** + * Walks the taken list and returns the first unmarked item + * + * @return the first unmarked item, or -1 if all items are marked. + */ + int getFirstUnmarkedItem() { + for (int i = 0; i < marked.length; i++) { + if (!marked[i]) { + return i; + } + } + return -1; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java index 39431d785a9..e0a7e49f416 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java @@ -41,7 +41,7 @@ public CachedInvalidCodeRule(final int maxEofVersion) { @Override public Optional validate( final Bytes contractCode, final MessageFrame frame) { - final Code code = CodeFactory.createCode(contractCode, maxEofVersion, false); + final Code code = CodeFactory.createCode(contractCode, maxEofVersion); if (!code.isValid()) { return Optional.of(ExceptionalHaltReason.INVALID_CODE); } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java index 19adcc3bf23..a052bcfa83d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java @@ -35,11 +35,9 @@ public class EOFValidationCodeRule implements ContractValidationRule { private static final Logger LOG = LoggerFactory.getLogger(EOFValidationCodeRule.class); final int maxEofVersion; - final boolean inCreateTransaction; - private EOFValidationCodeRule(final int maxEofVersion, final boolean inCreateTransaction) { + private EOFValidationCodeRule(final int maxEofVersion) { this.maxEofVersion = maxEofVersion; - this.inCreateTransaction = inCreateTransaction; } /** @@ -53,13 +51,13 @@ private EOFValidationCodeRule(final int maxEofVersion, final boolean inCreateTra @Override public Optional validate( final Bytes contractCode, final MessageFrame frame) { - Code code = CodeFactory.createCode(contractCode, maxEofVersion, inCreateTransaction); + Code code = CodeFactory.createCode(contractCode, maxEofVersion); if (!code.isValid()) { LOG.trace("EOF Validation Error: {}", ((CodeInvalid) code).getInvalidReason()); return Optional.of(ExceptionalHaltReason.INVALID_CODE); } - if (frame.getCode().getEofVersion() > code.getEofVersion()) { + if (frame.getCode().getEofVersion() != code.getEofVersion()) { LOG.trace( "Cannot deploy older eof versions: initcode version - {} runtime code version - {}", frame.getCode().getEofVersion(), @@ -74,11 +72,9 @@ public Optional validate( * Create EOF validation. * * @param maxEofVersion Maximum EOF version to validate - * @param inCreateTransaction Is this inside a create transaction? * @return The EOF validation contract validation rule. */ - public static ContractValidationRule of( - final int maxEofVersion, final boolean inCreateTransaction) { - return new EOFValidationCodeRule(maxEofVersion, inCreateTransaction); + public static ContractValidationRule of(final int maxEofVersion) { + return new EOFValidationCodeRule(maxEofVersion); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java index 087038d52f4..64e9653ab7a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java @@ -161,8 +161,12 @@ public static EVMExecutor evm( case SHANGHAI -> shanghai(chainId, evmConfiguration); case CANCUN -> cancun(chainId, evmConfiguration); case PRAGUE -> prague(chainId, evmConfiguration); + case PRAGUE_EOF -> pragueEOF(chainId, evmConfiguration); case OSAKA -> osaka(chainId, evmConfiguration); + case AMSTERDAM -> amsterdam(chainId, evmConfiguration); case BOGOTA -> bogota(chainId, evmConfiguration); + case POLIS -> polis(chainId, evmConfiguration); + case BANGKOK -> bangkok(chainId, evmConfiguration); case FUTURE_EIPS -> futureEips(chainId, evmConfiguration); case EXPERIMENTAL_EIPS -> experimentalEips(chainId, evmConfiguration); }; @@ -503,6 +507,21 @@ public static EVMExecutor prague( return executor; } + /** + * Instantiate PragueEOF evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor pragueEOF( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.pragueEOF(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Osaka evm executor. * @@ -518,6 +537,21 @@ public static EVMExecutor osaka( return executor; } + /** + * Instantiate Amsterdam evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor amsterdam( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.amsterdam(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Bogota evm executor. * @@ -533,6 +567,36 @@ public static EVMExecutor bogota( return executor; } + /** + * Instantiate Polis evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor polis( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.polis(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + + /** + * Instantiate Bangkok evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor bangkok( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.bangkok(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Future EIPs evm executor. * @@ -540,6 +604,7 @@ public static EVMExecutor bogota( * @return the evm executor * @deprecated Migrate to use {@link EVMExecutor#evm(EvmSpecVersion)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @InlineMe( replacement = "EVMExecutor.evm(EvmSpecVersion.FUTURE_EIPS, BigInteger.ONE, evmConfiguration)", imports = { @@ -672,11 +737,11 @@ public Bytes execute() { final Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { final MessageFrame messageFrame = messageFrameStack.peek(); - if (messageFrame.getType() == MessageFrame.Type.CONTRACT_CREATION) { - ccp.process(messageFrame, tracer); - } else if (messageFrame.getType() == MessageFrame.Type.MESSAGE_CALL) { - mcp.process(messageFrame, tracer); - } + (switch (messageFrame.getType()) { + case CONTRACT_CREATION -> ccp; + case MESSAGE_CALL -> mcp; + }) + .process(messageFrame, tracer); } if (commitWorldState) { worldUpdater.commit(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java index c39df00b6b1..af121ae92fb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java @@ -56,24 +56,16 @@ public interface ExceptionalHaltReason { /** The constant PRECOMPILE_ERROR. */ ExceptionalHaltReason PRECOMPILE_ERROR = DefaultExceptionalHaltReason.PRECOMPILE_ERROR; - /** The constant CODE_SECTION_MISSING. */ - ExceptionalHaltReason CODE_SECTION_MISSING = DefaultExceptionalHaltReason.CODE_SECTION_MISSING; - - /** The constant INCORRECT_CODE_SECTION_RETURN_OUTPUTS. */ - ExceptionalHaltReason INCORRECT_CODE_SECTION_RETURN_OUTPUTS = - DefaultExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - - /** The constant TOO_FEW_INPUTS_FOR_CODE_SECTION. */ - ExceptionalHaltReason TOO_FEW_INPUTS_FOR_CODE_SECTION = - DefaultExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; - - /** The constant JUMPF_STACK_MISMATCH. */ - ExceptionalHaltReason JUMPF_STACK_MISMATCH = DefaultExceptionalHaltReason.JUMPF_STACK_MISMATCH; - /** The constant EOF_CREATE_VERSION_INCOMPATIBLE. */ ExceptionalHaltReason EOF_CREATE_VERSION_INCOMPATIBLE = DefaultExceptionalHaltReason.EOF_CREATE_VERSION_INCOMPATIBLE; + /** The constant NONEXISTENT_CONTAINER */ + ExceptionalHaltReason NONEXISTENT_CONTAINER = DefaultExceptionalHaltReason.NONEXISTENT_CONTAINER; + + /** The constant ADDRESS_OUT_OF_RANGE */ + ExceptionalHaltReason ADDRESS_OUT_OF_RANGE = DefaultExceptionalHaltReason.ADDRESS_OUT_OF_RANGE; + /** * Name string. * @@ -114,21 +106,15 @@ enum DefaultExceptionalHaltReason implements ExceptionalHaltReason { INVALID_CODE("Code is invalid"), /** The Precompile error. */ PRECOMPILE_ERROR("Precompile error"), - /** The Code section missing. */ - CODE_SECTION_MISSING("No code section at requested index"), /** The Insufficient code section return data. */ INSUFFICIENT_CODE_SECTION_RETURN_DATA("The stack for a return "), - /** The Incorrect code section return outputs. */ - INCORRECT_CODE_SECTION_RETURN_OUTPUTS( - "The return of a code section does not have the correct number of outputs"), - /** The Too few inputs for code section. */ - TOO_FEW_INPUTS_FOR_CODE_SECTION("Not enough stack items for a function call"), - /** The Jumpf stack mismatch. */ - JUMPF_STACK_MISMATCH( - "The stack height for a JUMPF does not match the requirements of the target section"), /** The Eof version incompatible. */ EOF_CREATE_VERSION_INCOMPATIBLE( - "EOF Code is attempting to create EOF code of an earlier version"); + "EOF Code is attempting to create EOF code of an earlier version"), + /** Container referenced by EOFCREATE operation does not exist */ + NONEXISTENT_CONTAINER("Referenced subcontainer index does not exist (too large?)"), + /** A given address cannot be used by EOF */ + ADDRESS_OUT_OF_RANGE("Address has more than 20 bytes and is out of range"); /** The Description. */ final String description; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java index fe2bba89cf5..fbc96b034db 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java @@ -18,7 +18,6 @@ import java.util.Arrays; -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.MutableBytes; @@ -55,9 +54,6 @@ private static RuntimeException overflow(final long v) { } private static RuntimeException overflow(final String v) { - // TODO: we should probably have another specific exception so this properly end up as an - // exceptional halt condition with a clear message (message that can indicate that if anyone - // runs into this, he should contact us so we know it's a case we do need to handle). final String msg = "Memory index or length %s too large, cannot be larger than %d"; throw new IllegalStateException(String.format(msg, v, MAX_BYTES)); } @@ -180,7 +176,6 @@ int getActiveBytes() { * * @return The current number of active words stored in memory. */ - @VisibleForTesting public int getActiveWords() { return activeWords; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index d006c5de569..e01c9155106 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -25,7 +25,6 @@ import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.internal.MemoryEntry; import org.hyperledger.besu.evm.internal.OperandStack; import org.hyperledger.besu.evm.internal.ReturnStack; @@ -216,6 +215,7 @@ public enum Type { private final Supplier returnStack; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; + private Code createdCode = null; private final boolean isStatic; // Transaction state fields. @@ -277,13 +277,7 @@ private MessageFrame( this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; this.stack = new OperandStack(txValues.maxStackSize()); - this.returnStack = - Suppliers.memoize( - () -> { - var rStack = new ReturnStack(); - rStack.push(new ReturnStack.ReturnStackItem(0, 0, 0)); - return rStack; - }); + this.returnStack = Suppliers.memoize(ReturnStack::new); this.pc = code.isValid() ? code.getCodeSection(0).getEntryPoint() : 0; this.recipient = recipient; this.contract = contract; @@ -336,71 +330,6 @@ public int getSection() { return section; } - /** - * Call function and return exceptional halt reason. - * - * @param calledSection the called section - * @return the exceptional halt reason - */ - public ExceptionalHaltReason callFunction(final int calledSection) { - CodeSection info = code.getCodeSection(calledSection); - if (info == null) { - return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (stack.size() + info.getMaxStackHeight() > txValues.maxStackSize()) { - return ExceptionalHaltReason.TOO_MANY_STACK_ITEMS; - } else if (stack.size() < info.getInputs()) { - return ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; - } else { - returnStack - .get() - .push(new ReturnStack.ReturnStackItem(section, pc + 2, stack.size() - info.getInputs())); - pc = info.getEntryPoint() - 1; // will be +1ed at end of operations loop - this.section = calledSection; - return null; - } - } - - /** - * Execute the mechanics of the JUMPF operation. - * - * @param section the section - * @return the exceptional halt reason, if the jump failed - */ - public ExceptionalHaltReason jumpFunction(final int section) { - CodeSection info = code.getCodeSection(section); - if (info == null) { - return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (stackSize() != peekReturnStack().getStackHeight() + info.getInputs()) { - return ExceptionalHaltReason.JUMPF_STACK_MISMATCH; - } else { - pc = -1; // will be +1ed at end of operations loop - this.section = section; - return null; - } - } - - /** - * Return function exceptional halt reason. - * - * @return the exceptional halt reason - */ - public ExceptionalHaltReason returnFunction() { - CodeSection thisInfo = code.getCodeSection(this.section); - var rStack = returnStack.get(); - var returnInfo = rStack.pop(); - if ((returnInfo.getStackHeight() + thisInfo.getOutputs()) != stack.size()) { - return ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - } else if (rStack.isEmpty()) { - setState(MessageFrame.State.CODE_SUCCESS); - setOutputData(Bytes.EMPTY); - return null; - } else { - this.pc = returnInfo.getPC(); - this.section = returnInfo.getCodeSectionIndex(); - return null; - } - } - /** Deducts the remaining gas. */ public void clearGasRemaining() { this.gasRemaining = 0L; @@ -462,6 +391,24 @@ public void setOutputData(final Bytes output) { this.output = output; } + /** + * Sets the created code from CREATE* operations + * + * @param createdCode the code that was created + */ + public void setCreatedCode(final Code createdCode) { + this.createdCode = createdCode; + } + + /** + * gets the created code from CREATE* operations + * + * @return the code that was created + */ + public Code getCreatedCode() { + return createdCode; + } + /** Clears the output data buffer. */ public void clearOutputData() { setOutputData(Bytes.EMPTY); @@ -1027,18 +974,6 @@ public boolean warmUpStorage(final Address address, final Bytes32 slot) { return txValues.warmedUpStorage().put(address, slot, Boolean.TRUE) != null; } - /** - * Returns whether an address' slot is warmed up. Is deliberately publicly exposed for access from - * trace - * - * @param address the address context - * @param slot the slot to query - * @return whether the address/slot couple is warmed up - */ - public boolean isStorageWarm(final Address address, final Bytes32 slot) { - return this.txValues.warmedUpStorage().contains(address, slot); - } - /** * Return the world state. * @@ -1206,6 +1141,15 @@ public Deque getMessageFrameStack() { return txValues.messageFrameStack(); } + /** + * The return stack used for EOF code sections. + * + * @return the return stack + */ + public ReturnStack getReturnStack() { + return returnStack.get(); + } + /** * Sets exceptional halt reason. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java index 0808841c8e7..0376903c64a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java @@ -76,7 +76,8 @@ public class FrontierGasCalculator implements GasCalculator { private static final long NEW_ACCOUNT_GAS_COST = 25_000L; - private static final long CREATE_OPERATION_GAS_COST = 32_000L; + /** Yellow paper constant for the cost of creating a new contract on-chain */ + protected static final long CREATE_OPERATION_GAS_COST = 32_000L; private static final long COPY_WORD_GAS_COST = 3L; @@ -122,7 +123,9 @@ public class FrontierGasCalculator implements GasCalculator { private static final long SELF_DESTRUCT_REFUND_AMOUNT = 24_000L; /** Default constructor. */ - public FrontierGasCalculator() {} + public FrontierGasCalculator() { + // Default Constructor, for JavaDoc lint + } @Override public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreate) { @@ -214,21 +217,13 @@ public long callOperationBaseGasCost() { return CALL_OPERATION_BASE_GAS_COST; } - /** - * Returns the gas cost to transfer funds in a call operation. - * - * @return the gas cost to transfer funds in a call operation - */ - long callValueTransferGasCost() { + @Override + public long callValueTransferGasCost() { return CALL_VALUE_TRANSFER_GAS_COST; } - /** - * Returns the gas cost to create a new account. - * - * @return the gas cost to create a new account - */ - long newAccountGasCost() { + @Override + public long newAccountGasCost() { return NEW_ACCOUNT_GAS_COST; } @@ -309,6 +304,16 @@ public long gasAvailableForChildCall( } } + @Override + public long getMinRetainedGas() { + return 0; + } + + @Override + public long getMinCalleeGas() { + return 0; + } + /** * Returns the amount of gas the CREATE operation will consume. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 06f24c534e8..2e1728b2346 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -144,6 +144,20 @@ public interface GasCalculator { */ long callOperationBaseGasCost(); + /** + * Returns the gas cost to transfer funds in a call operation. + * + * @return the gas cost to transfer funds in a call operation + */ + long callValueTransferGasCost(); + + /** + * Returns the gas cost to create a new account. + * + * @return the gas cost to create a new account + */ + long newAccountGasCost(); + /** * Returns the gas cost for one of the various CALL operations. * @@ -227,6 +241,20 @@ long callOperationGasCost( */ long gasAvailableForChildCall(MessageFrame frame, long stipend, boolean transfersValue); + /** + * For EXT*CALL, the minimum amount of gas the parent must retain. First described in EIP-7069 + * + * @return MIN_RETAINED_GAS + */ + long getMinRetainedGas(); + + /** + * For EXT*CALL, the minimum amount of gas that a child must receive. First described in EIP-7069 + * + * @return MIN_CALLEE_GAS + */ + long getMinCalleeGas(); + /** * Returns the amount of gas the CREATE operation will consume. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java new file mode 100644 index 00000000000..5fa2fe87257 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.gascalculator; + +import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; + +/** + * Gas Calculator for Prague + * + *

Placeholder for new gas schedule items. If Prague finalzies without changes this can be + * removed + * + *

    + *
  • TBD + *
+ */ +public class PragueEOFGasCalculator extends PragueGasCalculator { + + static final long MIN_RETAINED_GAS = 5_000; + static final long MIN_CALLEE_GAS = 2300; + + /** Instantiates a new Prague Gas Calculator. */ + public PragueEOFGasCalculator() { + this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]); + } + + /** + * Instantiates a new Prague Gas Calculator + * + * @param maxPrecompile the max precompile + */ + protected PragueEOFGasCalculator(final int maxPrecompile) { + super(maxPrecompile); + } + + @Override + public long getMinRetainedGas() { + return MIN_RETAINED_GAS; + } + + @Override + public long getMinCalleeGas() { + return MIN_CALLEE_GAS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java index 9c752fa1a67..5601ca381a6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java @@ -14,91 +14,16 @@ */ package org.hyperledger.besu.evm.internal; -import java.util.Objects; - /** The type Return stack. */ public class ReturnStack extends FlexStack { - /** The type Return stack item. */ - // Java17 convert to record - public static final class ReturnStackItem { - - /** The Code section index. */ - final int codeSectionIndex; - - /** The Pc. */ - final int pc; - - /** The Stack height. */ - final int stackHeight; - - /** - * Instantiates a new Return stack item. - * - * @param codeSectionIndex the code section index - * @param pc the pc - * @param stackHeight the stack height - */ - public ReturnStackItem(final int codeSectionIndex, final int pc, final int stackHeight) { - this.codeSectionIndex = codeSectionIndex; - this.pc = pc; - this.stackHeight = stackHeight; - } - - /** - * Gets code section index. - * - * @return the code section index - */ - public int getCodeSectionIndex() { - return codeSectionIndex; - } - - /** - * Gets pc. - * - * @return the pc - */ - public int getPC() { - return pc; - } - - /** - * Gets stack height. - * - * @return the stack height - */ - public int getStackHeight() { - return stackHeight; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ReturnStackItem that = (ReturnStackItem) o; - return codeSectionIndex == that.codeSectionIndex - && pc == that.pc - && stackHeight == that.stackHeight; - } - - @Override - public int hashCode() { - return Objects.hash(codeSectionIndex, pc, stackHeight); - } - - @Override - public String toString() { - return "ReturnStackItem{" - + "codeSectionIndex=" - + codeSectionIndex - + ", pc=" - + pc - + ", stackHeight=" - + stackHeight - + '}'; - } - } + /** + * The type Return stack item. + * + * @param codeSectionIndex the code section index + * @param pc the pc + */ + public record ReturnStackItem(int codeSectionIndex, int pc) {} /** * Max return stack size specified in 0 || frame.getDepth() >= 1024) { frame.expandMemory(inputDataOffset(frame), inputDataLength(frame)); frame.expandMemory(outputDataOffset(frame), outputDataLength(frame)); + // For the following, we either increment the gas or return zero so weo don't get double + // charged. If we return zero then the traces don't have the right per-opcode cost. frame.incrementRemainingGas(gasAvailableForChildCall(frame) + cost); frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); return new OperationResult(cost, null); } @@ -197,29 +212,30 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { ? CodeV0.EMPTY_CODE : evm.getCode(contract.getCodeHash(), contract.getCode()); - if (code.isValid()) { - // frame addition is automatically handled by parent messageFrameStack - MessageFrame.builder() - .parentMessageFrame(frame) - .type(MessageFrame.Type.MESSAGE_CALL) - .initialGas(gasAvailableForChildCall(frame)) - .address(address(frame)) - .contract(to) - .inputData(inputData) - .sender(sender(frame)) - .value(value(frame)) - .apparentValue(apparentValue(frame)) - .code(code) - .isStatic(isStatic(frame)) - .completer(child -> complete(frame, child)) - .build(); - frame.incrementRemainingGas(cost); - - frame.setState(MessageFrame.State.CODE_SUSPENDED); - return new OperationResult(cost, null, 0); - } else { + // invalid code results in a quick exit + if (!code.isValid()) { return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); } + + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(gasAvailableForChildCall(frame)) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); + // see note in stack depth check about incrementing cost + frame.incrementRemainingGas(cost); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost, null, 0); } /** @@ -281,7 +297,7 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { if (outputSize > outputData.size()) { frame.expandMemory(outputOffset, outputSize); frame.writeMemory(outputOffset, outputData.size(), outputData, true); - } else { + } else if (outputSize > 0) { frame.writeMemory(outputOffset, outputSize, outputData, true); } @@ -294,13 +310,20 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { frame.incrementRemainingGas(gasRemaining); frame.popStackItems(getStackItemsConsumed()); - if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - frame.pushStackItem(SUCCESS_STACK_ITEM); - } else { - frame.pushStackItem(FAILURE_STACK_ITEM); - } + Bytes resultItem; + + resultItem = getCallResultStackItem(childFrame); + frame.pushStackItem(resultItem); final int currentPC = frame.getPC(); frame.setPC(currentPC + 1); } + + Bytes getCallResultStackItem(final MessageFrame childFrame) { + if (childFrame.getState() == State.COMPLETED_SUCCESS) { + return LEGACY_SUCCESS_STACK_ITEM; + } else { + return LEGACY_FAILURE_STACK_ITEM; + } + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index ba345ed8615..a484f28ceb6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.operation.AbstractCallOperation.LEGACY_FAILURE_STACK_ITEM; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -31,6 +32,7 @@ import java.util.Optional; import java.util.function.Supplier; +import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; /** The Abstract create operation. */ @@ -40,8 +42,15 @@ public abstract class AbstractCreateOperation extends AbstractOperation { protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + /** The constant UNDERFLOW_RESPONSE. */ + protected static final OperationResult INVALID_OPERATION = + new OperationResult(0L, ExceptionalHaltReason.INVALID_OPERATION); + /** The maximum init code size */ - protected int maxInitcodeSize; + protected final int maxInitcodeSize; + + /** The EOF Version this create operation requires initcode to be in */ + protected final int eofVersion; /** * Instantiates a new Abstract create operation. @@ -52,6 +61,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation { * @param stackItemsProduced the stack items produced * @param gasCalculator the gas calculator * @param maxInitcodeSize Maximum init code size + * @param eofVersion the EOF version this create operation is valid in */ protected AbstractCreateOperation( final int opcode, @@ -59,19 +69,25 @@ protected AbstractCreateOperation( final int stackItemsConsumed, final int stackItemsProduced, final GasCalculator gasCalculator, - final int maxInitcodeSize) { + final int maxInitcodeSize, + final int eofVersion) { super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); this.maxInitcodeSize = maxInitcodeSize; + this.eofVersion = eofVersion; } @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (frame.getCode().getEofVersion() != eofVersion) { + return INVALID_OPERATION; + } + // manual check because some reads won't come until the "complete" step. if (frame.stackSize() < getStackItemsConsumed()) { return UNDERFLOW_RESPONSE; } - Supplier codeSupplier = () -> getInitCode(frame, evm); + Supplier codeSupplier = Suppliers.memoize(() -> getInitCode(frame, evm)); final long cost = cost(frame, codeSupplier); if (frame.isStatic()) { @@ -85,36 +101,41 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final MutableAccount account = frame.getWorldUpdater().getAccount(address); frame.clearReturnData(); - final long inputOffset = clampedToLong(frame.getStackItem(1)); - final long inputSize = clampedToLong(frame.getStackItem(2)); - if (inputSize > maxInitcodeSize) { - frame.popStackItems(getStackItemsConsumed()); - return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); - } + + Code code = codeSupplier.get(); if (value.compareTo(account.getBalance()) > 0 || frame.getDepth() >= 1024 || account.getNonce() == -1 - || codeSupplier.get() == null) { + || code == null + || code.getEofVersion() != frame.getCode().getEofVersion()) { fail(frame); } else { account.incrementNonce(); - final Bytes inputData = frame.readMemory(inputOffset, inputSize); - // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly - // addresses disk loading delay, and we already have the code. - Code code = evm.getCodeUncached(inputData); + if (code.getSize() > maxInitcodeSize) { + frame.popStackItems(getStackItemsConsumed()); + return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); + } + if (!code.isValid()) { + fail(frame); + } else { - if (code.isValid() && frame.getCode().getEofVersion() <= code.getEofVersion()) { frame.decrementRemainingGas(cost); spawnChildMessage(frame, code, evm); frame.incrementRemainingGas(cost); - } else { - fail(frame); } } + return new OperationResult(cost, null, getPcIncrement()); + } - return new OperationResult(cost, null); + /** + * How many bytes does this operation occupy? + * + * @return The number of bytes the operation and immediate arguments occupy + */ + protected int getPcIncrement() { + return 1; } /** @@ -149,13 +170,14 @@ private void fail(final MessageFrame frame) { final long inputSize = clampedToLong(frame.getStackItem(2)); frame.readMutableMemory(inputOffset, inputSize); frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); } private void spawnChildMessage(final MessageFrame parent, final Code code, final EVM evm) { final Wei value = Wei.wrap(parent.getStackItem(0)); final Address contractAddress = targetContractAddress(parent, code); + final Bytes inputData = getInputData(parent); final long childGasStipend = gasCalculator().gasAvailableForChildCreate(parent.getRemainingGas()); @@ -168,7 +190,7 @@ private void spawnChildMessage(final MessageFrame parent, final Code code, final .initialGas(childGasStipend) .address(contractAddress) .contract(contractAddress) - .inputData(Bytes.EMPTY) + .inputData(inputData) .sender(parent.getRecipientAddress()) .value(value) .apparentValue(value) @@ -179,11 +201,24 @@ private void spawnChildMessage(final MessageFrame parent, final Code code, final parent.setState(MessageFrame.State.CODE_SUSPENDED); } + /** + * Get the input data to be appended to the EOF factory contract. For CREATE and CREATE2 this is + * always empty + * + * @param frame the message frame the operation was called in + * @return the input data as raw bytes, or `Bytes.EMPTY` if there is no aux data + */ + protected Bytes getInputData(final MessageFrame frame) { + return Bytes.EMPTY; + } + private void complete(final MessageFrame frame, final MessageFrame childFrame, final EVM evm) { frame.setState(MessageFrame.State.CODE_EXECUTING); Code outputCode = - CodeFactory.createCode(childFrame.getOutputData(), evm.getMaxEOFVersion(), true); + (childFrame.getCreatedCode() != null) + ? childFrame.getCreatedCode() + : CodeFactory.createCode(childFrame.getOutputData(), evm.getMaxEOFVersion()); frame.popStackItems(getStackItemsConsumed()); if (outputCode.isValid()) { @@ -198,18 +233,18 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame, f onSuccess(frame, createdAddress); } else { frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); onFailure(frame, childFrame.getExceptionalHaltReason()); } } else { frame.getWorldUpdater().deleteAccount(childFrame.getRecipientAddress()); frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); onInvalid(frame, (CodeInvalid) outputCode); } final int currentPC = frame.getPC(); - frame.setPC(currentPC + 1); + frame.setPC(currentPC + getPcIncrement()); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java new file mode 100644 index 00000000000..2c0535b197b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java @@ -0,0 +1,201 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.CodeV0; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.Words; + +import javax.annotation.Nonnull; + +import org.apache.tuweni.bytes.Bytes; + +/** + * A skeleton class for implementing call operations. + * + *

A call operation creates a child message call from the current message context, allows it to + * execute, and then updates the current message context based on its execution. + */ +public abstract class AbstractExtCallOperation extends AbstractCallOperation { + + static final int STACK_TO = 0; + + /** EXT*CALL response indicating success */ + public static final Bytes EOF1_SUCCESS_STACK_ITEM = Bytes.EMPTY; + + /** EXT*CALL response indicating a "soft failure" */ + public static final Bytes EOF1_EXCEPTION_STACK_ITEM = BYTES_ONE; + + /** EXT*CALL response indicating a hard failure, such as a REVERT was called */ + public static final Bytes EOF1_FAILURE_STACK_ITEM = Bytes.of(2); + + /** + * Instantiates a new Abstract call operation. + * + * @param opcode the opcode + * @param name the name + * @param stackItemsConsumed the stack items consumed + * @param stackItemsProduced the stack items produced + * @param gasCalculator the gas calculator + */ + AbstractExtCallOperation( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + } + + @Override + protected Address to(final MessageFrame frame) { + return Words.toAddress(frame.getStackItem(STACK_TO)); + } + + @Override + protected long gas(final MessageFrame frame) { + return Long.MAX_VALUE; + } + + @Override + protected long outputDataOffset(final MessageFrame frame) { + return 0; + } + + @Override + protected long outputDataLength(final MessageFrame frame) { + return 0; + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame) { + throw new UnsupportedOperationException("EXTCALL does not use gasAvailableForChildCall"); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final Bytes toBytes = frame.getStackItem(STACK_TO).trimLeadingZeros(); + final Wei value = value(frame); + final boolean zeroValue = value.isZero(); + long inputOffset = inputDataOffset(frame); + long inputLength = inputDataLength(frame); + + if (!zeroValue && isStatic(frame)) { + return new OperationResult( + gasCalculator().callValueTransferGasCost(), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + if (toBytes.size() > Address.SIZE) { + return new OperationResult( + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) + + (zeroValue ? 0 : gasCalculator().callValueTransferGasCost()) + + gasCalculator().getColdAccountAccessCost(), + ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE); + } + Address to = Words.toAddress(toBytes); + final Account contract = frame.getWorldUpdater().get(to); + boolean accountCreation = contract == null && !zeroValue; + long cost = + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) + + (zeroValue ? 0 : gasCalculator().callValueTransferGasCost()) + + (frame.warmUpAddress(to) + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()) + + (accountCreation ? gasCalculator().newAccountGasCost() : 0); + long currentGas = frame.getRemainingGas() - cost; + if (currentGas < 0) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Code code = + contract == null + ? CodeV0.EMPTY_CODE + : evm.getCode(contract.getCodeHash(), contract.getCode()); + + // invalid code results in a quick exit + if (!code.isValid()) { + return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); + } + + // last exceptional failure, prepare for call or soft failures + frame.clearReturnData(); + + // delegate calls to prior EOF versions are prohibited + if (isDelegate() && frame.getCode().getEofVersion() != code.getEofVersion()) { + return softFailure(frame, cost); + } + + long retainedGas = Math.max(currentGas / 64, gasCalculator().getMinRetainedGas()); + long childGas = currentGas - retainedGas; + + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); + final Wei balance = (zeroValue || account == null) ? Wei.ZERO : account.getBalance(); + + // There myst be a minimum gas for a call to have access to. + if (childGas < gasCalculator().getMinRetainedGas()) { + return softFailure(frame, cost); + } + // transferring value you don't have is not a halting exception, just a failure + if (!zeroValue && (value.compareTo(balance) > 0)) { + return softFailure(frame, cost); + } + // stack too deep, for large gas systems. + if (frame.getDepth() >= 1024) { + return softFailure(frame, cost); + } + + // all checks passed, do the call + final Bytes inputData = frame.readMutableMemory(inputOffset, inputLength); + + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(childGas) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost + childGas, null, 0); + } + + private @Nonnull OperationResult softFailure(final MessageFrame frame, final long cost) { + frame.popStackItems(getStackItemsConsumed()); + frame.pushStackItem(EOF1_EXCEPTION_STACK_ITEM); + return new OperationResult(cost, null); + } + + @Override + Bytes getCallResultStackItem(final MessageFrame childFrame) { + return switch (childFrame.getState()) { + case COMPLETED_SUCCESS -> EOF1_SUCCESS_STACK_ITEM; + case EXCEPTIONAL_HALT -> EOF1_EXCEPTION_STACK_ITEM; + default -> EOF1_FAILURE_STACK_ITEM; + }; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java index bd2c0c17624..82b20b268bc 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java @@ -25,8 +25,6 @@ public abstract class AbstractOperation implements Operation { static final Bytes BYTES_ONE = Bytes.of(1); - static final Bytes SUCCESS_STACK_ITEM = BYTES_ONE; - static final Bytes FAILURE_STACK_ITEM = Bytes.EMPTY; private final int opcode; private final String name; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java index d8e6d4aef85..4e21e36bc20 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java @@ -56,7 +56,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes value2 = frame.popStackItem(); if (value2.isZero()) { - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(Bytes.EMPTY); } else { BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java index a56099a0313..c6990634155 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java @@ -14,11 +14,12 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.ReturnStack; /** The Call F operation. */ public class CallFOperation extends AbstractOperation { @@ -40,26 +41,18 @@ public CallFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC()); - } - - /** - * Performs Call F operation. - * - * @param frame the frame - * @param code the code - * @param pc the pc - * @return the successful operation result - */ - public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc) { - int section = readBigEndianU16(pc + 1, code); - var exception = frame.callFunction(section); - if (exception == null) { - return callfSuccess; - } else { - return new OperationResult(callfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + + int pc = frame.getPC(); + int section = code.readBigEndianU16(pc + 1); + CodeSection info = code.getCodeSection(section); + frame.getReturnStack().push(new ReturnStack.ReturnStackItem(frame.getSection(), pc + 2)); + frame.setPC(info.getEntryPoint() - 1); // will be +1ed at end of operations loop + frame.setSection(section); + + return callfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java index bdf7172f363..a044cd138fa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java @@ -42,7 +42,7 @@ public class Create2Operation extends AbstractCreateOperation { * @param maxInitcodeSize Maximum init code size */ public Create2Operation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize); + super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize, 0); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java index c0332f378c6..fc260024530 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java @@ -39,7 +39,7 @@ public class CreateOperation extends AbstractCreateOperation { * @param maxInitcodeSize Maximum init code size */ public CreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize); + super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize, 0); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java new file mode 100644 index 00000000000..7813358eac0 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java @@ -0,0 +1,67 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataCopyOperation extends AbstractOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataCopyOperation(final GasCalculator gasCalculator) { + super(0xd3, "DATACOPY", 3, 1, gasCalculator); + } + + /** + * Cost of data Copy operation. + * + * @param frame the frame + * @param memOffset the mem offset + * @param length the length + * @return the long + */ + protected long cost(final MessageFrame frame, final long memOffset, final long length) { + return gasCalculator().getVeryLowTierGasCost() + + gasCalculator().extCodeCopyOperationGasCost(frame, memOffset, length); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int memOffset = clampedToInt(frame.popStackItem()); + final int sourceOffset = clampedToInt(frame.popStackItem()); + final int length = clampedToInt(frame.popStackItem()); + final long cost = cost(frame, memOffset, length); + + final Bytes data = code.getData(sourceOffset, length); + frame.writeMemory(memOffset, length, data); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java new file mode 100644 index 00000000000..7431ed5ac77 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataLoadNOperation extends AbstractFixedCostOperation { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xd1; + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataLoadNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "DATALOADN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + int pc = frame.getPC(); + int index = code.readBigEndianU16(pc + 1); + final Bytes data = code.getData(index, 32); + frame.pushStackItem(data); + frame.setPC(pc + 2); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java new file mode 100644 index 00000000000..29db444fac6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataLoadOperation extends AbstractFixedCostOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataLoadOperation(final GasCalculator gasCalculator) { + super(0xd0, "DATALOAD", 1, 1, gasCalculator, 4); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int sourceOffset = clampedToInt(frame.popStackItem()); + + final Bytes data = code.getData(sourceOffset, 32); + frame.pushStackItem(data); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java new file mode 100644 index 00000000000..13cfab0df2c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataSizeOperation extends AbstractFixedCostOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataSizeOperation(final GasCalculator gasCalculator) { + super(0xd2, "DATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + final Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int size = code.getDataSize(); + frame.pushStackItem(Bytes.ofUnsignedInt(size)); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java index 0205eb8753c..e7fb3f7c626 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java @@ -83,4 +83,9 @@ protected Address sender(final MessageFrame frame) { public long gasAvailableForChildCall(final MessageFrame frame) { return gasCalculator().gasAvailableForChildCall(frame, gas(frame), false); } + + @Override + protected boolean isDelegate() { + return true; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java new file mode 100644 index 00000000000..61108e2b610 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Dup operation. */ +public class DupNOperation extends AbstractFixedCostOperation { + + /** DUPN Opcode 0xe6 */ + public static final int OPCODE = 0xe6; + + /** The Dup success operation result. */ + static final OperationResult dupSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Dup operation. + * + * @param gasCalculator the gas calculator + */ + public DupNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "DUPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + + int depth = code.readU8(pc + 1); + frame.pushStackItem(frame.getStackItem(depth)); + frame.setPC(pc + 1); + + return dupSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java new file mode 100644 index 00000000000..cb6b2d4e3c4 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java @@ -0,0 +1,91 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.crypto.Hash.keccak256; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Create2 operation. */ +public class EOFCreateOperation extends AbstractCreateOperation { + + /** Opcode 0xEC for operation EOFCREATE */ + public static final int OPCODE = 0xec; + + private static final Bytes PREFIX = Bytes.fromHexString("0xFF"); + + /** + * Instantiates a new EOFCreate operation. + * + * @param gasCalculator the gas calculator + */ + public EOFCreateOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EOFCREATE", 4, 1, gasCalculator, Integer.MAX_VALUE, 1); + } + + @Override + public long cost(final MessageFrame frame, final Supplier codeSupplier) { + final int inputOffset = clampedToInt(frame.getStackItem(2)); + final int inputSize = clampedToInt(frame.getStackItem(3)); + return clampedAdd( + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize), + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().createKeccakCost(codeSupplier.get().getSize()))); + } + + @Override + public Address targetContractAddress(final MessageFrame frame, final Code initcode) { + final Address sender = frame.getRecipientAddress(); + final Bytes32 salt = Bytes32.leftPad(frame.getStackItem(1)); + final Bytes32 hash = keccak256(Bytes.concatenate(PREFIX, sender, salt, initcode.getCodeHash())); + final Address address = Address.extract(hash); + frame.warmUpAddress(address); + return address; + } + + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final Code code = frame.getCode(); + int startIndex = frame.getPC() + 1; + final int initContainerIndex = code.readU8(startIndex); + + return code.getSubContainer(initContainerIndex, null).orElse(null); + } + + @Override + protected Bytes getInputData(final MessageFrame frame) { + final long inputOffset = clampedToLong(frame.getStackItem(2)); + final long inputSize = clampedToLong(frame.getStackItem(3)); + return frame.readMemory(inputOffset, inputSize); + } + + @Override + protected int getPcIncrement() { + return 2; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java new file mode 100644 index 00000000000..cc6f8c9351a --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java @@ -0,0 +1,60 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Exchange operation. */ +public class ExchangeOperation extends AbstractFixedCostOperation { + + /** EXCHANGE Opcode 0xe8 */ + public static final int OPCODE = 0xe8; + + /** The Exchange operation success result. */ + static final OperationResult exchangeSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Exchange operation. + * + * @param gasCalculator the gas calculator + */ + public ExchangeOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EXCHANGE", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + int imm = code.readU8(pc + 1); + int n = (imm >> 4) + 1; + int m = (imm & 0x0F) + 1 + n; + + final Bytes tmp = frame.getStackItem(n); + frame.setStackItem(n, frame.getStackItem(m)); + frame.setStackItem(m, tmp); + frame.setPC(pc + 1); + + return exchangeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java new file mode 100644 index 00000000000..bf986a572e5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java @@ -0,0 +1,69 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Call operation. */ +public class ExtCallOperation extends AbstractExtCallOperation { + + static final int STACK_VALUE = 1; + static final int STACK_INPUT_OFFSET = 2; + static final int STACK_INPUT_LENGTH = 3; + + /** + * Instantiates a new Call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtCallOperation(final GasCalculator gasCalculator) { + super(0xF8, "EXTCALL", 4, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.wrap(frame.getStackItem(STACK_VALUE)); + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return value(frame); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return to(frame); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index 7801b6d7d37..37a92ffc6ef 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -30,6 +31,9 @@ /** The Ext code copy operation. */ public class ExtCodeCopyOperation extends AbstractOperation { + /** This is the "code" legacy contracts see when copying code from an EOF contract. */ + public static final Bytes EOF_REPLACEMENT_CODE = Bytes.fromHexString("0xef00"); + /** * Instantiates a new Ext code copy operation. * @@ -78,7 +82,12 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final Account account = frame.getWorldUpdater().get(address); final Bytes code = account != null ? account.getCode() : Bytes.EMPTY; - frame.writeMemory(memOffset, sourceOffset, numBytes, code); + if (code.size() >= 2 && code.get(0) == EOFLayout.EOF_PREFIX_BYTE && code.get(1) == 0) { + frame.writeMemory(memOffset, sourceOffset, numBytes, EOF_REPLACEMENT_CODE); + } else { + frame.writeMemory(memOffset, sourceOffset, numBytes, code); + } + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index ba6cd22d9df..953ddfb04d2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -15,8 +15,10 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -29,6 +31,9 @@ /** The Ext code hash operation. */ public class ExtCodeHashOperation extends AbstractOperation { + // // 0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5 + static final Hash EOF_REPLACEMENT_HASH = Hash.hash(ExtCodeCopyOperation.EOF_REPLACEMENT_CODE); + /** * Instantiates a new Ext code hash operation. * @@ -65,7 +70,12 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { if (account == null || account.isEmpty()) { frame.pushStackItem(Bytes.EMPTY); } else { - frame.pushStackItem(account.getCodeHash()); + final Bytes code = account.getCode(); + if (code.size() >= 2 && code.get(0) == EOFLayout.EOF_PREFIX_BYTE && code.get(1) == 0) { + frame.pushStackItem(EOF_REPLACEMENT_HASH); + } else { + frame.pushStackItem(account.getCodeHash()); + } } return new OperationResult(cost, null); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 9d891f7d37e..95e5acc6ff1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -29,6 +30,8 @@ /** The Ext code size operation. */ public class ExtCodeSizeOperation extends AbstractOperation { + static final Bytes EOF_SIZE = Bytes.of(2); + /** * Instantiates a new Ext code size operation. * @@ -62,8 +65,18 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } else { final Account account = frame.getWorldUpdater().get(address); - frame.pushStackItem( - account == null ? Bytes.EMPTY : Words.intBytes(account.getCode().size())); + Bytes codeSize; + if (account == null) { + codeSize = Bytes.EMPTY; + } else { + final Bytes code = account.getCode(); + if (code.size() >= 2 && code.get(0) == EOFLayout.EOF_PREFIX_BYTE && code.get(1) == 0) { + codeSize = EOF_SIZE; + } else { + codeSize = Words.intBytes(code.size()); + } + } + frame.pushStackItem(codeSize); return new OperationResult(cost, null); } } catch (final UnderflowException ufe) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java new file mode 100644 index 00000000000..bddb337c73b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Delegate call operation. */ +public class ExtDelegateCallOperation extends AbstractExtCallOperation { + + static final int STACK_INPUT_OFFSET = 1; + static final int STACK_INPUT_LENGTH = 2; + + /** + * Instantiates a new Delegate call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtDelegateCallOperation(final GasCalculator gasCalculator) { + super(0xF9, "EXTDELEGATECALL", 3, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return frame.getApparentValue(); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getSenderAddress(); + } + + @Override + protected boolean isDelegate() { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java new file mode 100644 index 00000000000..3a202f46e71 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Static call operation. */ +public class ExtStaticCallOperation extends AbstractExtCallOperation { + + static final int STACK_INPUT_OFFSET = 1; + static final int STACK_INPUT_LENGTH = 2; + + /** + * Instantiates a new Static call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtStaticCallOperation(final GasCalculator gasCalculator) { + super(0xFB, "EXTSTATICCALL", 3, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return value(frame); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return to(frame); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + protected boolean isStatic(final MessageFrame frame) { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java index 86c6f906607..c76caa9667a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java @@ -14,8 +14,7 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -27,7 +26,7 @@ public class JumpFOperation extends AbstractOperation { public static final int OPCODE = 0xe5; /** The Jump F success operation result. */ - static final OperationResult jumpfSuccess = new OperationResult(3, null); + static final OperationResult jumpfSuccess = new OperationResult(5, null); /** * Instantiates a new Jump F operation. @@ -40,26 +39,15 @@ public JumpFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC()); - } - - /** - * Performs Jump F operation. - * - * @param frame the frame - * @param code the code - * @param pc the pc - * @return the successful operation result - */ - public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc) { - int section = readBigEndianU16(pc + 1, code); - var exception = frame.jumpFunction(section); - if (exception == null) { - return jumpfSuccess; - } else { - return new OperationResult(jumpfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + int pc = frame.getPC(); + int section = code.readBigEndianU16(pc + 1); + var info = code.getCodeSection(section); + frame.setPC(info.getEntryPoint() - 1); // will be +1ed at end of operations loop + frame.setSection(section); + return jumpfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java index 839b8612fe2..cf345fc9308 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -21,7 +22,7 @@ import org.apache.tuweni.bytes.Bytes; /** The type Relative jump If operation. */ -public class RelativeJumpIfOperation extends RelativeJumpOperation { +public class RelativeJumpIfOperation extends AbstractFixedCostOperation { /** The constant OPCODE. */ public static final int OPCODE = 0xe1; @@ -37,11 +38,16 @@ public RelativeJumpIfOperation(final GasCalculator gasCalculator) { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } final Bytes condition = frame.popStackItem(); - // If condition is zero (false), no jump is will be performed. Therefore, skip the rest. if (!condition.isZero()) { - return super.executeFixedCostOperation(frame, evm); + final int pcPostInstruction = frame.getPC() + 1; + return new OperationResult(gasCost, null, 2 + code.readBigEndianI16(pcPostInstruction) + 1); + } else { + return new OperationResult(gasCost, null, 2 + 1); } - return new OperationResult(gasCost, null, 2 + 1); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java index e96d747ff17..eb63d2409f2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java @@ -14,12 +14,10 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes; /** The type Relative jump operation. */ public class RelativeJumpOperation extends AbstractFixedCostOperation { @@ -58,9 +56,11 @@ protected RelativeJumpOperation( @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - final Bytes code = frame.getCode().getBytes(); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } final int pcPostInstruction = frame.getPC() + 1; - return new OperationResult( - gasCost, null, 2 + Words.readBigEndianI16(pcPostInstruction, code.toArrayUnsafe()) + 1); + return new OperationResult(gasCost, null, 2 + code.readBigEndianI16(pcPostInstruction) + 1); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java index 7304ffa8bb0..22d4d4e53eb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java @@ -14,8 +14,7 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -39,36 +38,42 @@ public RelativeJumpVectorOperation(final GasCalculator gasCalculator) { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - final Bytes code = frame.getCode().getBytes(); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } int offsetCase; try { - offsetCase = frame.popStackItem().toInt(); + offsetCase = frame.popStackItem().trimLeadingZeros().toInt(); if (offsetCase < 0) { offsetCase = Integer.MAX_VALUE; } } catch (ArithmeticException | IllegalArgumentException ae) { offsetCase = Integer.MAX_VALUE; } - final int vectorSize = getVectorSize(code, frame.getPC() + 1); + final int vectorSize = getVectorSize(code.getBytes(), frame.getPC() + 1); + int jumpDelta = + (offsetCase < vectorSize) + ? code.readBigEndianI16( + frame.getPC() + 2 + offsetCase * 2) // lookup delta if offset is in vector + : 0; // if offsetCase is outside the vector the jump delta is zero / next opcode. return new OperationResult( gasCost, null, - 1 - + 2 * vectorSize - + ((offsetCase >= vectorSize) - ? 0 - : readBigEndianI16(frame.getPC() + 2 + offsetCase * 2, code.toArrayUnsafe())) - + 1); + 2 // Opcode + length immediate + + 2 * vectorSize // vector size + + jumpDelta); } /** - * Gets vector size. + * Gets vector size. Vector size is one greater than length immediate, because (a) zero length + * tables are useless and (b) it allows for 256 byte tables * * @param code the code * @param offsetCountByteIndex the offset count byte index * @return the vector size */ public static int getVectorSize(final Bytes code, final int offsetCountByteIndex) { - return code.get(offsetCountByteIndex) & 0xff; + return (code.toArrayUnsafe()[offsetCountByteIndex] & 0xff) + 1; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java index 1e5155fb81c..4efa71eaeb1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -38,11 +39,15 @@ public RetFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - var exception = frame.returnFunction(); - if (exception == null) { - return retfSuccess; - } else { - return new OperationResult(retfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + var rStack = frame.getReturnStack(); + var returnInfo = rStack.pop(); + frame.setPC(returnInfo.pc()); + frame.setSection(returnInfo.codeSectionIndex()); + + return retfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java new file mode 100644 index 00000000000..c8031f4c4d8 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** The Return operation. */ +public class ReturnContractOperation extends AbstractOperation { + + /** Opcode of RETURNCONTRACT operation */ + public static final int OPCODE = 0xEE; + + /** + * Instantiates a new Return operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnContractOperation(final GasCalculator gasCalculator) { + super(OPCODE, "RETURNCONTRACT", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + int pc = frame.getPC(); + int index = code.readU8(pc + 1); + + final long from = clampedToLong(frame.popStackItem()); + final long length = clampedToLong(frame.popStackItem()); + + final long cost = gasCalculator().memoryExpansionGasCost(frame, from, length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + if (index >= code.getSubcontainerCount()) { + return new OperationResult(cost, ExceptionalHaltReason.NONEXISTENT_CONTAINER); + } + + Bytes auxData = frame.readMemory(from, length); + Optional newCode = code.getSubContainer(index, auxData); + if (newCode.isEmpty()) { + return new OperationResult(cost, ExceptionalHaltReason.NONEXISTENT_CONTAINER); + } + + frame.setCreatedCode(newCode.get()); + frame.setState(MessageFrame.State.CODE_SUCCESS); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java index c7c69149db7..e6c91b0ee47 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java @@ -51,13 +51,15 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final Bytes returnData = frame.getReturnData(); final int returnDataLength = returnData.size(); - try { - final long end = Math.addExact(sourceOffset, numBytes); - if (end > returnDataLength) { - return INVALID_RETURN_DATA_BUFFER_ACCESS; + if (frame.getCode().getEofVersion() < 1) { + try { + final long end = Math.addExact(sourceOffset, numBytes); + if (end > returnDataLength) { + return INVALID_RETURN_DATA_BUFFER_ACCESS; + } + } catch (final ArithmeticException ae) { + return OUT_OF_BOUNDS; } - } catch (final ArithmeticException ae) { - return OUT_OF_BOUNDS; } final long cost = gasCalculator().dataCopyOperationGasCost(frame, memOffset, numBytes); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java new file mode 100644 index 00000000000..7d8a5e42091 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Return data copy operation. */ +public class ReturnDataLoadOperation extends AbstractOperation { + + /** + * Instantiates a new Return data copy operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnDataLoadOperation(final GasCalculator gasCalculator) { + super(0xf7, "RETURNDATALOAD", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final int offset = clampedToInt(frame.popStackItem()); + Bytes returnData = frame.getReturnData(); + int retunDataSize = returnData.size(); + + Bytes value; + if (offset > retunDataSize) { + value = Bytes.EMPTY; + } else if (offset + 32 >= returnData.size()) { + value = Bytes32.rightPad(returnData.slice(offset)); + } else { + value = returnData.slice(offset, 32); + } + + frame.pushStackItem(value); + return new OperationResult(3L, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java index 7bbdb00682f..c3026be5d43 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java @@ -23,6 +23,9 @@ /** The Stop operation. */ public class StopOperation extends AbstractFixedCostOperation { + /** Opcode of STOP operation */ + public static final int OPCODE = 0x00; + /** The Stop operation success result. */ static final OperationResult stopSuccess = new OperationResult(0, null); @@ -32,7 +35,7 @@ public class StopOperation extends AbstractFixedCostOperation { * @param gasCalculator the gas calculator */ public StopOperation(final GasCalculator gasCalculator) { - super(0x00, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); + super(OPCODE, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java new file mode 100644 index 00000000000..badd14b34f4 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The SwapN operation. */ +public class SwapNOperation extends AbstractFixedCostOperation { + + /** SWAPN Opcode 0xe7 */ + public static final int OPCODE = 0xe7; + + /** The Swap operation success result. */ + static final OperationResult swapSuccess = new OperationResult(3, null); + + /** + * Instantiates a new SwapN operation. + * + * @param gasCalculator the gas calculator + */ + public SwapNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "SWAPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + int index = code.readU8(pc + 1); + + final Bytes tmp = frame.getStackItem(0); + frame.setStackItem(0, frame.getStackItem(index + 1)); + frame.setStackItem(index + 1, tmp); + frame.setPC(pc + 1); + + return swapSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java index 2511492a1a0..684d18f987a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.apache.tuweni.bytes.Bytes32; @@ -50,8 +49,6 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } } catch (final UnderflowException ufe) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(cost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java index 7116229c48c..5e668326345 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java @@ -243,12 +243,12 @@ public Code getCodeFromEVM(@Nonnull final Hash codeHash, final Bytes codeBytes) } /** - * Gets code from evm, skipping the code cache + * Gets code from evm, with handling for EOF code plus calldata * * @param codeBytes the code bytes * @return the code from evm */ - public Code getCodeFromEVMUncached(final Bytes codeBytes) { - return evm.getCodeUncached(codeBytes); + public Code getCodeFromEVMForCreation(final Bytes codeBytes) { + return evm.getCodeForCreation(codeBytes); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java index 744f7147fdf..0e47db90acb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java @@ -137,7 +137,8 @@ public void start(final MessageFrame frame, final OperationTracer operationTrace @Override public void codeSuccess(final MessageFrame frame, final OperationTracer operationTracer) { - final Bytes contractCode = frame.getOutputData(); + final Bytes contractCode = + frame.getCreatedCode() == null ? frame.getOutputData() : frame.getCreatedCode().getBytes(); final long depositFee = gasCalculator.codeDepositGasCost(contractCode.size()); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java index 86abd3ba06d..4ea5bc1b7f7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java @@ -16,6 +16,7 @@ import static com.google.common.base.Strings.padStart; +import org.hyperledger.besu.evm.code.OpcodeInfo; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.AbstractCallOperation; @@ -48,6 +49,7 @@ public class StandardJsonTracer implements OperationTracer { private Bytes memory; private int memorySize; private int depth; + private int subdepth; private String storageString; /** @@ -135,6 +137,7 @@ public void tracePreExecution(final MessageFrame messageFrame) { memory = null; } depth = messageFrame.getMessageStackSize(); + subdepth = messageFrame.returnStackSize(); StringBuilder sb = new StringBuilder(); if (showStorage) { @@ -181,10 +184,22 @@ public void tracePostExecution( final StringBuilder sb = new StringBuilder(1024); sb.append("{"); sb.append("\"pc\":").append(pc).append(","); - if (section > 0) { + boolean eofContract = messageFrame.getCode().getEofVersion() > 0; + if (eofContract) { sb.append("\"section\":").append(section).append(","); } sb.append("\"op\":").append(opcode).append(","); + OpcodeInfo opInfo = OpcodeInfo.getOpcode(opcode); + if (eofContract && opInfo.pcAdvance() > 1) { + var immediate = + messageFrame + .getCode() + .getBytes() + .slice( + pc + messageFrame.getCode().getCodeSection(0).getEntryPoint() + 1, + opInfo.pcAdvance() - 1); + sb.append("\"immediate\":\"").append(immediate.toHexString()).append("\","); + } sb.append("\"gas\":\"").append(gas).append("\","); sb.append("\"gasCost\":\"").append(shortNumber(thisGasCost)).append("\","); if (memory != null) { @@ -198,6 +213,9 @@ public void tracePostExecution( sb.append("\"returnData\":\"").append(returnData.toHexString()).append("\","); } sb.append("\"depth\":").append(depth).append(","); + if (subdepth > 1) { + sb.append("\"subdepth\":").append(subdepth).append(","); + } sb.append("\"refund\":").append(messageFrame.getGasRefund()).append(","); sb.append("\"opName\":\"").append(currentOp.getName()).append("\""); if (executeResult.getHaltReason() != null) { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java b/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java new file mode 100644 index 00000000000..f71adfee2a1 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java @@ -0,0 +1,95 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm; + +import org.apache.tuweni.bytes.Bytes; + +public class EOFTestConstants { + + public static final Bytes INNER_CONTRACT = + bytesFromPrettyPrint( + """ + # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0009 # Code section 0 , 9 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0003 # max stack: 3 + # Code section 0 + 5f # [0] PUSH0 + 35 # [1] CALLDATALOAD + 5f # [2] PUSH0 + 5f # [3] PUSH0 + a1 # [4] LOG1 + 5f # [5] PUSH0 + 5f # [6] PUSH0 + ee00 # [7] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 + 00 # [0] STOP + """); + + public static Bytes EOF_CREATE_CONTRACT = + bytesFromPrettyPrint( + String.format( + """ + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000e # Code section 0 , 14 bytes + 030001 # Total subcontainers ( 1 ) + %04x # Subcontainer 0 size ? + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 + 61c0de # [0] PUSH2(0xc0de) + 5f # [3] PUSH0 + 52 # [4] MSTORE + 6002 # [5] PUSH1(2) + 601e # [7] PUSH1 30 + 5f # [9] PUSH0 + 5f # [10] PUSH0 + ec00 # [11] EOFCREATE(0) + 00 # [13] STOP + # Data section (empty) + %s # subcontainer + """, + INNER_CONTRACT.size(), INNER_CONTRACT.toUnprefixedHexString())); + + public static Bytes bytesFromPrettyPrint(final String prettyPrint) { + return Bytes.fromHexString(prettyPrint.replaceAll("#.*?\n", "").replaceAll("\\s", "")); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java index adcf5b3585f..d3335d077cf 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java @@ -25,12 +25,12 @@ class CodeFactoryTest { @Test void invalidCodeIncompleteMagic() { - invalidCode("0xEF"); + invalidCode("0xEF", true); } @Test void invalidCodeInvalidMagic() { - invalidCode("0xEFFF0101000302000400600000AABBCCDD"); + invalidCode("0xEFFF0101000302000400600000AABBCCDD", true); } @Test @@ -179,7 +179,12 @@ void invalidCodeUnknownSectionId3() { } private static void invalidCode(final String str) { - Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1, true); + Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1); + assertThat(code.isValid()).isFalse(); + } + + private static void invalidCode(final String str, final boolean legacy) { + Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1, legacy, false); assertThat(code.isValid()).isFalse(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java index 36416329d12..1bc984222b4 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java @@ -62,7 +62,7 @@ void startUp() { void shouldReuseJumpDestMap() { final JumpOperation operation = new JumpOperation(gasCalculator); final Bytes jumpBytes = Bytes.fromHexString("0x6003565b00"); - final CodeV0 getsCached = (CodeV0) spy(CodeFactory.createCode(jumpBytes, 0, false)); + final CodeV0 getsCached = (CodeV0) spy(CodeFactory.createCode(jumpBytes, 0)); MessageFrame frame = createJumpFrame(getsCached); OperationResult result = operation.execute(frame, evm); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java index 5f06bb48727..8eda50316fd 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java @@ -18,12 +18,13 @@ import static org.hyperledger.besu.evm.code.CodeV1Validation.validateCode; import static org.hyperledger.besu.evm.code.CodeV1Validation.validateStack; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -39,10 +40,50 @@ class CodeV1Test { public static final String ZERO_HEX = "00"; public static final String NOOP_HEX = "5b"; + private static void assertValidation(final String error, final String code) { + assertValidation(error, code, false, 1, 5); + } + + private static void assertValidation( + final String error, + final String code, + final boolean returning, + final int... codeSectionSizes) { + Bytes codeBytes = Bytes.fromHexString(code); + for (int i : codeSectionSizes) { + CodeSection[] codeSections = new CodeSection[i]; + Arrays.fill(codeSections, new CodeSection(1, 0, returning ? 0 : 0x80, 1, 1)); + EOFLayout testLayout = + new EOFLayout( + codeBytes, + 1, + codeSections, + new EOFLayout[0], + 0, + Bytes.EMPTY, + error, + new AtomicReference<>()); + assertValidation(error, codeBytes, codeSections[0], testLayout); + } + } + + private static void assertValidation( + final String error, + final Bytes codeBytes, + final CodeSection thisCodeSection, + final EOFLayout eofLayout) { + final String validationError = validateCode(codeBytes, thisCodeSection, eofLayout); + if (error == null) { + assertThat(validationError).isNull(); + } else { + assertThat(validationError).startsWith(error); + } + } + @Test void validCode() { String codeHex = - "0xEF0001 01000C 020003 000b 0002 0008 030000 00 00000000 02010001 01000002 60016002e30001e30002e4 01e4 60005360106000f3"; + "0xEF0001 01000C 020003 000b 0002 0008 040000 00 00800000 02010001 01000002 60016002e30001e30002f3 01e4 60005360106000e4"; final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); String validationError = validateCode(layout); @@ -50,26 +91,36 @@ void validCode() { assertThat(validationError).isNull(); } + @Test + void invalidCode() { + String codeHex = + "0xEF0001 01000C 020003 000b 0002 0008 040000 00 00000000 02010001 01000002 60016002e30001e30002f3 01e4 60005360106000e4"; + final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); + + String validationError = validateCode(layout); + + assertThat(validationError) + .isEqualTo( + "Invalid EOF container - Code section at zero expected non-returning flag, but had return stack of 0"); + } + @ParameterizedTest @ValueSource( - strings = {"3000", "5000", "e0000000", "6000e1000000", "6000e201000000", "fe00", "0000"}) + strings = {"3000", "5000", "e0000000", "6000e1000000", "6000e200000000", "fe00", "0000"}) void testValidOpcodes(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } @ParameterizedTest @ValueSource(strings = {"00", "3030f3", "3030fd", "fe"}) void testValidCodeTerminator(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } @ParameterizedTest @MethodSource("testPushValidImmediateArguments") void testPushValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testPushValidImmediateArguments() { @@ -82,8 +133,7 @@ private static Stream testPushValidImmediateArguments() { @ParameterizedTest @MethodSource("testRjumpValidImmediateArguments") void testRjumpValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testRjumpValidImmediateArguments() { @@ -103,8 +153,7 @@ private static Stream testRjumpValidImmediateArguments() { @ParameterizedTest @MethodSource("testRjumpiValidImmediateArguments") void testRjumpiValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testRjumpiValidImmediateArguments() { @@ -125,28 +174,26 @@ private static Stream testRjumpiValidImmediateArguments() { @ParameterizedTest @MethodSource("rjumptableValidImmediateArguments") void testRjumptableValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream rjumptableValidImmediateArguments() { return Stream.of( - "6001e201000000", - "6001e202000000010000", - "6001e203000000040100" + "5b".repeat(256) + ZERO_HEX, - "6001e2040000000401007fff" + "5b".repeat(32767) + ZERO_HEX, - "6001e201fffc0000", - "5b".repeat(248) + "6001e202fffaff0000", - "5b".repeat(32760) + "6001e202fffa800000", - "e201000000") + "6001e200000000", + "6001e201000000010000", + "6001e202000600080100" + "5b".repeat(256) + ZERO_HEX, + "6001e2030008000801007ffe" + "5b".repeat(32767) + ZERO_HEX, + "6001e200fffc0000", + "5b".repeat(252) + "6001e201fffaff0000", + "5b".repeat(32764) + "6001e201fffa800000", + "e200000000") .map(Arguments::arguments); } @ParameterizedTest @MethodSource("invalidCodeArguments") void testInvalidCode(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).startsWith("Invalid Instruction 0x"); + assertValidation("Invalid Instruction 0x", code); } private static Stream invalidCodeArguments() { @@ -154,13 +201,12 @@ private static Stream invalidCodeArguments() { IntStream.rangeClosed(0x0c, 0x0f), IntStream.of(0x1e, 0x1f), IntStream.rangeClosed(0x21, 0x2f), - IntStream.rangeClosed(0x49, 0x4f), + IntStream.rangeClosed(0x4b, 0x4f), IntStream.rangeClosed(0xa5, 0xaf), - IntStream.rangeClosed(0xb0, 0xbf), - IntStream.rangeClosed(0xc0, 0xcf), - IntStream.rangeClosed(0xd0, 0xdf), - IntStream.rangeClosed(0xe5, 0xef), - IntStream.of(0xf6, 0xf7, 0xf8, 0xf9, 0xfb, 0xfc)) + IntStream.rangeClosed(0xb0, 0xcf), + IntStream.rangeClosed(0xd4, 0xdf), + IntStream.rangeClosed(0xe9, 0xeb), + IntStream.of(0xef, 0xf6, 0xfc)) .flatMapToInt(i -> i) .mapToObj(i -> String.format("%02x", i) + ZERO_HEX) .map(Arguments::arguments); @@ -169,8 +215,7 @@ private static Stream invalidCodeArguments() { @ParameterizedTest @MethodSource("pushTruncatedImmediateArguments") void testPushTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("No terminating instruction"); + assertValidation("No terminating instruction", code); } private static Stream pushTruncatedImmediateArguments() { @@ -184,15 +229,13 @@ private static Stream pushTruncatedImmediateArguments() { @ParameterizedTest @ValueSource(strings = {"e0", "e000"}) void testRjumpTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated relative jump offset"); + assertValidation("Truncated relative jump offset", code); } @ParameterizedTest @ValueSource(strings = {"6001e1", "6001e100"}) void testRjumpiTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated relative jump offset"); + assertValidation("Truncated relative jump offset", code); } @ParameterizedTest @@ -206,8 +249,7 @@ void testRjumpiTruncatedImmediate(final String code) { "6001e2030000000100" }) void testRjumpvTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated jump table"); + assertValidation("Truncated jump table", code); } @ParameterizedTest @@ -219,12 +261,11 @@ void testRjumpvTruncatedImmediate(final String code) { "6001e10000", "6001e1000100", "6001e1fffa00", - "6001e201000100", - "6001e201fff900" + "6001e200000300", + "6001e200fff900" }) void testRjumpsOutOfBounds(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Relative jump destination out of bounds"); + assertValidation("Relative jump destination out of bounds", code); } @ParameterizedTest @@ -240,44 +281,44 @@ void testRjumpsOutOfBounds(final String code) { "6001e10001e0000000", "6001e10002e0000000", // RJUMPV into RJUMP immediate - "6001e2010001e0000000", - "6001e2010002e0000000", + "6001e2000001e0000000", + "6001e2000002e0000000", // RJUMP into RJUMPI immediate "e000036001e1000000", "e000046001e1000000", + // RJUMPI backwards into push + "6001e1fffc00", // RJUMPI into RJUMPI immediate "6001e1ffff00", "6001e1fffe00", "6001e100036001e1000000", "6001e100046001e1000000", // RJUMPV into RJUMPI immediate - "6001e20100036001e1000000", - "6001e20100046001e1000000", + "6001e20000036001e1000000", + "6001e20000046001e1000000", // RJUMP into RJUMPV immediate - "e00001e201000000", - "e00002e201000000", - "e00003e201000000", + "e00001e200000000", + "e00002e200000000", + "e00003e200000000", // RJUMPI into RJUMPV immediate - "6001e10001e201000000", - "6001e10002e201000000", - "6001e10003e201000000", + "6001e10001e200000000", + "6001e10002e200000000", + "6001e10003e200000000", // RJUMPV into RJUMPV immediate - "6001e201ffff00", - "6001e201fffe00", - "6001e201fffd00", - "6001e2010001e201000000", - "6001e2010002e201000000", - "6001e2010003e201000000", - "6001e2010001e2020000fff400", - "6001e2010002e2020000fff400", - "6001e2010003e2020000fff400", - "6001e2010004e2020000fff400", - "6001e2010005e2020000fff400" + "6001e200ffff00", + "6001e200fffe00", + "6001e200fffd00", + "6001e2000001e200000000", + "6001e2000002e200000000", + "6001e2000003e200000000", + "6001e2000001e2010000000000", + "6001e2000002e2010000000000", + "6001e2000003e2010000000000", + "6001e2000004e2010000000000", + "6001e2000005e2010000000000" }) void testRjumpsIntoImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError) - .isEqualTo("Relative jump destinations targets invalid immediate data"); + assertValidation("Relative jump destinations targets invalid immediate data", code); } private static Stream rjumpsIntoImmediateExtraArguments() { @@ -302,7 +343,7 @@ private static Stream rjumpsIntoImmediateExtraArguments() { ZERO_HEX.repeat(n) + // push data ZERO_HEX, // STOP - String.format("6001e20100%02x", offset) + String.format("6001e20000%02x", offset) + String.format("%02x", 0x60 + n - 1) + // PUSHn ZERO_HEX.repeat(n) @@ -314,63 +355,52 @@ private static Stream rjumpsIntoImmediateExtraArguments() { .map(Arguments::arguments); } - @ParameterizedTest - @ValueSource(strings = {"6001e20000"}) - void testRjumpvEmptyTable(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Empty jump table"); - } - @ParameterizedTest @ValueSource(strings = {"e3", "e300"}) void testCallFTruncated(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF"); + assertValidation("Truncated CALLF", code); } @ParameterizedTest @ValueSource(strings = {"e5", "e500"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpCallFTruncated(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF"); + assertValidation("Truncated JUMPF", code); } @ParameterizedTest @ValueSource(strings = {"e30004", "e303ff", "e3ffff"}) void testCallFWrongSection(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF to non-existent section -"); + assertValidation("CALLF to non-existent section -", code, false, 3); } @ParameterizedTest @ValueSource(strings = {"e50004", "e503ff", "e5ffff"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpFWrongSection(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF to non-existent section -"); + assertValidation("JUMPF to non-existent section -", code, false, 3); } @ParameterizedTest - @ValueSource(strings = {"e3000100", "e3000200", "e3000000"}) + @ValueSource(strings = {"e3000100", "e3000200"}) void testCallFValid(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).isNull(); + var testContainer = + EOFLayout.parseEOF( + Bytes.fromHexString( + "ef000101000c0200030001000100010400000000800000000000000000000000e4e4")); + + assertValidation( + null, Bytes.fromHexString(code), testContainer.getCodeSection(0), testContainer); } @ParameterizedTest @ValueSource(strings = {"e50001", "e50002", "e50000"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpFValid(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).isNull(); + assertValidation(null, code, false, 3); } @ParameterizedTest @MethodSource("immediateContainsOpcodeArguments") void testImmediateContainsOpcode(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream immediateContainsOpcodeArguments() { @@ -394,7 +424,7 @@ private static Stream immediateContainsOpcodeArguments() { // 0x60 byte which could be interpreted as PUSH, but it's not because it's in RJUMPV // data // offset = -160 - "5b".repeat(160) + "e201ff6000") + "5b".repeat(160) + "e200ff6000") .map(Arguments::arguments); } @@ -439,14 +469,15 @@ void validateStackAnalysis( + String.format("01%04x", sectionCount * 4) + String.format("02%04x", sectionCount) + codeLengths - + "030000" + + "040000" + "00" + typesData + codeData; EOFLayout eofLayout = EOFLayout.parseEOF(Bytes.fromHexString(sb)); - assertThat(validateStack(sectionToTest, eofLayout)).isEqualTo(expectedError); + assertThat(validateStack(sectionToTest, eofLayout, new WorkList(sectionCount))) + .isEqualTo(expectedError); } /** @@ -457,85 +488,86 @@ void validateStackAnalysis( * @return parameterized test vectors */ static Stream stackEmpty() { - return Stream.of(Arguments.of("Empty", null, 0, List.of(List.of("00", 0, 0, 0)))); + return Stream.of(Arguments.of("Empty", null, 0, List.of(List.of("00", 0, 0x80, 0)))); } static Stream stackEmptyAtExit() { return Stream.of( // this depends on requiring stacks to be "clean" returns - Arguments.of("Stack Empty at Exit", null, 0, List.of(List.of("43 50 00", 0, 0, 1))), + Arguments.of("Stack Empty at Exit", null, 0, List.of(List.of("43 50 00", 0, 0x80, 1))), Arguments.of( "Stack empty with input", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("50 00", 1, 0, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("50 00", 1, 0x80, 1))), // this depends on requiring stacks to be "clean" returns Arguments.of( "Stack not empty at output", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("00", 1, 0, 1)))); + List.of(List.of("00", 0, 0x80, 0), List.of("00", 1, 0x80, 1)))); } static Stream stackImmediateBytes() { return Stream.of( Arguments.of( - "Immediate Bytes - simple push", null, 0, List.of(List.of("6001 50 00", 0, 0, 1)))); + "Immediate Bytes - simple push", null, 0, List.of(List.of("6001 50 00", 0, 0x80, 1)))); } static Stream stackUnderflow() { return Stream.of( Arguments.of( "Stack underflow", - "Operation 0x50 requires stack of 1 but only has 0 items", + "Operation 0x50 requires stack of 1 but may only have 0 items", 0, - List.of(List.of("50 00", 0, 0, 1)))); + List.of(List.of("50 00", 0, 0x80, 1)))); } static Stream stackRJumpForward() { return Stream.of( - Arguments.of("RJUMP 0", null, 0, List.of(List.of("e00000 00", 0, 0, 0))), + Arguments.of("RJUMP 0", null, 0, List.of(List.of("e00000 00", 0, 0x80, 0))), Arguments.of( "RJUMP 1 w/ dead code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("e00001 43 00", 0, 0, 0))), + List.of(List.of("e00001 43 00", 0, 0x80, 0))), Arguments.of( "RJUMP 2 w/ dead code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("e00002 43 50 00", 0, 0, 0))), + List.of(List.of("e00002 43 50 00", 0, 0x80, 0))), Arguments.of( "RJUMP 3 and -10", - null, + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("e00003 01 50 00 6001 6001 e0fff6", 0, 0, 2)))); + List.of(List.of("e00003 01 50 00 6001 6001 e0fff6", 0, 0x80, 2)))); } static Stream stackRJumpBackward() { return Stream.of( - Arguments.of("RJUMP -3", null, 0, List.of(List.of("e0fffd", 0, 0, 0))), - Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B e0fffc", 0, 0, 0))), + Arguments.of("RJUMP -3", null, 0, List.of(List.of("e0fffd", 0, 0x80, 0))), + Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B e0fffc", 0, 0x80, 0))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (0) does not match previous value (1)", + "Stack minimum violation on backwards jump from 1 to 0, 1 != 1", 0, - List.of(List.of("43 e0fffc", 0, 0, 0))), + List.of(List.of("43 e0fffc", 0, 0x80, 0))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (1) does not match previous value (0)", + "Stack minimum violation on backwards jump from 2 to 1, 0 != 0", 0, - List.of(List.of("43 50 e0fffc 00", 0, 0, 0))), - Arguments.of("RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 e0fffd", 0, 0, 1))), + List.of(List.of("43 50 e0fffc 00", 0, 0x80, 0))), Arguments.of( - "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B e0fffc", 0, 0, 1))), + "RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 e0fffd", 0, 0x80, 1))), Arguments.of( - "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 e0fffb", 0, 0, 1))), + "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B e0fffc", 0, 0x80, 1))), + Arguments.of( + "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 e0fffb", 0, 0x80, 1))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (0) does not match previous value (1)", + "Stack minimum violation on backwards jump from 3 to 2, 1 != 1", 0, - List.of(List.of("43 50 43 e0fffc 50 00", 0, 0, 0)))); + List.of(List.of("43 50 43 e0fffc 50 00", 0, 0x80, 0)))); } static Stream stackRJumpI() { @@ -544,61 +576,61 @@ static Stream stackRJumpI() { "RJUMPI Each branch ending with STOP", null, 0, - List.of(List.of("60ff 6001 e10002 50 00 50 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10002 50 00 50 00", 0, 0x80, 2))), Arguments.of( "RJUMPI One branch ending with RJUMP", null, 0, - List.of(List.of("60ff 6001 e10004 50 e00001 50 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10004 50 e00001 50 00", 0, 0x80, 2))), Arguments.of( "RJUMPI Fallthrough", null, 0, - List.of(List.of("60ff 6001 e10004 80 80 50 50 50 00", 0, 0, 3))), + List.of(List.of("60ff 6001 e10004 80 80 50 50 50 00", 0, 0x80, 3))), Arguments.of( - "RJUMPI Offset 0", null, 0, List.of(List.of("60ff 6001 e10000 50 00", 0, 0, 2))), + "RJUMPI Offset 0", null, 0, List.of(List.of("60ff 6001 e10000 50 00", 0, 0x80, 2))), Arguments.of( "Simple loop (RJUMPI offset = -5)", null, 0, - List.of(List.of("6001 60ff 81 02 80 e1fffa 50 50 00", 0, 0, 3))), + List.of(List.of("6001 60ff 81 02 80 e1fffa 50 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI One branch increasing max stack more stack than another", null, 0, - List.of(List.of("6001 e10007 30 30 30 50 50 50 00 30 50 00", 0, 0, 3))), + List.of(List.of("6001 e10007 30 30 30 50 50 50 00 30 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI One branch increasing max stack more stack than another II", null, 0, - List.of(List.of("6001 e10003 30 50 00 30 30 30 50 50 50 00", 0, 0, 3))), + List.of(List.of("6001 e10003 30 50 00 30 30 30 50 50 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI Missing stack argument", - "Operation 0xE1 requires stack of 1 but only has 0 items", + "Operation 0xE1 requires stack of 1 but may only have 0 items", 0, - List.of(List.of("e10000 00", 0, 0, 0))), + List.of(List.of("e10000 00", 0, 0x80, 0))), Arguments.of( "Stack underflow one branch", - "Operation 0x02 requires stack of 2 but only has 1 items", + "Operation 0x02 requires stack of 2 but may only have 1 items", 0, - List.of(List.of("60ff 6001 e10002 50 00 02 50 00", 0, 0, 0))), + List.of(List.of("60ff 6001 e10002 50 00 02 50 00", 0, 0x80, 0))), Arguments.of( "Stack underflow another branch", - "Operation 0x02 requires stack of 2 but only has 1 items", + "Operation 0x02 requires stack of 2 but may only have 1 items", 0, - List.of(List.of("60ff 6001 e10002 02 00 19 50 00", 0, 0, 0))), + List.of(List.of("60ff 6001 e10002 02 00 19 50 00", 0, 0x80, 0))), // this depends on requiring stacks to be "clean" returns Arguments.of( "RJUMPI Stack not empty in the end of one branch", null, 0, - List.of(List.of("60ff 6001 e10002 50 00 19 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10002 50 00 19 00", 0, 0x80, 2))), // this depends on requiring stacks to be "clean" returns Arguments.of( "RJUMPI Stack not empty in the end of one branch II", null, 0, - List.of(List.of("60ff 6001 e10002 19 00 50 00", 0, 0, 2)))); + List.of(List.of("60ff 6001 e10002 19 00 50 00", 0, 0x80, 2)))); } static Stream stackCallF() { @@ -607,225 +639,231 @@ static Stream stackCallF() { "0 input 0 output", null, 0, - List.of(List.of("e30001 00", 0, 0, 0), List.of("e4", 0, 0, 0))), + List.of(List.of("e30001 00", 0, 0x80, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 inputs, 0 output 3 sections", null, 0, - List.of(List.of("e30002 00", 0, 0, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), + List.of( + List.of("e30002 00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 inputs", null, 0, - List.of(List.of("30 e30001 00", 0, 0, 1), List.of("00", 1, 0, 1))), + List.of(List.of("30 e30001 00", 0, 0x80, 1), List.of("00", 1, 0x80, 1))), Arguments.of( "forwarding an argument", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30002 00", 1, 0, 1), List.of("00", 1, 0, 1))), + List.of( + List.of("00", 0, 0x80, 0), + List.of("e30002 00", 1, 0x80, 1), + List.of("00", 1, 0x80, 1))), Arguments.of( "more than 1 inputs", null, 0, - List.of(List.of("30 80 e30001 00", 0, 0, 2), List.of("00", 2, 0, 2))), + List.of(List.of("30 80 e30001 00", 0, 0x80, 2), List.of("00", 2, 0x80, 2))), Arguments.of( "more than 0 outputs", null, 0, - List.of(List.of("e30001 50 00", 0, 0, 1), List.of("3000", 0, 1, 1))), + List.of(List.of("e30001 50 00", 0, 0x80, 1), List.of("30e4", 0, 1, 1))), Arguments.of( "more than 0 outputs 3 sections", null, 0, List.of( - List.of("e30002 50 00", 0, 0, 1), - List.of("00", 0, 0, 0), + List.of("e30002 50 00", 0, 0x80, 1), + List.of("00", 0, 0x80, 0), List.of("30305000", 0, 1, 2))), Arguments.of( "more than 1 outputs", null, 0, - List.of(List.of("e30001 50 50 00", 0, 0, 2), List.of("303000", 0, 2, 2))), + List.of(List.of("e30001 50 50 00", 0, 0x80, 2), List.of("3030e4", 0, 2, 2))), Arguments.of( "more than 0 inputs, more than 0 outputs", null, 0, List.of( - List.of("30 30 e30001 50 50 50 00", 0, 0, 3), - List.of("30 30 e30001 50 50 00", 2, 3, 5))), - Arguments.of("recursion", null, 0, List.of(List.of("e30000 00", 0, 0, 0))), + List.of("30 30 e30001 50 50 50 00", 0, 0x80, 3), + List.of("30 30 e30001 50 50 e4", 2, 3, 5))), + Arguments.of("recursion", null, 0, List.of(List.of("e30000 00", 0, 0x80, 0))), Arguments.of( "recursion 2 inputs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30000 00", 2, 0, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e30000 00", 2, 0x80, 2))), Arguments.of( "recursion 2 inputs 2 outputs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30000 50 50 00", 2, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e30000 50 50 00", 2, 2, 2))), Arguments.of( "recursion 2 inputs 1 output", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("30 30 e30001 50 50 50 00", 2, 1, 4))), + List.of(List.of("00", 0, 0x80, 0), List.of("30 30 e30001 50 50 50 00", 2, 1, 4))), Arguments.of( "multiple CALLFs with different types", null, 1, List.of( - List.of("00", 0, 0, 0), - List.of("44 e30002 80 80 e30003 44 80 e30004 50 50 00", 0, 0, 3), - List.of("3030505000", 1, 1, 3), - List.of("50505000", 3, 0, 3), - List.of("00", 2, 2, 2))), + List.of("00", 0, 0x80, 0), + List.of("44 e30002 80 80 e30003 44 80 e30004 50 50 e4", 0, 0, 3), + List.of("30305050e4", 1, 1, 3), + List.of("505050e4", 3, 0, 3), + List.of("e4", 2, 2, 2))), Arguments.of( "underflow", - "Operation 0xE3 requires stack of 1 but only has 0 items", + "Operation 0xE3 requires stack of 1 but may only have 0 items", 0, - List.of(List.of("e30001 00", 0, 0, 0), List.of("00", 1, 0, 0))), + List.of(List.of("e30001 00", 0, 0x80, 0), List.of("e4", 1, 0, 0))), Arguments.of( "underflow 2", - "Operation 0xE3 requires stack of 2 but only has 1 items", + "Operation 0xE3 requires stack of 2 but may only have 1 items", 0, - List.of(List.of("30 e30001 00", 0, 0, 0), List.of("00", 2, 0, 2))), + List.of(List.of("30 e30001 00", 0, 0x80, 0), List.of("e4", 2, 0, 2))), Arguments.of( "underflow 3", - "Operation 0xE3 requires stack of 1 but only has 0 items", + "Operation 0xE3 requires stack of 1 but may only have 0 items", 1, - List.of(List.of("00", 0, 0, 0), List.of("50 e30001 00", 1, 0, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("50 e30001 e4", 1, 0, 1))), Arguments.of( "underflow 4", - "Operation 0xE3 requires stack of 3 but only has 2 items", + "Operation 0xE3 requires stack of 3 but may only have 2 items", 0, List.of( - List.of("44 e30001 80 e30002 00", 0, 0, 0), - List.of("00", 1, 1, 1), - List.of("00", 3, 0, 3)))); + List.of("44 e30001 80 e30002 00", 0, 0x80, 0), + List.of("e4", 1, 1, 1), + List.of("e4", 3, 0, 3)))); } static Stream stackRetF() { return Stream.of( Arguments.of( "0 outputs at section 0", - null, + "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", 0, - List.of(List.of("e4", 0, 0, 0), List.of("00", 0, 0, 0))), + List.of(List.of("e4", 0, 0, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 outputs at section 1", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 0, 0, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 outputs at section 2", null, 2, - List.of(List.of("00", 0, 0, 0), List.of("00", 1, 1, 1), List.of("e4", 0, 0, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 outputs section 0", - null, + "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", 0, List.of(List.of("44 50 e4", 0, 0, 1), List.of("4400", 0, 1, 1))), Arguments.of( "more than 0 outputs section 0", - null, + "EOF Layout invalid - Code section at zero expected non-returning flag, but had return stack of 0", 1, List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 1, 1))), Arguments.of( "more than 1 outputs section 1", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("44 80 e4", 0, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("44 80 e4", 0, 2, 2))), Arguments.of( "Forwarding return values", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 1, 1, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1))), Arguments.of( "Forwarding of return values 2", null, 1, List.of( - List.of("00", 0, 0, 0), List.of("e30002 e4", 0, 1, 1), List.of("3000", 0, 1, 1))), + List.of("00", 0, 0x80, 0), + List.of("e30002 e4", 0, 1, 1), + List.of("30e4", 0, 1, 1))), Arguments.of( "Multiple RETFs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e10003 44 80 e4 30 80 e4", 1, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e10003 44 80 e4 30 80 e4", 1, 2, 2))), Arguments.of( "underflow 1", - "Section return (RETF) calculated height 0x0 does not match configured height 0x1", + "RETF in section 1 calculated height 0 does not match configured return stack 1, min height 0, and max height 0", 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 0, 1, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 0, 1, 0))), Arguments.of( "underflow 2", - "Section return (RETF) calculated height 0x1 does not match configured height 0x2", + "RETF in section 1 calculated height 1 does not match configured return stack 2, min height 1, and max height 1", 1, - List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 2, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("44 e4", 0, 2, 1))), Arguments.of( "underflow 3", - "Section return (RETF) calculated height 0x1 does not match configured height 0x2", + "RETF in section 1 calculated height 1 does not match configured return stack 2, min height 1, and max height 1", 1, - List.of(List.of("00", 0, 0, 0), List.of("e10003 44 80 e4 30 e4", 1, 2, 2)))); + List.of(List.of("00", 0, 0x80, 0), List.of("e10003 44 80 e4 30 e4", 1, 2, 2)))); } static Stream stackUnreachable() { return Stream.of( Arguments.of( "Max stack not changed by unreachable code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Max stack not changed by unreachable code RETf", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 e4 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 e4 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Max stack not changed by unreachable code RJUMP", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 5", 0, - List.of(List.of("30 50 e00006 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 e00006 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 00 50 00", 0, 0, 1))), + List.of(List.of("30 50 00 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code RETF", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 3", 0, - List.of(List.of("30 50 e4 50 00", 0, 0, 1))), + List.of(List.of("30 50 e4 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code RJUMP", - "Dead code detected in section 0", + "Code that was not forward referenced in section 0x0 pc 5", 0, - List.of(List.of("30 50 e00001 50 00", 0, 0, 1)))); + List.of(List.of("30 50 e00001 50 00", 0, 0x80, 1)))); } static Stream stackHeight() { return Stream.of( Arguments.of( "Stack height mismatch backwards", - "Jump into code stack height (0) does not match previous value (1)", + "Stack minimum violation on backwards jump from 1 to 0, 1 != 1", 0, - List.of(List.of("30 e0fffc00", 0, 0, 1))), + List.of(List.of("30 e0fffc00", 0, 0x80, 1))), Arguments.of( "Stack height mismatch forwards", - "Jump into code stack height (3) does not match previous value (0)", + "Calculated max stack height (5) does not match reported stack height (2)", 0, - List.of(List.of("30e10003303030303000", 0, 0, 2)))); + List.of(List.of("30e10003303030303000", 0, 0x80, 2)))); } static Stream invalidInstructions() { return IntStream.range(0, 256) - .filter(opcode -> CodeV1Validation.OPCODE_ATTRIBUTES[opcode] == CodeV1Validation.INVALID) + .filter(opcode -> !OpcodeInfo.V1_OPCODES[opcode].valid()) .mapToObj( opcode -> Arguments.of( String.format("Invalid opcode %02x", opcode), String.format("Invalid Instruction 0x%02x", opcode), 0, - List.of(List.of(String.format("0x%02x", opcode), 0, 0, 0)))); + List.of(List.of(String.format("0x%02x", opcode), 0, 0x80, 0)))); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java index d25d1bcdf81..94f1d9598e0 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java @@ -72,109 +72,109 @@ public static Collection containersWithFormatErrors() { "Invalid Code section size for section 1", 1 }, - {"EF0001 010004 0200010001 03", "No data section size", "Invalid Data section size", 1}, + {"EF0001 010004 0200010001 04", "No data section size", "Invalid Data section size", 1}, { - "EF0001 010004 0200010001 0300", + "EF0001 010004 0200010001 0400", "Short data section size", "Invalid Data section size", 1 }, - {"EF0001 010004 0200010001 030000", "No Terminator", "Improper section headers", 1}, - {"EF0001 010004 0200010002 030000 00", "No type section", "Incomplete type section", 1}, + {"EF0001 010004 0200010001 040000", "No Terminator", "Improper section headers", 1}, + {"EF0001 010004 0200010002 040000 00", "No type section", "Incomplete type section", 1}, { - "EF0001 010004 0200010002 030001 030001 00 DA DA", + "EF0001 010004 0200010002 040001 040001 00 DA DA", "Duplicate data sections", - "Expected kind 0 but read kind 3", + "Expected kind 0 but read kind 4", 1 }, { - "EF0001 010004 0200010002 030000 00 00", + "EF0001 010004 0200010002 040000 00 00", "Incomplete type section", "Incomplete type section", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000FE", + "EF0001 010008 02000200020002 040000 00 00000000FE", "Incomplete type section", "Incomplete type section", 1 }, { - "EF0001 010008 0200010001 030000 00 00000000 FE ", + "EF0001 010008 0200010001 040000 00 00000000 FE ", "Incorrect type section size", "Type section length incompatible with code section count - 0x1 * 4 != 0x8", 1 }, { - "EF0001 010008 02000200010001 030000 00 0100000000000000 FE FE", + "EF0001 010008 02000200010001 040000 00 0100000000000000 FE FE", "Incorrect section zero type input", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010008 02000200010001 030000 00 0001000000000000 FE FE", + "EF0001 010008 02000200010001 040000 00 0001000000000000 FE FE", "Incorrect section zero type output", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010004 0200010002 030000 00 00000000 ", + "EF0001 010004 0200010002 040000 00 00000000 ", "Incomplete code section", "Incomplete code section 0", 1 }, { - "EF0001 010004 0200010002 030000 00 00000000 FE", + "EF0001 010004 0200010002 040000 00 00000000 FE", "Incomplete code section", "Incomplete code section 0", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000 00000000 FEFE ", + "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE ", "No code section multiple", "Incomplete code section 1", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000 00000000 FEFE FE", + "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE FE", "Incomplete code section multiple", "Incomplete code section 1", 1 }, { - "EF0001 010004 0200010001 030003 00 00000000 FE DEADBEEF", + "EF0001 010004 0200010001 040003 00 00800000 FE DEADBEEF", "Incomplete data section", "Dangling data after end of all sections", 1 }, { - "EF0001 010004 0200010001 030003 00 00000000 FE BEEF", + "EF0001 010004 0200010001 040003 00 00800000 FE BEEF", "Incomplete data section", "Incomplete data section", 1 }, { - "EF0001 0200010001 030001 00 FE DA", + "EF0001 0200010001 040001 00 FE DA", "type section missing", "Expected kind 1 but read kind 2", 1 }, { - "EF0001 010004 030001 00 00000000 DA", + "EF0001 010004 040001 00 00000000 DA", "code section missing", - "Expected kind 2 but read kind 3", + "Expected kind 2 but read kind 4", 1 }, { "EF0001 010004 0200010001 00 00000000 FE", "data section missing", - "Expected kind 3 but read kind 0", + "Expected kind 4 but read kind 0", 1 }, { - "EF0001 030001 00 DA", + "EF0001 040001 00 DA", "type and code section missing", - "Expected kind 1 but read kind 3", + "Expected kind 1 but read kind 4", 1 }, { @@ -193,7 +193,7 @@ public static Collection containersWithFormatErrors() { { "EF0001 011004 020401" + " 0001".repeat(1025) - + " 030000 00" + + " 040000 00" + " 00000000".repeat(1025) + " FE".repeat(1025), "no data section, 1025 code sections", @@ -216,31 +216,13 @@ public static Collection correctContainers() { return Arrays.asList( new Object[][] { { - "EF0001 010004 0200010001 030000 00 00000000 FE", - "no data section, one code section", + "0xef0001 010004 0200010010 040000 00 00800002 e00001 f3 6001 6000 53 6001 6000 e0fff3 ", + "1", null, 1 }, { - "EF0001 010004 0200010001 030001 00 00000000 FE DA", - "with data section, one code section", - null, - 1 - }, - { - "EF0001 010008 02000200010001 030000 00 00000000 00000000 FE FE", - "no data section, multiple code section", - null, - 1 - }, - { - "EF0001 010008 02000200010001 030001 00 00000000 00000000 FE FE DA", - "with data section, multiple code section", - null, - 1 - }, - { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 @@ -248,8 +230,8 @@ public static Collection correctContainers() { { "EF0001 011000 020400" + " 0001".repeat(1024) - + " 030000 00" - + " 00000000".repeat(1024) + + " 040000 00" + + " 00800000".repeat(1024) + " FE".repeat(1024), "no data section, 1024 code sections", null, @@ -262,37 +244,37 @@ public static Collection typeSectionTests() { return Arrays.asList( new Object[][] { { - "EF0001 010008 02000200020002 030000 00 0100000000000000", + "EF0001 010008 02000200020002 040000 00 0100000000000000", "Incorrect section zero type input", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010008 02000200020002 030000 00 0001000000000000", + "EF0001 010008 02000200020002 040000 00 0001000000000000", "Incorrect section zero type output", "Code section does not have zero inputs and outputs", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 F0000000 00010000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 F0000000 00010000 02030000 FE 5000 3000 8000", "inputs too large", "Type data input stack too large - 0xf0", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00F00000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000000 00F00000 02030000 FE 5000 3000 8000", "outputs too large", "Type data output stack too large - 0xf0", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", "stack too large", "Type data max stack too large - 0x400", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 @@ -300,20 +282,84 @@ public static Collection typeSectionTests() { }); } + public static Collection subContainers() { + return Arrays.asList( + new Object[][] { + { + "EF0001 010004 0200010001 0300010014 040000 00 00800000 FE EF000101000402000100010400000000800000FE", + "no data section, one code section, one subcontainer", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)", + "evmone - one container", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040003 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00) 000000", + "evmone - one container two bytes", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030003001400140014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)", + "evmone - three subContainers", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030003001400140014 040003 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00) ddeeff", + "evmone - three subContainers and data", + null, + 1 + }, + { + "EF00 01 01000C 020003000100010001 030003001400140014 040003 00 008000000000000000000000 00 00 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00) ddeeff", + "evmone - three subContainers three code and data", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010100 040000 00 00800000 00 (EF0001 010004 02000100ED 040000 00 00800000 " + + "5d".repeat(237) + + ")", + "evmone - 256 byte container", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030100" + + "0014".repeat(256) + + "040000 00 00800000 00 " + + "(EF0001 010004 0200010001 040000 00 00800000 00)".repeat(256), + "evmone - 256 subContainers", + null, + 1 + }, + }); + } + @ParameterizedTest(name = "{1}") - @MethodSource({"correctContainers", "containersWithFormatErrors", "typeSectionTests"}) + @MethodSource({ + // "correctContainers", + // "containersWithFormatErrors", + // "typeSectionTests", + "subContainers" + }) void test( final String containerString, final String description, final String failureReason, final int expectedVersion) { - final Bytes container = Bytes.fromHexString(containerString.replace(" ", "")); + final Bytes container = Bytes.fromHexString(containerString.replaceAll("[^a-fxA-F0-9]", "")); final EOFLayout layout = EOFLayout.parseEOF(container); - assertThat(layout.getVersion()).isEqualTo(expectedVersion); - assertThat(layout.getInvalidReason()).isEqualTo(failureReason); - assertThat(layout.getContainer()).isEqualTo(container); - if (layout.getInvalidReason() != null) { + assertThat(layout.version()).isEqualTo(expectedVersion); + assertThat(layout.invalidReason()).isEqualTo(failureReason); + assertThat(layout.container()).isEqualTo(container); + if (layout.invalidReason() != null) { assertThat(layout.isValid()).isFalse(); assertThat(layout.getCodeSectionCount()).isZero(); } else { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java index 97a2a4b8367..871d99f768d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java @@ -128,7 +128,7 @@ void defaultChainIdAPIs() { assertThat(cancunEVM.getChainId()).contains(defaultChainId); EVMExecutor pragueEVM = - EVMExecutor.prague(defaultChainId.toBigInteger(), EvmConfiguration.DEFAULT); + EVMExecutor.pragueEOF(defaultChainId.toBigInteger(), EvmConfiguration.DEFAULT); assertThat(pragueEVM.getChainId()).contains(defaultChainId); EVMExecutor futureEipsVM = EVMExecutor.futureEips(EvmConfiguration.DEFAULT); @@ -141,7 +141,7 @@ void executeCode() { EVMExecutor.evm(EvmSpecVersion.SHANGHAI) .worldUpdater(createSimpleWorld().updater()) .execute( - CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 1, false), + CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 1), Bytes.EMPTY, Wei.ZERO, Address.ZERO); @@ -180,7 +180,7 @@ void giantExecuteStack() { .blobGasPrice(Wei.ONE) .callData(Bytes.fromHexString("0x12345678")) .ethValue(Wei.fromEth(1)) - .code(CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 0, false)) + .code(CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 0)) .blockValues(new SimpleBlockValues()) .difficulty(Bytes.ofUnsignedLong(1L)) .mixHash(Bytes32.ZERO) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java new file mode 100644 index 00000000000..46fe1dcba8b --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.gascalculator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; + +import org.junit.jupiter.api.Test; + +class PragueEOFGasCalculatorTest { + + @Test + void testPrecompileSize() { + PragueEOFGasCalculator subject = new PragueEOFGasCalculator(); + assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse(); + assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue(); + } + + @Test + void testNewConstants() { + CancunGasCalculator cancunGas = new CancunGasCalculator(); + PragueEOFGasCalculator praugeGasCalculator = new PragueEOFGasCalculator(); + + assertThat(praugeGasCalculator.getMinCalleeGas()).isGreaterThan(cancunGas.getMinCalleeGas()); + assertThat(praugeGasCalculator.getMinRetainedGas()) + .isGreaterThan(cancunGas.getMinRetainedGas()); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java b/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java index c18bef8889b..94f616898a5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java @@ -32,7 +32,7 @@ void testScale() { final Bytes contractBytes = Bytes.fromHexString("0xDEAD" + op + "BEEF" + op + "B0B0" + op + "C0DE" + op + "FACE"); final CodeScale scale = new CodeScale(); - final Code contractCode = CodeFactory.createCode(contractBytes, 0, false); + final Code contractCode = CodeFactory.createCode(contractBytes, 0); final int weight = scale.weigh(contractCode.getCodeHash(), contractCode); assertThat(weight) .isEqualTo(contractCode.getCodeHash().size() + (contractBytes.size() * 9 + 7) / 8); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java index 2ba4ea918f2..79f07e11af5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java @@ -78,7 +78,7 @@ class AbstractCreateOperationTest { public static final Bytes INVALID_EOF = Bytes.fromHexString( "0x" - + "73EF99010100040200010001030000000000000000" // PUSH20 contract + + "73EF00990100040200010001030000000000000000" // PUSH20 contract + "6000" // PUSH1 0x00 + "52" // MSTORE + "6014" // PUSH1 20 @@ -104,7 +104,7 @@ public static class FakeCreateOperation extends AbstractCreateOperation { * @param maxInitcodeSize Maximum init code size */ public FakeCreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize); + super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize, 0); } @Override @@ -166,7 +166,7 @@ private void executeOperation(final Bytes contract, final EVM evm) { .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(CodeFactory.createCode(SIMPLE_CREATE, 0)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java index cc896f010d4..fe662893e1e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.ReturnStack; @@ -36,9 +36,7 @@ class CallFOperationTest { @Test void callFHappyPath() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); + final Code mockCode = mockCode("00" + "b0" + "0001" + "00"); final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); when(mockCode.getCodeSection(1)).thenReturn(codeSection); @@ -59,98 +57,7 @@ void callFHappyPath() { assertThat(callfResult.getPcIncrement()).isEqualTo(1); assertThat(messageFrame.getSection()).isEqualTo(1); assertThat(messageFrame.getPC()).isEqualTo(-1); - assertThat(messageFrame.returnStackSize()).isEqualTo(2); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 3, 1)); - } - - @Test - void callFMissingCodeSection() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "03ff" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.CODE_SECTION_MISSING); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void callFTooMuchStack() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 1023, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void callFTooFewStack() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 5, 2, 5, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()) - .isEqualTo(ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 3)); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java index 6298aa245a7..eb04ccb7419 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,6 +25,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; @@ -81,7 +83,7 @@ public class Create2OperationTest { + "F3" // RETURN ); public static final Bytes SIMPLE_EOF = - Bytes.fromHexString("0xEF00010100040200010001030000000000000000"); + Bytes.fromHexString("0xEF00010100040200010001040000000080000000"); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; private static final int SHANGHAI_CREATE_GAS = 41240 + (0xc000 / 32) * 6; @@ -152,7 +154,7 @@ public void setUp(final String sender, final String salt, final String code) { .sender(Address.fromHexString(sender)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(codeBytes, 0, true)) + .code(CodeFactory.createCode(codeBytes, 0)) .completer(__ -> {}) .address(Address.fromHexString(sender)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -174,7 +176,7 @@ public void setUp(final String sender, final String salt, final String code) { when(worldUpdater.getAccount(any())).thenReturn(account); when(worldUpdater.updater()).thenReturn(worldUpdater); when(evm.getCode(any(), any())) - .thenAnswer(invocation -> CodeFactory.createCode(invocation.getArgument(1), 0, true)); + .thenAnswer(invocation -> CodeFactory.createCode(invocation.getArgument(1), 0)); } @ParameterizedTest @@ -188,7 +190,7 @@ void shouldCalculateAddress( setUp(sender, salt, code); final Address targetContractAddress = operation.targetContractAddress( - messageFrame, CodeFactory.createCode(Bytes.fromHexString(code), 0, true)); + messageFrame, CodeFactory.createCode(Bytes.fromHexString(code), 0)); assertThat(targetContractAddress).isEqualTo(Address.fromHexString(expectedAddress)); } @@ -266,7 +268,7 @@ private MessageFrame testMemoryFrame(final UInt256 memoryOffset, final UInt256 m .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(CodeFactory.createCode(SIMPLE_CREATE, 0)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -292,54 +294,30 @@ private MessageFrame testMemoryFrame(final UInt256 memoryOffset, final UInt256 m } @Test - void eofV1CannotCreateLegacy() { + void eofV1CannotCall() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_EOF, 1, true)) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(memoryLength) - .pushStackItem(memoryOffset) - .pushStackItem(Bytes.EMPTY) - .worldUpdater(worldUpdater) - .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - - when(account.getBalance()).thenReturn(Wei.ZERO); - when(worldUpdater.getAccount(any())).thenReturn(account); - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } + Code eofCode = CodeFactory.createCode(SIMPLE_EOF, 1); + assertThat(eofCode.isValid()).isTrue(); - @Test - void legacyCanCreateEOFv1() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size()); final MessageFrame messageFrame = new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_CREATE, 1, true)) + .code(eofCode) .pushStackItem(Bytes.EMPTY) .pushStackItem(memoryLength) .pushStackItem(memoryOffset) .pushStackItem(Bytes.EMPTY) .worldUpdater(worldUpdater) .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF); + messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); - when(worldUpdater.get(any())).thenReturn(account); - when(worldUpdater.getSenderAccount(any())).thenReturn(account); - when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0)).isNotEqualTo(UInt256.ZERO); + assertThat(result.getHaltReason()).isEqualTo(INVALID_OPERATION); + assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java index f1de36cc563..d95875e492b 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -74,7 +75,7 @@ class CreateOperationTest { + "F3" // RETURN ); public static final Bytes SIMPLE_EOF = - Bytes.fromHexString("0xEF00010100040200010001030000000000000000"); + Bytes.fromHexString("0xEF00010100040200010001040000000080000000"); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; private static final int SHANGHAI_CREATE_GAS = 41240; @@ -223,12 +224,12 @@ void shanghaiMaxInitCodeSizePlus1Create() { } @Test - void eofV1CannotCreateLegacy() { + void eofV1CannotCall() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); final MessageFrame messageFrame = new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_EOF, 1, true)) + .code(CodeFactory.createCode(SIMPLE_EOF, 1)) .pushStackItem(memoryLength) .pushStackItem(memoryOffset) .pushStackItem(Bytes.EMPTY) @@ -236,40 +237,14 @@ void eofV1CannotCreateLegacy() { .build(); messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getBalance()).thenReturn(Wei.ZERO); - when(worldUpdater.getAccount(any())).thenReturn(account); - - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void legacyCanCreateEOFv1() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size()); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_CREATE, 1, true)) - .pushStackItem(memoryLength) - .pushStackItem(memoryOffset) - .pushStackItem(Bytes.EMPTY) - .worldUpdater(worldUpdater) - .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF); - - when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); when(worldUpdater.get(any())).thenReturn(account); - when(worldUpdater.getSenderAccount(any())).thenReturn(account); - when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getState()).isEqualTo(MessageFrame.State.CODE_SUSPENDED); + assertThat(result.getHaltReason()).isEqualTo(INVALID_OPERATION); + assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); } @Nonnull @@ -286,7 +261,7 @@ private MessageFrame testMemoryFrame( .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(CodeFactory.createCode(SIMPLE_CREATE, 0)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java new file mode 100644 index 00000000000..2e41b49665b --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/EofCreateOperationTest.java @@ -0,0 +1,160 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.hyperledger.besu.evm.EOFTestConstants.EOF_CREATE_CONTRACT; +import static org.hyperledger.besu.evm.EOFTestConstants.INNER_CONTRACT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts; +import org.hyperledger.besu.evm.processor.ContractCreationProcessor; +import org.hyperledger.besu.evm.processor.MessageCallProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class EofCreateOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final MutableAccount newAccount = mock(MutableAccount.class); + + static final Bytes CALL_DATA = + Bytes.fromHexString( + "cafebaba600dbaadc0de57aff60061e5cafebaba600dbaadc0de57aff60061e5"); // 32 bytes + + public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; + + // private static final int SHANGHAI_CREATE_GAS = 41240; + + @Test + void innerContractIsCorrect() { + Code code = CodeFactory.createCode(INNER_CONTRACT, 1); + assertThat(code.isValid()).isTrue(); + + final MessageFrame messageFrame = testMemoryFrame(code, CALL_DATA); + + when(account.getNonce()).thenReturn(55L); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); + assertThat(createFrame).isNotNull(); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + ccp.process(createFrame, OperationTracer.NO_TRACING); + + final Log log = createFrame.getLogs().get(0); + final Bytes calculatedTopic = log.getTopics().get(0); + assertThat(calculatedTopic).isEqualTo(CALL_DATA); + } + + @Test + void eofCreatePassesInCallData() { + Bytes outerContract = EOF_CREATE_CONTRACT; + + Code code = CodeFactory.createCode(outerContract, 1); + if (!code.isValid()) { + System.out.println(outerContract); + fail(((CodeInvalid) code).getInvalidReason()); + } + + final MessageFrame messageFrame = testMemoryFrame(code, CALL_DATA); + + when(account.getNonce()).thenReturn(55L); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + var precompiles = MainnetPrecompiledContracts.prague(evm.getGasCalculator()); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); + assertThat(createFrame).isNotNull(); + final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompiles); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + while (!createFrame.getMessageFrameStack().isEmpty()) { + var frame = createFrame.getMessageFrameStack().peek(); + assert frame != null; + (switch (frame.getType()) { + case CONTRACT_CREATION -> ccp; + case MESSAGE_CALL -> mcp; + }) + .process(frame, OperationTracer.NO_TRACING); + } + + final Log log = createFrame.getLogs().get(0); + final String calculatedTopic = log.getTopics().get(0).slice(0, 2).toHexString(); + assertThat(calculatedTopic).isEqualTo("0xc0de"); + + assertThat(createFrame.getCreates()) + .containsExactly(Address.fromHexString("0x8c308e96997a8052e3aaab5af624cb827218687a")); + } + + private MessageFrame testMemoryFrame(final Code code, final Bytes initData) { + return MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .contract(Address.ZERO) + .inputData(initData) + .sender(Address.fromHexString(SENDER)) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(code) + .completer(__ -> {}) + .address(Address.fromHexString(SENDER)) + .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) + .blockValues(mock(BlockValues.class)) + .gasPrice(Wei.ZERO) + .miningBeneficiary(Address.ZERO) + .originator(Address.ZERO) + .initialGas(100000L) + .worldUpdater(worldUpdater) + .build(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java new file mode 100644 index 00000000000..3a2ddde7e47 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCallOperationTest.java @@ -0,0 +1,274 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.operation.AbstractExtCallOperation; +import org.hyperledger.besu.evm.operation.ExtCallOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final EVM evm = mock(EVM.class); + public static final Code SIMPLE_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1); + public static final Code INVALID_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + static Iterable valueData() { + return List.of( + Arguments.of( + "enough value", + 40000, + 35000, + 25900, + null, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + false), + Arguments.of( + "static context", + 40000, + 9000, + 40000, + ExceptionalHaltReason.ILLEGAL_STATE_CHANGE, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + true), + Arguments.of( + "not enough value", + 40000, + 9100, + 40000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + Wei.of(1000), + Wei.of(200), + false), + Arguments.of( + "too little gas", + 5000, + 9100, + 5000, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + false)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("valueData") + void callWithValueTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final Wei valueSent, + final Wei valueWeiHave, + final boolean isStatic) { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(valueSent) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .isStatic(isStatic) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(valueWeiHave); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java new file mode 100644 index 00000000000..7a46654039d --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtDelegateCallOperationTest.java @@ -0,0 +1,258 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.operation.AbstractExtCallOperation; +import org.hyperledger.besu.evm.operation.ExtDelegateCallOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtDelegateCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + // private final MutableAccount targetAccount = mock(MutableAccount.class); + private final EVM evm = mock(EVM.class); + public static final Code SIMPLE_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1); + public static final Code SIMPLE_LEGACY = CodeFactory.createCode(Bytes.fromHexString("0x00"), 1); + public static final Code EMPTY_CODE = CodeFactory.createCode(Bytes.fromHexString(""), 1); + public static final Code INVALID_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .code(SIMPLE_EOF) + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + static Iterable delegateData() { + return List.of( + Arguments.of("EOF", 40000, 35000, 34900L, null, CONTRACT_ADDRESS), + Arguments.of( + "Legacy", 40000, 100, 40000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM), + Arguments.of( + "Empty", + 40000, + 100, + 40000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + CONTRACT_ADDRESS), + Arguments.of( + "EOA", 5000, 100, 5000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("delegateData") + void callTypes( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem) { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .code(SIMPLE_EOF) + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account); + when(worldUpdater.getAccount(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account); + + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(CONTRACT_ADDRESS)).thenReturn("Empty".equals(name) ? null : account); + when(worldUpdater.getAccount(CONTRACT_ADDRESS)) + .thenReturn("Empty".equals(name) ? null : account); + when(evm.getCode(any(), any())) + .thenReturn( + switch (name) { + case "EOF" -> SIMPLE_EOF; + case "Legacy" -> SIMPLE_LEGACY; + default -> EMPTY_CODE; + }); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java new file mode 100644 index 00000000000..25be1d1dfee --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtStaticCallOperationTest.java @@ -0,0 +1,185 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.operation.AbstractExtCallOperation; +import org.hyperledger.besu.evm.operation.ExtStaticCallOperation; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtStaticCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final EVM evm = mock(EVM.class); + public static final Code SIMPLE_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000000"), 1); + public static final Code INVALID_EOF = + CodeFactory.createCode(Bytes.fromHexString("0xEF00010100040200010001040000000080000023"), 1); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(validCode ? SIMPLE_EOF : INVALID_EOF); + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + when(evm.getCode(any(), any())).thenReturn(SIMPLE_EOF); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, evm); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java index 7efbb8e9281..52a7211050c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java @@ -15,15 +15,14 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.ReturnStack; import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; @@ -36,9 +35,7 @@ class JumpFOperationTest { @Test void jumpFHappyPath() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b2" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); + final Code mockCode = mockCode("00" + "b2" + "0001" + "00"); final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); when(mockCode.getCodeSection(1)).thenReturn(codeSection); @@ -60,70 +57,7 @@ void jumpFHappyPath() { assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); assertThat(messageFrame.getSection()).isEqualTo(1); assertThat(messageFrame.getPC()).isEqualTo(-1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void jumpFMissingCodeSection() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b2" + "03ff" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - JumpFOperation jumpF = new JumpFOperation(gasCalculator); - Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); - - assertThat(jumpFResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.CODE_SECTION_MISSING); - assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void jumpFTooMuchStack() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b2" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection1 = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection1); - final CodeSection codeSection2 = new CodeSection(0, 2, 2, 3, 0); - when(mockCode.getCodeSection(2)).thenReturn(codeSection2); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(2) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - JumpFOperation jumpF = new JumpFOperation(gasCalculator); - Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); - - assertThat(jumpFResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.JUMPF_STACK_MISMATCH); - assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(2); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); + assertThat(messageFrame.returnStackSize()).isZero(); + assertThat(messageFrame.peekReturnStack()).isNull(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java index 37b5998309f..e04b0d16f61 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java @@ -74,7 +74,7 @@ void shouldJumpWhenLocationIsJumpDest() { final MessageFrame frame = createMessageFrameBuilder(10_000L) .pushStackItem(UInt256.fromHexString("0x03")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(CodeFactory.createCode(jumpBytes, 0)) .build(); frame.setPC(CURRENT_PC); @@ -89,7 +89,7 @@ void shouldJumpWhenLocationIsJumpDestAndAtEndOfCode() { final MessageFrame frame = createMessageFrameBuilder(10_000L) .pushStackItem(UInt256.fromHexString("0x03")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(CodeFactory.createCode(jumpBytes, 0)) .build(); frame.setPC(CURRENT_PC); @@ -104,7 +104,7 @@ void shouldHaltWithInvalidJumDestinationWhenLocationIsOutsideOfCodeRange() { final MessageFrame frameDestinationGreaterThanCodeSize = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0xFFFFFFFF")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(CodeFactory.createCode(jumpBytes, 0)) .build(); frameDestinationGreaterThanCodeSize.setPC(CURRENT_PC); @@ -114,7 +114,7 @@ void shouldHaltWithInvalidJumDestinationWhenLocationIsOutsideOfCodeRange() { final MessageFrame frameDestinationEqualsToCodeSize = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0x04")) - .code(CodeFactory.createCode(badJump, 0, false)) + .code(CodeFactory.createCode(badJump, 0)) .build(); frameDestinationEqualsToCodeSize.setPC(CURRENT_PC); @@ -132,7 +132,7 @@ void longContractsValidate() { final MessageFrame longContract = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0x12c")) - .code(CodeFactory.createCode(longCode, 0, false)) + .code(CodeFactory.createCode(longCode, 0)) .build(); longContract.setPC(255); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java index f55b7629ba3..31042257759 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -41,13 +42,11 @@ class RelativeJumpOperationTest { void rjumpOperation(final int jumpLength) { final GasCalculator gasCalculator = mock(GasCalculator.class); final MessageFrame messageFrame = mock(MessageFrame.class, Mockito.RETURNS_DEEP_STUBS); - final Code mockCode = mock(Code.class); final String twosComplementJump = String.format("%08x", jumpLength).substring(4); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(3) + "5c" + twosComplementJump); + final Code mockCode = mockCode("00".repeat(3) + "5c" + twosComplementJump); when(messageFrame.getCode()).thenReturn(mockCode); - when(mockCode.getBytes()).thenReturn(code); when(messageFrame.getRemainingGas()).thenReturn(3L); when(messageFrame.getPC()).thenReturn(rjumpOperationIndex); @@ -55,15 +54,14 @@ void rjumpOperation(final int jumpLength) { Operation.OperationResult rjumpResult = rjump.execute(messageFrame, null); assertThat(rjumpResult.getPcIncrement()) - .isEqualTo(code.size() - rjumpOperationIndex + jumpLength); + .isEqualTo(mockCode.getBytes().size() - rjumpOperationIndex + jumpLength); } @Test void rjumpiOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5d0004"); + final Code mockCode = mockCode("00".repeat(rjumpOperationIndex) + "5d0004"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -72,7 +70,6 @@ void rjumpiOperation() { .initialGas(5L) .pushStackItem(Bytes.EMPTY) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); @@ -83,9 +80,8 @@ void rjumpiOperation() { @Test void rjumpiHitOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5dfffc00"); + final Code mockCode = mockCode("00".repeat(rjumpOperationIndex) + "5dfffc00"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -94,7 +90,6 @@ void rjumpiHitOperation() { .initialGas(5L) .pushStackItem(Words.intBytes(1)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); @@ -105,14 +100,13 @@ void rjumpiHitOperation() { @Test void rjumpvOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 1; final int jumpLength = 4; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x%04x", jumpVectorSize, jumpLength)); + + String.format("e2%02x%04x", jumpVectorSize - 1, jumpLength)); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -121,7 +115,6 @@ void rjumpvOperation() { .initialGas(5L) .pushStackItem(Bytes.of(jumpVectorSize)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); @@ -156,17 +149,15 @@ void rjumpvOperation() { }) void rjumpvOverflowOperation(final String stackValue) { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 255; final int jumpLength = 400; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x", jumpVectorSize) + + String.format("e2%02x", jumpVectorSize - 1) + String.format("%04x", jumpLength).repeat(jumpVectorSize)); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -185,17 +176,15 @@ void rjumpvOverflowOperation(final String stackValue) { @ValueSource(strings = {"0x7f", "0xf5", "0x5f", "0xfe"}) void rjumpvIndexOperation(final String stackValue) { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 255; final int jumpLength = 400; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x", jumpVectorSize) + + String.format("e2%02x", jumpVectorSize - 1) + String.format("%04x", jumpLength).repeat(jumpVectorSize)); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -213,11 +202,10 @@ void rjumpvIndexOperation(final String stackValue) { @Test void rjumpvHitOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 2; - final Bytes code = - Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5e" + "02" + "1234" + "5678"); + final Code mockCode = + mockCode("00".repeat(rjumpOperationIndex) + "e2" + "01" + "1234" + "5678"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -226,7 +214,6 @@ void rjumpvHitOperation() { .initialGas(5L) .pushStackItem(Bytes.of(jumpVectorSize - 1)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java index 193900ede7d..672628140cf 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.evm.operations; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.ReturnStack; @@ -36,9 +36,7 @@ class RetFOperationTest { @Test void retFHappyPath() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); - when(mockCode.getBytes()).thenReturn(code); + final Code mockCode = mockCode("00" + "b1" + "00"); final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); when(mockCode.getCodeSection(1)).thenReturn(codeSection); @@ -53,7 +51,7 @@ void retFHappyPath() { .pushStackItem(Bytes.EMPTY) .pushStackItem(Bytes.EMPTY) .build(); - messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3, 1)); + messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3)); RetFOperation retF = new RetFOperation(gasCalculator); Operation.OperationResult retFResult = retF.execute(messageFrame, null); @@ -62,68 +60,6 @@ void retFHappyPath() { assertThat(retFResult.getPcIncrement()).isEqualTo(1); assertThat(messageFrame.getSection()).isEqualTo(2); assertThat(messageFrame.getPC()).isEqualTo(3); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - } - - @Test - void retFFinalReturn() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - RetFOperation retF = new RetFOperation(gasCalculator); - Operation.OperationResult retFResult = retF.execute(messageFrame, null); - - assertThat(retFResult.getHaltReason()).isNull(); - assertThat(retFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getState()).isEqualTo(MessageFrame.State.CODE_SUCCESS); - assertThat(messageFrame.getOutputData()).isEqualTo(Bytes.EMPTY); - } - - @Test - void retFIncorrectOutput() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - RetFOperation retF = new RetFOperation(gasCalculator); - Operation.OperationResult retFResult = retF.execute(messageFrame, null); - - assertThat(retFResult.getHaltReason()) - .isEqualTo(ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS); - assertThat(retFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(1); - assertThat(messageFrame.getPC()).isEqualTo(1); assertThat(messageFrame.returnStackSize()).isZero(); - assertThat(messageFrame.peekReturnStack()).isNull(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java index 66598ab5c7d..a87c254b0ed 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java @@ -79,7 +79,7 @@ void checkContractDeletionCommon( .sender(beneficiaryAddress) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0, true)) + .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0)) .completer(__ -> {}) .address(originatorAddress) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java index 975cd0e5fa1..32f5ecc3238 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java @@ -15,14 +15,14 @@ package org.hyperledger.besu.evm.processor; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.EOFTestConstants.EOF_CREATE_CONTRACT; +import static org.hyperledger.besu.evm.EOFTestConstants.INNER_CONTRACT; import static org.hyperledger.besu.evm.frame.MessageFrame.State.COMPLETED_SUCCESS; import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.code.CodeFactory; -import org.hyperledger.besu.evm.contractvalidation.CachedInvalidCodeRule; import org.hyperledger.besu.evm.contractvalidation.EOFValidationCodeRule; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; @@ -35,6 +35,7 @@ import java.util.Collections; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -115,7 +116,7 @@ void shouldThrowAnExceptionWhenCodeContractFormatInvalidPostEOF() { gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("EF00010101010101"); @@ -131,13 +132,13 @@ void shouldThrowAnExceptionWhenCodeContractFormatInvalidPostEOF() { } @Test - void eofValidationShouldAllowLegacyCode() { + void eofValidationShouldAllowLegacyDeployFromLegacyInit() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("0101010101010101"); @@ -157,13 +158,12 @@ void eofValidationShouldAllowEOFCode() { gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); - final Bytes contractCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000002020100020100000260016002e30001e30002e401e460005360106000f3"); - final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + final Bytes contractCode = INNER_CONTRACT; + final MessageFrame messageFrame = + new TestMessageFrameBuilder().code(CodeFactory.createCode(EOF_CREATE_CONTRACT, 1)).build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -173,21 +173,17 @@ void eofValidationShouldAllowEOFCode() { } @Test - void eofValidationShouldPreventLegacyCodeDeployment() { + void prefixValidationShouldPreventEOFCode() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(PrefixCodeRule.of()), 1, Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("6030602001"); - final Bytes initCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000002020100020100000260016002e30001e30002e401e460005360106000f3"); - final MessageFrame messageFrame = - new TestMessageFrameBuilder().code(CodeFactory.createCode(initCode, 1, true)).build(); + final Bytes contractCode = INNER_CONTRACT; + final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -197,19 +193,19 @@ void eofValidationShouldPreventLegacyCodeDeployment() { } @Test - void eofValidationPreventsInvalidEOFCode() { + void eofValidationShouldPreventLegacyDeployFromEOFInit() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); - final Bytes contractCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000000020100020100000260016002b00001b00002b101b160005360106000f3"); - final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + final Bytes contractCode = Bytes.fromHexString("6030602001"); + final Bytes initCode = EOF_CREATE_CONTRACT; + final MessageFrame messageFrame = + new TestMessageFrameBuilder().code(CodeFactory.createCode(initCode, 1)).build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -219,16 +215,17 @@ void eofValidationPreventsInvalidEOFCode() { } @Test - void shouldThrowAnExceptionWhenCodeContractTooLarge() { + @Disabled("This is what's changing") + void eofValidationPreventsEOFDeployFromLegacyInit() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(MaxCodeSizeRule.of(24 * 1024)), + Collections.singletonList(EOFValidationCodeRule.of(1)), 1, Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024 + 1)); + final Bytes contractCode = EOF_CREATE_CONTRACT; final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); messageFrame.setGasRemaining(100L); @@ -236,31 +233,28 @@ void shouldThrowAnExceptionWhenCodeContractTooLarge() { when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); - assertThat(messageFrame.getExceptionalHaltReason()) - .contains(ExceptionalHaltReason.CODE_TOO_LARGE); } @Test - void shouldThrowAnExceptionWhenDeployingInvalidContract() { - EvmSpecVersion evmSpecVersion = EvmSpecVersion.FUTURE_EIPS; + void shouldThrowAnExceptionWhenCodeContractTooLarge() { processor = new ContractCreationProcessor( gasCalculator, evm, true, - Collections.singletonList(CachedInvalidCodeRule.of(evmSpecVersion)), + Collections.singletonList(MaxCodeSizeRule.of(24 * 1024)), 1, Collections.emptyList()); - final Bytes contractCreateCode = Bytes.fromHexString("0x67ef0001010001006060005260086018f3"); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code( - CodeFactory.createCode(contractCreateCode, evmSpecVersion.getMaxEofVersion(), true)) - .build(); - messageFrame.setOutputData(Bytes.fromHexString("0xef00010100010060")); + final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024 + 1)); + final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + messageFrame.setOutputData(contractCode); + messageFrame.setGasRemaining(100L); + when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); + assertThat(messageFrame.getExceptionalHaltReason()) + .contains(ExceptionalHaltReason.CODE_TOO_LARGE); } @Test diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java new file mode 100644 index 00000000000..778459a6f27 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/OperationsTestUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.evm.testutils; + +import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; +import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; + +import org.apache.tuweni.bytes.Bytes; + +public class OperationsTestUtils { + + public static Code mockCode(final String codeString) { + Code mockCode = mock(Code.class); + final Bytes codeBytes = Bytes.fromHexString(codeString); + when(mockCode.getBytes()).thenReturn(codeBytes); + when(mockCode.getEofVersion()).thenReturn(1); + when(mockCode.readBigEndianI16(anyInt())) + .thenAnswer( + invocationOnMock -> + readBigEndianI16(invocationOnMock.getArgument(0), codeBytes.toArrayUnsafe())); + when(mockCode.readBigEndianU16(anyInt())) + .thenAnswer( + invocationOnMock -> + readBigEndianU16(invocationOnMock.getArgument(0), codeBytes.toArrayUnsafe())); + when(mockCode.readU8(anyInt())) + .thenAnswer( + invocationOnMock -> + codeBytes.toArrayUnsafe()[(int) invocationOnMock.getArgument(0)] & 0xff); + return mockCode; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java index fb4f56dbeac..801fcae8e2d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java @@ -56,6 +56,7 @@ public class TestMessageFrameBuilder { private final List stackItems = new ArrayList<>(); private Optional blockHashLookup = Optional.empty(); private Bytes memory = Bytes.EMPTY; + private boolean isStatic = false; public TestMessageFrameBuilder worldUpdater(final WorldUpdater worldUpdater) { this.worldUpdater = Optional.of(worldUpdater); @@ -102,7 +103,7 @@ public TestMessageFrameBuilder value(final Wei value) { return this; } - TestMessageFrameBuilder inputData(final Bytes inputData) { + public TestMessageFrameBuilder inputData(final Bytes inputData) { this.inputData = inputData; return this; } @@ -142,6 +143,11 @@ public TestMessageFrameBuilder memory(final Bytes memory) { return this; } + public TestMessageFrameBuilder isStatic(final boolean isStatic) { + this.isStatic = isStatic; + return this; + } + public MessageFrame build() { final MessageFrame frame = MessageFrame.builder() @@ -163,6 +169,7 @@ public MessageFrame build() { .miningBeneficiary(Address.ZERO) .blockHashLookup(blockHashLookup.orElse(number -> Hash.hash(Words.longBytes(number)))) .maxStackSize(maxStackSize) + .isStatic(isStatic) .build(); frame.setPC(pc); frame.setSection(section); diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java index 88588d52187..062e14cfc7f 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java @@ -42,6 +42,7 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import org.apache.tuweni.bytes.Bytes; /** * Utility class for generating JUnit test parameters from json files. Each set of test parameters @@ -75,7 +76,7 @@ private Collector(@Nullable final Predicate includes, final Predicate testParameters = new ArrayList<>(256); /** - * Add. + * Add standard reference test. * * @param name the name * @param fullPath the full path of the test @@ -88,6 +89,27 @@ public void add( new Object[] {name, value, runTest && includes(name) && includes(fullPath)}); } + /** + * Add EOF test. + * + * @param name the name + * @param fullPath the full path of the test + * @param fork the fork to be tested + * @param code the code to be tested + * @param value the value + * @param runTest the run test + */ + public void add( + final String name, + final String fullPath, + final String fork, + final Bytes code, + final S value, + final boolean runTest) { + testParameters.add( + new Object[] {name, fork, code, value, runTest && includes(name) && includes(fullPath)}); + } + private boolean includes(final String name) { // If there is no specific includes, everything is included unless it is ignored, otherwise, // only what is in includes is included whether or not it is ignored. From dfe256e6478a91122e40adda928a71e1353668f1 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Thu, 13 Jun 2024 02:10:52 +0200 Subject: [PATCH 06/22] Update Dockerfile - reuse NO_PROXY_CACHE env (#7203) * Update Dockerfile reuse NO_PROXY_CACHE env Signed-off-by: Enrico Del Fante * Update Dockerfile Signed-off-by: Enrico Del Fante --------- Signed-off-by: Enrico Del Fante Co-authored-by: Usman Saleem Co-authored-by: Sally MacFarlane --- docker/Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ee138afca62..e7878ccdd9b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,13 +5,10 @@ ENV NO_PROXY_CACHE="-o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true # Update and install dependencies without using any cache RUN apt-get update $NO_PROXY_CACHE && \ - # $NO_PROXY_CACHE must not be used here or otherwise will trigger a hadolint error - apt-get -o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0 \ - --no-install-recommends -q --assume-yes install openjdk-21-jre-headless=21* libjemalloc-dev=5.* adduser=3* && \ + apt-get $NO_PROXY_CACHE --no-install-recommends -q --assume-yes install openjdk-21-jre-headless=21* libjemalloc-dev=5.* adduser=3* && \ # Clean apt cache apt-get clean && \ - rm -rf /var/cache/apt/archives/* /var/cache/apt/archives/partial/* && \ - rm -rf /var/lib/apt/lists/* && \ + rm -rf /var/cache/apt/archives/* && rm -rf /var/lib/apt/lists/* && \ # Ubuntu 23.10 comes with an "ubuntu" user with uid 1000. We need 1000 for besu. userdel ubuntu 2>/dev/null || true && rm -rf /home/ubuntu && \ # Ensure we use a stable UID for besu, as file permissions are tied to UIDs. From c8d01075e713b23367dc38713c7b27763f847d7c Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 13 Jun 2024 10:47:32 +1000 Subject: [PATCH 07/22] Revert "Update Dockerfile - reuse NO_PROXY_CACHE env (#7203)" (#7219) This reverts commit dfe256e6478a91122e40adda928a71e1353668f1. Signed-off-by: Sally MacFarlane --- docker/Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e7878ccdd9b..ee138afca62 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,10 +5,13 @@ ENV NO_PROXY_CACHE="-o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true # Update and install dependencies without using any cache RUN apt-get update $NO_PROXY_CACHE && \ - apt-get $NO_PROXY_CACHE --no-install-recommends -q --assume-yes install openjdk-21-jre-headless=21* libjemalloc-dev=5.* adduser=3* && \ + # $NO_PROXY_CACHE must not be used here or otherwise will trigger a hadolint error + apt-get -o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0 \ + --no-install-recommends -q --assume-yes install openjdk-21-jre-headless=21* libjemalloc-dev=5.* adduser=3* && \ # Clean apt cache apt-get clean && \ - rm -rf /var/cache/apt/archives/* && rm -rf /var/lib/apt/lists/* && \ + rm -rf /var/cache/apt/archives/* /var/cache/apt/archives/partial/* && \ + rm -rf /var/lib/apt/lists/* && \ # Ubuntu 23.10 comes with an "ubuntu" user with uid 1000. We need 1000 for besu. userdel ubuntu 2>/dev/null || true && rm -rf /home/ubuntu && \ # Ensure we use a stable UID for besu, as file permissions are tied to UIDs. From c52975b275b24be774a3c34a1f72d36016142e04 Mon Sep 17 00:00:00 2001 From: Matt Whitehead Date: Thu, 13 Jun 2024 02:14:36 +0100 Subject: [PATCH 08/22] Don't persist BFT proposed blocks, only committed ones (#7204) * Don't persist BFT proposed blocks, only committed ones Signed-off-by: Matthew Whitehead * Fix unit tests, update copyright Signed-off-by: Matthew Whitehead * Update changelog Signed-off-by: Matthew Whitehead --------- Signed-off-by: Matthew Whitehead Signed-off-by: Matt Whitehead --- CHANGELOG.md | 2 ++ .../validation/ProposalPayloadValidator.java | 4 ++-- .../ProposalPayloadValidatorTest.java | 18 ++++++++++++------ .../qbft/validation/ProposalValidatorTest.java | 8 +++++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cba041d31d0..520a087fc18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ ### Bug fixes - Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) - Validation errors ignored in accounts-allowlist and empty list [#7138](https://github.com/hyperledger/besu/issues/7138) +- Fix "Invalid block detected" for BFT chains using Bonsai DB [#7204](https://github.com/hyperledger/besu/pull/7204) + ## 24.5.2 ### Upcoming Breaking Changes diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java index bdb4ebd14c3..640216e9a97 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * 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 @@ -150,7 +150,7 @@ private boolean validateBlock(final Block block) { final var validationResult = blockValidator.validateAndProcessBlock( - protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL); + protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL, false); if (!validationResult.isSuccessful()) { LOG.info( diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java index e80f3028924..182393484ee 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java @@ -105,7 +105,8 @@ public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isTrue(); @@ -129,7 +130,8 @@ public void validationPassesWhenBlockRoundDoesNotMatchProposalRound() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isTrue(); @@ -152,7 +154,8 @@ public void validationFailsWhenBlockFailsValidation() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult("Failed")); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse(); @@ -228,7 +231,8 @@ public void validationFailsForBlockWithIncorrectHeight() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse(); @@ -262,7 +266,8 @@ public void validationForCmsFailsWhenCmsFailsValidation() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); when(cmsValidator.validate(eq(cms), eq(hashWithoutCms))).thenReturn(false); @@ -297,7 +302,8 @@ public void validationForCmsPassesWhenCmsIsValid() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); when(cmsValidator.validate(eq(cms), eq(hashWithoutCms))).thenReturn(true); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java index 53876f5b87e..c28588a9d5d 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * 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 @@ -111,7 +111,8 @@ QbftContext.class, emptyList(), bftExtraDataEncoder), eq(protocolContext), any(), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); @@ -168,7 +169,8 @@ public void validationFailsIfBlockIsInvalid() { eq(protocolContext), any(), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult("Failed")); assertThat(roundItem.messageValidator.validate(proposal)).isFalse(); From 90d2db97367b02768401c4fa44dd9dbb32996286 Mon Sep 17 00:00:00 2001 From: cangqiaoyuzhuo <850072022@qq.com> Date: Thu, 13 Jun 2024 13:06:08 +0900 Subject: [PATCH 09/22] chore: fix some comments (#7215) Signed-off-by: cangqiaoyuzhuo <850072022@qq.com> Co-authored-by: Sally MacFarlane --- .../main/java/org/hyperledger/besu/evm/code/CodeInvalid.java | 2 +- .../main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java index 688be5a3cfd..be71c296aef 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java @@ -25,7 +25,7 @@ import org.apache.tuweni.bytes.Bytes; /** - * For code versions where code can be deemed "invalid" this represents a cachable instance of + * For code versions where code can be deemed "invalid" this represents a cacheable instance of * invalid code. Note that EXTCODE operations can still access invalid code. */ public class CodeInvalid implements Code { diff --git a/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java b/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java index 22cc1ce13f1..a7386d7f78e 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java @@ -466,7 +466,7 @@ public void failure( futures.add(future); } - // return a future that completes succeessfully only when each of our port delete requests + // return a future that completes successfully only when each of our port delete requests // complete return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } From e3e86c7ef6c3216c6105500dc6b42b5e885966c1 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Thu, 13 Jun 2024 14:44:29 +1000 Subject: [PATCH 10/22] Subnet-Based Peer Permissions (#7168) Signed-off-by: Gabriel-Trintinalia --- CHANGELOG.md | 1 + besu/build.gradle | 1 + .../org/hyperledger/besu/RunnerBuilder.java | 22 ++++- .../org/hyperledger/besu/cli/BesuCommand.java | 12 +++ .../cli/converter/SubnetInfoConverter.java | 36 +++++++++ .../hyperledger/besu/cli/BesuCommandTest.java | 22 +++++ .../besu/cli/CommandTestAbstract.java | 3 + .../converter/SubnetInfoConverterTest.java | 59 ++++++++++++++ .../src/test/resources/everything_config.toml | 1 + ethereum/p2p/build.gradle | 1 + .../p2p/permissions/PeerPermissionSubnet.java | 78 ++++++++++++++++++ .../PeerPermissionsSubnetTest.java | 81 +++++++++++++++++++ gradle/verification-metadata.xml | 26 ++++++ gradle/versions.gradle | 1 + 14 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java create mode 100644 besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 520a087fc18..c77db882307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) - Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) - A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) +- Add Subnet-Based Peer Permissions. [#7168](https://github.com/hyperledger/besu/pull/7168) ### Bug fixes - Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) diff --git a/besu/build.gradle b/besu/build.gradle index 85c17e7ff35..286ca7a30fc 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -80,6 +80,7 @@ dependencies { implementation 'org.xerial.snappy:snappy-java' implementation 'tech.pegasys:jc-kzg-4844' implementation 'org.rocksdb:rocksdbjni' + implementation 'commons-net:commons-net' runtimeOnly 'org.apache.logging.log4j:log4j-jul' runtimeOnly 'com.splunk.logging:splunk-library-javalogging' diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index eb734ba606a..1f9c48cccc5 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -88,6 +88,7 @@ import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration; +import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissionSubnet; import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions; import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissionsDenylist; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; @@ -146,6 +147,7 @@ import graphql.GraphQL; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; @@ -192,6 +194,7 @@ public class RunnerBuilder { private JsonRpcIpcConfiguration jsonRpcIpcConfiguration; private boolean legacyForkIdEnabled; private Optional enodeDnsConfiguration; + private List allowedSubnets = new ArrayList<>(); /** Instantiates a new Runner builder. */ public RunnerBuilder() {} @@ -589,6 +592,17 @@ public RunnerBuilder enodeDnsConfiguration(final EnodeDnsConfiguration enodeDnsC return this; } + /** + * Add subnet configuration + * + * @param allowedSubnets the allowedSubnets + * @return the runner builder + */ + public RunnerBuilder allowedSubnets(final List allowedSubnets) { + this.allowedSubnets = allowedSubnets; + return this; + } + /** * Build Runner instance. * @@ -648,6 +662,10 @@ public Runner build() { final PeerPermissionsDenylist bannedNodes = PeerPermissionsDenylist.create(); bannedNodeIds.forEach(bannedNodes::add); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + final PeerPermissions defaultPeerPermissions = + PeerPermissions.combine(peerPermissionSubnet, bannedNodes); + final List bootnodes = discoveryConfiguration.getBootnodes(); final Synchronizer synchronizer = besuController.getSynchronizer(); @@ -667,8 +685,8 @@ public Runner build() { final PeerPermissions peerPermissions = nodePermissioningController .map(nodePC -> new PeerPermissionsAdapter(nodePC, bootnodes, context.getBlockchain())) - .map(nodePerms -> PeerPermissions.combine(nodePerms, bannedNodes)) - .orElse(bannedNodes); + .map(nodePerms -> PeerPermissions.combine(nodePerms, defaultPeerPermissions)) + .orElse(defaultPeerPermissions); LOG.info("Detecting NAT service."); final boolean fallbackEnabled = natMethod == NatMethod.AUTO || natMethodFallbackEnabled; diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index b14efb2e80b..9e612fea3b2 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -43,6 +43,7 @@ import org.hyperledger.besu.cli.config.ProfileName; import org.hyperledger.besu.cli.converter.MetricCategoryConverter; import org.hyperledger.besu.cli.converter.PercentageConverter; +import org.hyperledger.besu.cli.converter.SubnetInfoConverter; import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler; import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; @@ -243,6 +244,7 @@ import io.vertx.core.VertxOptions; import io.vertx.core.json.DecodeException; import io.vertx.core.metrics.MetricsOptions; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; @@ -527,6 +529,15 @@ private InetAddress autoDiscoverDefaultIP() { return autoDiscoveredDefaultIP; } + + @Option( + names = {"--net-restrict"}, + arity = "1..*", + split = ",", + converter = SubnetInfoConverter.class, + description = + "Comma-separated list of allowed IP subnets (e.g., '192.168.1.0/24,10.0.0.0/8').") + private List allowedSubnets; } @Option( @@ -2320,6 +2331,7 @@ private Runner synchronize( .storageProvider(keyValueStorageProvider(keyValueStorageName)) .rpcEndpointService(rpcEndpointServiceImpl) .enodeDnsConfiguration(getEnodeDnsConfiguration()) + .allowedSubnets(p2PDiscoveryOptionGroup.allowedSubnets) .build(); addShutdownHook(runner); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java new file mode 100644 index 00000000000..9ad9db9d14e --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.cli.converter; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import picocli.CommandLine; + +/** The SubnetInfo converter for CLI options. */ +public class SubnetInfoConverter implements CommandLine.ITypeConverter { + /** Default Constructor. */ + public SubnetInfoConverter() {} + + /** + * Converts an IP addresses with CIDR notation into SubnetInfo + * + * @param value The IP addresses with CIDR notation. + * @return the SubnetInfo + */ + @Override + public SubnetInfo convert(final String value) { + return new SubnetUtils(value).getInfo(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index e86e500a7d0..1e8d15cdb4f 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -1217,6 +1217,28 @@ public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() { .contains("Invalid value for option '--fast-sync-min-peers': 'ten' is not an int"); } + @Test + public void netRestrictParsedCorrectly() { + final String subnet1 = "127.0.0.1/24"; + final String subnet2 = "10.0.0.1/24"; + parseCommand("--net-restrict", String.join(",", subnet1, subnet2)); + verify(mockRunnerBuilder).allowedSubnets(allowedSubnetsArgumentCaptor.capture()); + assertThat(allowedSubnetsArgumentCaptor.getValue().size()).isEqualTo(2); + assertThat(allowedSubnetsArgumentCaptor.getValue().get(0).getCidrSignature()) + .isEqualTo(subnet1); + assertThat(allowedSubnetsArgumentCaptor.getValue().get(1).getCidrSignature()) + .isEqualTo(subnet2); + } + + @Test + public void netRestrictInvalidShouldFail() { + final String subnet = "127.0.0.1/abc"; + parseCommand("--net-restrict", subnet); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Invalid value for option '--net-restrict'"); + } + @Test public void ethStatsOptionIsParsedCorrectly() { final String url = "besu-node:secret@host:443"; diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 1dfb5f449b7..f620c729b68 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -117,6 +117,7 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import io.vertx.core.json.JsonObject; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.commons.text.StringEscapeUtils; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -261,6 +262,7 @@ public abstract class CommandTestAbstract { @Captor protected ArgumentCaptor apiConfigurationCaptor; @Captor protected ArgumentCaptor ethstatsOptionsArgumentCaptor; + @Captor protected ArgumentCaptor> allowedSubnetsArgumentCaptor; @BeforeEach public void initMocks() throws Exception { @@ -354,6 +356,7 @@ public void initMocks() throws Exception { when(mockRunnerBuilder.legacyForkId(anyBoolean())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.apiConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.enodeDnsConfiguration(any())).thenReturn(mockRunnerBuilder); + when(mockRunnerBuilder.allowedSubnets(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.build()).thenReturn(mockRunner); final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java new file mode 100644 index 00000000000..aaeb4536142 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.cli.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.junit.jupiter.api.Test; + +public class SubnetInfoConverterTest { + + @Test + void testCreateIpRestrictionHandlerWithValidSubnets() { + String subnet = "192.168.1.0/24"; + assertThat(parseSubnetRules(subnet).getCidrSignature()).isEqualTo(subnet); + } + + @Test + void testCreateIpRestrictionHandlerWithInvalidSubnet() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("abc")); + } + + @Test + void testCreateIpRestrictionHandlerMissingCIDR() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0")); + } + + @Test + void testCreateIpRestrictionHandlerBigCIDR() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0:25")); + } + + @Test + void testCreateIpRestrictionHandlerWithInvalidCIDR() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0/abc")); + } + + @Test + void testCreateIpRestrictionHandlerWithEmptyString() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("")); + } + + private SubnetInfo parseSubnetRules(final String subnet) { + return new SubnetInfoConverter().convert(subnet); + } +} diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index 6c98e2ebaf7..d5cc291237a 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -51,6 +51,7 @@ engine-jwt-disabled=true engine-jwt-secret="/tmp/jwt.hex" required-blocks=["8675309=123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"] discovery-dns-url="enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org" +net-restrict=["none"] # chain network="MAINNET" diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index 21bfdc6bf54..2d0331503f5 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -57,6 +57,7 @@ dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib' implementation 'org.owasp.encoder:encoder' implementation 'org.xerial.snappy:snappy-java' + implementation 'commons-net:commons-net' annotationProcessor "org.immutables:value" implementation "org.immutables:value-annotations" diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java new file mode 100644 index 00000000000..720cd4dd609 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java @@ -0,0 +1,78 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.p2p.permissions; + +import org.hyperledger.besu.ethereum.p2p.peers.Peer; + +import java.util.List; + +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages peer permissions based on IP subnet restrictions. + * + *

This class extends {@link PeerPermissions} to implement access control based on IP subnets. It + * allows for the configuration of permitted subnets and uses these configurations to determine + * whether a peer should be allowed or denied access based on its IP address. + * + *

Note: If no subnets are specified, all peers are considered permitted by default. + * + * @see PeerPermissions + */ +public class PeerPermissionSubnet extends PeerPermissions { + private static final Logger LOG = LoggerFactory.getLogger(PeerPermissionSubnet.class); + + private final List allowedSubnets; + + /** + * Constructs a new {@code PeerPermissionSubnet} instance with specified allowed subnets. + * + * @param allowedSubnets A list of {@link SubnetInfo} objects representing the subnets that are + * allowed to interact with the local node. Cannot be {@code null}. + */ + public PeerPermissionSubnet(final List allowedSubnets) { + this.allowedSubnets = allowedSubnets; + } + + /** + * Determines if a peer is permitted based on the configured subnets. + * + *

This method checks if the remote peer's IP address falls within any of the configured + * allowed subnets. If the peer's IP is within any of the allowed subnets, it is permitted. + * Otherwise, it is denied. + * + * @param localNode This parameter is not used in the current implementation. + * @param remotePeer The remote peer to check. Its IP address is used to determine permission. + * @param action Ignored. If the peer is not allowed in the subnet, all actions are now allowed. + * @return {@code true} if the peer is permitted based on its IP address; {@code false} otherwise. + */ + @Override + public boolean isPermitted(final Peer localNode, final Peer remotePeer, final Action action) { + // If no subnets are specified, all peers are permitted + if (allowedSubnets == null || allowedSubnets.isEmpty()) { + return true; + } + String remotePeerHostAddress = remotePeer.getEnodeURL().getIpAsString(); + for (SubnetInfo subnet : allowedSubnets) { + if (subnet.isInRange(remotePeerHostAddress)) { + return true; + } + } + LOG.trace("Peer {} is not allowed in any of the configured subnets.", remotePeerHostAddress); + return false; + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java new file mode 100644 index 00000000000..185858e658e --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java @@ -0,0 +1,81 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.p2p.permissions; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; +import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; +import org.hyperledger.besu.ethereum.p2p.peers.Peer; +import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions.Action; + +import java.util.List; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.junit.jupiter.api.Test; + +public class PeerPermissionsSubnetTest { + + private final Peer remoteNode = createPeer(); + + @Test + public void peerInSubnetRangeShouldBePermitted() { + List allowedSubnets = List.of(subnet("127.0.0.0/24")); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + checkPermissions(peerPermissionSubnet, remoteNode, true); + } + + @Test + public void peerInAtLeastOneSubnetRangeShouldBePermitted() { + List allowedSubnets = List.of(subnet("127.0.0.0/24"), subnet("10.0.0.1/24")); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + checkPermissions(peerPermissionSubnet, remoteNode, true); + } + + @Test + public void peerOutSubnetRangeShouldNotBePermitted() { + List allowedSubnets = List.of(subnet("10.0.0.0/24")); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + checkPermissions(peerPermissionSubnet, remoteNode, false); + } + + @Test + public void peerShouldBePermittedIfNoSubnets() { + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(List.of()); + checkPermissions(peerPermissionSubnet, remoteNode, true); + } + + private void checkPermissions( + final PeerPermissions peerPermissions, final Peer remotePeer, final boolean expectedResult) { + for (Action action : Action.values()) { + assertThat(peerPermissions.isPermitted(createPeer(), remotePeer, action)) + .isEqualTo(expectedResult); + } + } + + private SubnetInfo subnet(final String subnet) { + return new SubnetUtils(subnet).getInfo(); + } + + private Peer createPeer() { + return DefaultPeer.fromEnodeURL( + EnodeURLImpl.builder() + .nodeId(Peer.randomId()) + .ipAddress("127.0.0.1") + .discoveryAndListeningPorts(EnodeURLImpl.DEFAULT_LISTENING_PORT) + .build()); + } +} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 253868aa367..86f65970e61 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1353,6 +1353,14 @@ + + + + + + + + @@ -3007,6 +3015,11 @@ + + + + + @@ -3161,6 +3174,11 @@ + + + + + @@ -5135,6 +5153,14 @@ + + + + + + + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 4dd279d7e5f..db29cfd589b 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -134,6 +134,7 @@ dependencyManagement { dependency 'org.apache.commons:commons-lang3:3.14.0' dependency 'org.apache.commons:commons-text:1.11.0' dependency 'org.apache.commons:commons-collections4:4.4' + dependency 'commons-net:commons-net:3.11.0' dependencySet(group: 'org.apache.logging.log4j', version: '2.22.1') { entry 'log4j-api' From 19d20793771f90c17454419affca85e9235db27c Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Thu, 13 Jun 2024 12:25:51 +0200 Subject: [PATCH 11/22] Reduce lock contention on transaction pool when building a block (#7180) * Avoid keeping txpool lock during block creation Signed-off-by: Fabio Di Fabio * Update CHANGELOG Signed-off-by: Fabio Di Fabio * Remove unneeded synchronized Signed-off-by: Fabio Di Fabio --------- Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 1 + .../txselection/BlockTransactionSelector.java | 2 +- .../bonsai/AbstractIsolationTests.java | 3 +- .../transactions/TransactionPoolFactory.java | 3 +- .../AbstractPrioritizedTransactions.java | 22 ++- .../layered/AbstractTransactionsLayer.java | 26 ++-- .../layered/LayeredPendingTransactions.java | 129 ++++++++---------- .../layered/ReadyTransactions.java | 18 ++- .../layered/SenderPendingTransactions.java | 43 ++++++ .../layered/SparseTransactions.java | 31 ++++- .../layered/TransactionsLayer.java | 8 +- .../AbstractLayeredTransactionPoolTest.java | 3 +- .../LayeredPendingTransactionsTest.java | 8 +- .../eth/transactions/layered/LayersTest.java | 54 ++++---- .../eth/transactions/layered/ReplayTest.java | 2 +- 15 files changed, 222 insertions(+), 131 deletions(-) create mode 100644 ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c77db882307..9cae8bb60b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) - A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) - Add Subnet-Based Peer Permissions. [#7168](https://github.com/hyperledger/besu/pull/7168) +- Reduce lock contention on transaction pool when building a block [#7180](https://github.com/hyperledger/besu/pull/7180) ### Bug fixes - Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java index 22d75e27aaa..ab311c3fb38 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java @@ -164,7 +164,7 @@ private List createTransactionSelectors( public TransactionSelectionResults buildTransactionListForBlock() { LOG.atDebug() .setMessage("Transaction pool stats {}") - .addArgument(blockSelectionContext.transactionPool().logStats()) + .addArgument(blockSelectionContext.transactionPool()::logStats) .log(); timeLimitedSelection(); LOG.atTrace() diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java index 62adf26eccb..845b859ef73 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java @@ -136,7 +136,8 @@ public abstract class AbstractIsolationTests { txPoolMetrics, transactionReplacementTester, new BlobCache(), - MiningParameters.newDefault())); + MiningParameters.newDefault()), + ethScheduler); protected final List accounts = GenesisConfigFile.fromResource("/dev.json") diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java index 4c0462f2d72..d32b7bafe8e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java @@ -357,6 +357,7 @@ private static PendingTransactions createLayeredPendingTransactions( miningParameters); } - return new LayeredPendingTransactions(transactionPoolConfiguration, pendingTransactionsSorter); + return new LayeredPendingTransactions( + transactionPoolConfiguration, pendingTransactionsSorter, ethScheduler); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java index 11654be53cb..7a65b3febca 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java @@ -24,13 +24,13 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Predicate; -import java.util.stream.Stream; /** * Holds the current set of executable pending transactions, that are candidate for inclusion on @@ -167,9 +167,25 @@ protected int[] getRemainingPromotionsPerType() { return remainingPromotionsPerType; } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + *

Returned sender list order detail: first the sender of the most profitable tx. + * + * @return a list of sender pending txs + */ @Override - public Stream stream() { - return orderByFee.descendingSet().stream(); + public List getBySender() { + final var sendersToAdd = new HashSet<>(txsBySender.keySet()); + return orderByFee.descendingSet().stream() + .map(PendingTransaction::getSender) + .filter(sendersToAdd::remove) + .map( + sender -> + new SenderPendingTransactions( + sender, List.copyOf(txsBySender.get(sender).values()))) + .toList(); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java index 9cc8b44296a..5997fe6c189 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; +import static java.util.Collections.unmodifiableList; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.REJECTED_UNDERPRICED_REPLACEMENT; @@ -54,7 +55,6 @@ import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -138,6 +138,14 @@ public boolean contains(final Transaction transaction) { || nextLayer.contains(transaction); } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + * @return a list of sender pending txs + */ + public abstract List getBySender(); + @Override public List getAll() { final List allNextLayers = nextLayer.getAll(); @@ -548,17 +556,17 @@ public List getAllPriority() { return priorityTxs; } - Stream stream(final Address sender) { - return txsBySender.getOrDefault(sender, EMPTY_SENDER_TXS).values().stream(); - } - @Override - public List getAllFor(final Address sender) { - return Stream.concat(stream(sender), nextLayer.getAllFor(sender).stream()).toList(); + public synchronized List getAllFor(final Address sender) { + final var fromNextLayers = nextLayer.getAllFor(sender); + final var fromThisLayer = txsBySender.getOrDefault(sender, EMPTY_SENDER_TXS).values(); + final var concatLayers = + new ArrayList(fromThisLayer.size() + fromNextLayers.size()); + concatLayers.addAll(fromThisLayer); + concatLayers.addAll(fromNextLayers); + return unmodifiableList(concatLayers); } - abstract Stream stream(); - @Override public int count() { return pendingTransactions.size() + nextLayer.count(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java index 9e304a4f0f5..9444885ebe4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedListener; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; @@ -41,13 +42,10 @@ import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalLong; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -63,12 +61,15 @@ public class LayeredPendingTransactions implements PendingTransactions { private static final Marker INVALID_TX_REMOVED = MarkerFactory.getMarker("INVALID_TX_REMOVED"); private final TransactionPoolConfiguration poolConfig; private final AbstractPrioritizedTransactions prioritizedTransactions; + private final EthScheduler ethScheduler; public LayeredPendingTransactions( final TransactionPoolConfiguration poolConfig, - final AbstractPrioritizedTransactions prioritizedTransactions) { + final AbstractPrioritizedTransactions prioritizedTransactions, + final EthScheduler ethScheduler) { this.poolConfig = poolConfig; this.prioritizedTransactions = prioritizedTransactions; + this.ethScheduler = ethScheduler; } @Override @@ -311,79 +312,57 @@ public synchronized List getPriorityTransactions() { } @Override - // There's a small edge case here we could encounter. - // When we pass an upgrade block that has a new transaction type, we start allowing transactions - // of that new type into our pool. - // If we then reorg to a block lower than the upgrade block height _and_ we create a block, that - // block could end up with transactions of the new type. - // This seems like it would be very rare but worth it to document that we don't handle that case - // right now. - public synchronized void selectTransactions( - final PendingTransactions.TransactionSelector selector) { + public void selectTransactions(final PendingTransactions.TransactionSelector selector) { final List invalidTransactions = new ArrayList<>(); - final Set alreadyChecked = new HashSet<>(); - final Set

skipSenders = new HashSet<>(); - final AtomicBoolean completed = new AtomicBoolean(false); - - prioritizedTransactions.stream() - .takeWhile(unused -> !completed.get()) - .filter(highPrioPendingTx -> !skipSenders.contains(highPrioPendingTx.getSender())) - .peek(this::logSenderTxs) - .forEach( - highPrioPendingTx -> - prioritizedTransactions.stream(highPrioPendingTx.getSender()) - .takeWhile( - candidatePendingTx -> - !skipSenders.contains(candidatePendingTx.getSender()) - && !completed.get()) - .filter( - candidatePendingTx -> - !alreadyChecked.contains(candidatePendingTx.getHash()) - && Long.compareUnsigned( - candidatePendingTx.getNonce(), highPrioPendingTx.getNonce()) - <= 0) - .forEach( - candidatePendingTx -> { - alreadyChecked.add(candidatePendingTx.getHash()); - final var res = selector.evaluateTransaction(candidatePendingTx); - - LOG.atTrace() - .setMessage("Selection result {} for transaction {}") - .addArgument(res) - .addArgument(candidatePendingTx::toTraceLog) - .log(); - - if (res.discard()) { - invalidTransactions.add(candidatePendingTx); - logDiscardedTransaction(candidatePendingTx, res); - } - - if (res.stop()) { - completed.set(true); - } - - if (!res.selected()) { - // avoid processing other txs from this sender if this one is skipped - // since the following will not be selected due to the nonce gap - skipSenders.add(candidatePendingTx.getSender()); - LOG.trace("Skipping tx from sender {}", candidatePendingTx.getSender()); - } - })); - - invalidTransactions.forEach( - invalidTx -> prioritizedTransactions.remove(invalidTx, INVALIDATED)); - } - private void logSenderTxs(final PendingTransaction highPrioPendingTx) { - LOG.atTrace() - .setMessage("highPrioPendingTx {}, senderTxs {}") - .addArgument(highPrioPendingTx::toTraceLog) - .addArgument( - () -> - prioritizedTransactions.stream(highPrioPendingTx.getSender()) - .map(PendingTransaction::toTraceLog) - .collect(Collectors.joining(", "))) - .log(); + final List candidateTxsBySender; + synchronized (this) { + // since selecting transactions for block creation is a potential long operation + // we want to avoid to keep the lock for all the process, but we just lock to get + // the candidate transactions + candidateTxsBySender = prioritizedTransactions.getBySender(); + } + + selection: + for (final var senderTxs : candidateTxsBySender) { + LOG.trace("highPrioSenderTxs {}", senderTxs); + + for (final var candidatePendingTx : senderTxs.pendingTransactions()) { + final var selectionResult = selector.evaluateTransaction(candidatePendingTx); + + LOG.atTrace() + .setMessage("Selection result {} for transaction {}") + .addArgument(selectionResult) + .addArgument(candidatePendingTx::toTraceLog) + .log(); + + if (selectionResult.discard()) { + invalidTransactions.add(candidatePendingTx); + logDiscardedTransaction(candidatePendingTx, selectionResult); + } + + if (selectionResult.stop()) { + LOG.trace("Stopping selection"); + break selection; + } + + if (!selectionResult.selected()) { + // avoid processing other txs from this sender if this one is skipped + // since the following will not be selected due to the nonce gap + LOG.trace("Skipping remaining txs for sender {}", candidatePendingTx.getSender()); + break; + } + } + } + + ethScheduler.scheduleTxWorkerTask( + () -> + invalidTransactions.forEach( + invalidTx -> { + synchronized (this) { + prioritizedTransactions.remove(invalidTx, INVALIDATED); + } + })); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java index 3cd07ecab9c..1f9fc0ab8d6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java @@ -38,7 +38,6 @@ import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; public class ReadyTransactions extends AbstractSequentialTransactionsLayer { @@ -137,11 +136,24 @@ protected boolean promotionFilter(final PendingTransaction pendingTransaction) { return true; } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + *

Returned sender list order detail: first the sender of the tx with the highest max gas + * price. + * + * @return a list of sender pending txs + */ @Override - public Stream stream() { + public List getBySender() { return orderByMaxFee.descendingSet().stream() .map(PendingTransaction::getSender) - .flatMap(sender -> txsBySender.get(sender).values().stream()); + .map( + sender -> + new SenderPendingTransactions( + sender, List.copyOf(txsBySender.get(sender).values()))) + .toList(); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java new file mode 100644 index 00000000000..7fb7c5cfeb7 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java @@ -0,0 +1,43 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.eth.transactions.layered; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * A list of pending transactions of a specific sender, ordered by nonce asc + * + * @param sender the sender + * @param pendingTransactions the list of pending transactions order by nonce asc + */ +public record SenderPendingTransactions( + Address sender, List pendingTransactions) { + + @Override + public String toString() { + return "Sender " + + sender + + " has " + + pendingTransactions.size() + + " pending transactions " + + pendingTransactions.stream() + .map(PendingTransaction::toTraceLog) + .collect(Collectors.joining(",", "[", "]")); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java index 1a4f7b67d8c..52f598ba71a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java @@ -44,15 +44,19 @@ import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.IntStream; -import java.util.stream.Stream; import com.google.common.collect.Iterables; public class SparseTransactions extends AbstractTransactionsLayer { + /** + * Order sparse tx by priority flag and sequence asc, so that we pick for eviction txs that have + * no priority and with the lowest sequence number (oldest) first. + */ private final NavigableSet sparseEvictionOrder = new TreeSet<>( Comparator.comparing(PendingTransaction::hasPriority) .thenComparing(PendingTransaction::getSequence)); + private final Map gapBySender = new HashMap<>(); private final List orderByGap; @@ -220,7 +224,8 @@ private NavigableMap getSequentialSubset( } @Override - public void remove(final PendingTransaction invalidatedTx, final RemovalReason reason) { + public synchronized void remove( + final PendingTransaction invalidatedTx, final RemovalReason reason) { final var senderTxs = txsBySender.get(invalidatedTx.getSender()); if (senderTxs != null && senderTxs.containsKey(invalidatedTx.getNonce())) { @@ -312,9 +317,27 @@ protected boolean promotionFilter(final PendingTransaction pendingTransaction) { return false; } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + *

Returned sender list order detail: first the sender of the tx that will be evicted as last. + * So for example if the same sender has the first and the last txs in the eviction order, it will + * be the first in the returned list, since we give precedence to tx that will be evicted later. + * + * @return a list of sender pending txs + */ @Override - public Stream stream() { - return sparseEvictionOrder.descendingSet().stream(); + public List getBySender() { + final var sendersToAdd = new HashSet<>(txsBySender.keySet()); + return sparseEvictionOrder.descendingSet().stream() + .map(PendingTransaction::getSender) + .filter(sendersToAdd::remove) + .map( + sender -> + new SenderPendingTransactions( + sender, List.copyOf(txsBySender.get(sender).values()))) + .toList(); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java index 46c756226d2..d3c22aeef10 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java @@ -41,8 +41,6 @@ public interface TransactionsLayer { boolean contains(Transaction transaction); - List getAll(); - TransactionAddedResult add(PendingTransaction pendingTransaction, int gap); void remove(PendingTransaction pendingTransaction, RemovalReason reason); @@ -52,6 +50,10 @@ void blockAdded( BlockHeader blockHeader, final Map maxConfirmedNonceBySender); + List getAll(); + + List getAllFor(Address sender); + List getAllLocal(); List getAllPriority(); @@ -93,8 +95,6 @@ List promote( String logSender(Address sender); - List getAllFor(Address sender); - enum RemovalReason { CONFIRMED, CROSS_LAYER_REPLACED, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java index c43f7d6cb30..1e2c4101929 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java @@ -58,7 +58,8 @@ protected PendingTransactions createPendingTransactions( return new LayeredPendingTransactions( poolConfig, createPrioritizedTransactions( - poolConfig, readyLayer, txPoolMetrics, transactionReplacementTester)); + poolConfig, readyLayer, txPoolMetrics, transactionReplacementTester), + ethScheduler); } protected abstract AbstractPrioritizedTransactions createPrioritizedTransactions( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java index 3b79b463687..b2f03ce175d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java @@ -170,14 +170,16 @@ public void setup() { senderLimitedLayers = createLayers(senderLimitedConfig); smallLayers = createLayers(smallPoolConfig); - pendingTransactions = new LayeredPendingTransactions(poolConf, layers.prioritizedTransactions); + pendingTransactions = + new LayeredPendingTransactions(poolConf, layers.prioritizedTransactions, ethScheduler); senderLimitedTransactions = new LayeredPendingTransactions( - senderLimitedConfig, senderLimitedLayers.prioritizedTransactions); + senderLimitedConfig, senderLimitedLayers.prioritizedTransactions, ethScheduler); smallPendingTransactions = - new LayeredPendingTransactions(smallPoolConfig, smallLayers.prioritizedTransactions); + new LayeredPendingTransactions( + smallPoolConfig, smallLayers.prioritizedTransactions, ethScheduler); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java index dab3cce6810..6946111e5ce 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java @@ -199,7 +199,7 @@ private void assertScenario( MiningParameters.newDefault().setMinTransactionGasPrice(MIN_GAS_PRICE)); final LayeredPendingTransactions pendingTransactions = - new LayeredPendingTransactions(poolConfig, prioritizedTransactions); + new LayeredPendingTransactions(poolConfig, prioritizedTransactions, ethScheduler); scenario.execute( pendingTransactions, @@ -306,7 +306,7 @@ static Stream providerAddTransactions() { Arguments.of( new Scenario("fill sparse 2") .addForSender(S1, 5, 3, 2) - .expectedSparseForSender(S1, 5, 3, 2)), + .expectedSparseForSender(S1, 2, 3, 5)), Arguments.of( new Scenario("overflow sparse 1") .addForSender(S1, 1, 2, 3, 4) @@ -315,13 +315,13 @@ static Stream providerAddTransactions() { Arguments.of( new Scenario("overflow sparse 2") .addForSender(S1, 4, 2, 3, 1) - .expectedSparseForSender(S1, 2, 3, 1) + .expectedSparseForSender(S1, 1, 2, 3) .expectedDroppedForSender(S1, 4)), Arguments.of( new Scenario("overflow sparse 3") .addForSender(S1, 0, 4, 2, 3, 5) .expectedPrioritizedForSender(S1, 0) - .expectedSparseForSender(S1, 4, 2, 3) + .expectedSparseForSender(S1, 2, 3, 4) .expectedDroppedForSender(S1, 5))); } @@ -334,7 +334,7 @@ static Stream providerAddTransactionsMultipleSenders() { Arguments.of( new Scenario("add first sparse") .addForSenders(S1, 1, S2, 2) - .expectedSparseForSenders(S1, 1, S2, 2)), + .expectedSparseForSenders(S2, 2, S1, 1)), Arguments.of( new Scenario("fill prioritized 1") .addForSender(S1, 0, 1, 2) @@ -357,11 +357,11 @@ static Stream providerAddTransactionsMultipleSenders() { .addForSenders(S1, 2, S2, 1) .expectedPrioritizedForSenders() .expectedReadyForSenders() - .expectedSparseForSenders(S1, 2, S2, 1) + .expectedSparseForSenders(S2, 1, S1, 2) .addForSenders(S2, 2, S1, 0) .expectedPrioritizedForSender(S1, 0) .expectedReadyForSenders() - .expectedSparseForSenders(S1, 2, S2, 1, S2, 2) + .expectedSparseForSenders(S2, 1, S2, 2, S1, 2) .addForSenders(S1, 1) .expectedPrioritizedForSenders(S1, 0, S1, 1, S1, 2) .expectedReadyForSenders() @@ -431,15 +431,15 @@ static Stream providerAddTransactionsMultipleSenders() { .addForSenders(S2, 0, S3, 2, S1, 1) .expectedPrioritizedForSender(S2, 0) .expectedReadyForSenders() - .expectedSparseForSenders(S3, 2, S1, 1) + .expectedSparseForSenders(S1, 1, S3, 2) .addForSenders(S2, 1) .expectedPrioritizedForSenders(S2, 0, S2, 1) .expectedReadyForSenders() - .expectedSparseForSenders(S3, 2, S1, 1) + .expectedSparseForSenders(S1, 1, S3, 2) .addForSenders(S3, 0) .expectedPrioritizedForSenders(S3, 0, S2, 0, S2, 1) .expectedReadyForSenders() - .expectedSparseForSenders(S3, 2, S1, 1) + .expectedSparseForSenders(S1, 1, S3, 2) .addForSenders(S1, 0) .expectedPrioritizedForSenders(S3, 0, S2, 0, S2, 1) .expectedReadyForSenders(S1, 0, S1, 1) @@ -452,7 +452,7 @@ static Stream providerAddTransactionsMultipleSenders() { .addForSenders(S4, 0, S4, 1, S3, 3) .expectedPrioritizedForSenders(S4, 0, S4, 1, S3, 0) .expectedReadyForSenders(S3, 1, S2, 0, S2, 1) - .expectedSparseForSenders(S3, 2, S1, 1, S1, 0) + .expectedSparseForSenders(S1, 0, S1, 1, S3, 2) // ToDo: non optimal discard, worth to improve? .expectedDroppedForSender(S3, 3)), Arguments.of( @@ -813,7 +813,7 @@ static Stream providerNextNonceForSender() { Arguments.of( new Scenario("out of order sequence with gap 1") .addForSender(S1, 2, 1) - .expectedSparseForSender(S1, 2, 1) + .expectedSparseForSender(S1, 1, 2) .expectedNextNonceForSenders(S1, null)), Arguments.of( new Scenario("out of order sequence with gap 2") @@ -969,7 +969,7 @@ static Stream providerSelectTransactions() { Arguments.of( new Scenario("out of order sequence with gap 1") .addForSender(S1, 2, 1) - .expectedSparseForSender(S1, 2, 1) + .expectedSparseForSender(S1, 1, 2) .expectedSelectedTransactions()), Arguments.of( new Scenario("out of order sequence with gap 2") @@ -1073,8 +1073,7 @@ static Stream providerAsyncWorldStateUpdates() { .setAccountNonce(S1, 5) .addForSender(S1, 7) .expectedPrioritizedForSenders() - // remember that sparse are checked by oldest first - .expectedSparseForSender(S1, 8, 9, 7))); + .expectedSparseForSender(S1, 7, 8, 9))); } static Stream providerPrioritySenders() { @@ -1195,7 +1194,7 @@ static Stream providerPrioritySenders() { .addForSender(S3, 0) .expectedSparseForSender(S3, 0) .addForSender(SP1, 0) - .expectedSparseForSenders(S3, 0, SP1, 0) + .expectedSparseForSenders(SP1, 0, S3, 0) .confirmedForSenders(SP2, 0) .expectedPrioritizedForSender(SP2, 1, 2, 3) .expectedReadyForSenders(SP2, 4, SP2, 5, SP1, 0) @@ -1510,23 +1509,26 @@ public Scenario expectedDroppedForSenders() { private void assertExpectedPrioritized( final AbstractPrioritizedTransactions prioLayer, final List expected) { - assertThat(prioLayer.stream()).describedAs("Prioritized").containsExactlyElementsOf(expected); + assertThat(prioLayer.getBySender()) + .describedAs("Prioritized") + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expected); } private void assertExpectedReady( final ReadyTransactions readyLayer, final List expected) { - assertThat(readyLayer.stream()).describedAs("Ready").containsExactlyElementsOf(expected); + assertThat(readyLayer.getBySender()) + .describedAs("Ready") + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expected); } private void assertExpectedSparse( final SparseTransactions sparseLayer, final List expected) { - // sparse txs are returned from the most recent to the oldest, so reverse it to make writing - // scenarios easier - final var sortedExpected = new ArrayList<>(expected); - Collections.reverse(sortedExpected); - assertThat(sparseLayer.stream()) + assertThat(sparseLayer.getBySender()) .describedAs("Sparse") - .containsExactlyElementsOf(sortedExpected); + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expected); } private void assertExpectedDropped( @@ -1587,7 +1589,9 @@ public Scenario expectedSelectedTransactions(final Object... args) { } actions.add( (pending, prio, ready, sparse, dropped) -> - assertThat(prio.stream()).containsExactlyElementsOf(expectedSelected)); + assertThat(prio.getBySender()) + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expectedSelected)); return this; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java index 8b395a44153..14ed39c2e59 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java @@ -129,7 +129,7 @@ public void replay() throws IOException { final AbstractPrioritizedTransactions prioritizedTransactions = createLayers(poolConfig, txPoolMetrics, baseFeeMarket); final LayeredPendingTransactions pendingTransactions = - new LayeredPendingTransactions(poolConfig, prioritizedTransactions); + new LayeredPendingTransactions(poolConfig, prioritizedTransactions, ethScheduler); br.lines() .forEach( line -> { From 1ee35a3dea261e2782616a9585fdb7ed6b7b4654 Mon Sep 17 00:00:00 2001 From: Matt Whitehead Date: Thu, 13 Jun 2024 11:56:56 +0100 Subject: [PATCH 12/22] Change eth trace from 'Message not expected' to 'Request message' (#6734) * Change eth trace from 'Message not expected' to 'Request message' Signed-off-by: Matthew Whitehead * Add braces to log message Signed-off-by: Matthew Whitehead * Spotless fix Signed-off-by: Matthew Whitehead --------- Signed-off-by: Matthew Whitehead Signed-off-by: Matt Whitehead --- .../org/hyperledger/besu/ethereum/eth/manager/EthPeer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java index 41db20bb07f..e0f0517f1e6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * 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 @@ -437,7 +437,7 @@ Optional dispatch(final EthMessage ethMessage, final String prot localRequestManager -> localRequestManager.dispatchResponse(ethMessage), () -> { LOG.trace( - "Message {} not expected has just been received for protocol {}, {} ", + "Request message {} has just been received for protocol {}, peer {} ", messageCode, protocolName, this); From 884834f352b6e6b112c481690646f2f4cfe6d347 Mon Sep 17 00:00:00 2001 From: Chaminda Divitotawela Date: Thu, 13 Jun 2024 22:21:00 +1000 Subject: [PATCH 13/22] Add container security scanning (#7216) Container security scanning workflow added. This runs on schedule everyday. Also possible to run on-demand for a given image tag Signed-off-by: Chaminda Divitotawela Co-authored-by: Sally MacFarlane Co-authored-by: Justin Florentine --- .github/workflows/container-security-scan.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/container-security-scan.yml diff --git a/.github/workflows/container-security-scan.yml b/.github/workflows/container-security-scan.yml new file mode 100644 index 00000000000..e88689a06db --- /dev/null +++ b/.github/workflows/container-security-scan.yml @@ -0,0 +1,44 @@ +name: container security scan + +on: + workflow_dispatch: + inputs: + tag: + description: 'Container image tag' + required: false + default: 'develop' + schedule: + # Start of the hour is the busy time. Scheule it to run 8:17am UTC + - cron: '17 8 * * *' + +jobs: + scan-sarif: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Shell parameter expansion does not support directly on a step + # Adding a separate step to set the image tag. This allows running + # this workflow with a schedule as well as manual + - name: Set image tag + id: tag + run: | + echo "TAG=${INPUT_TAG:-develop}" >> "$GITHUB_OUTPUT" + env: + INPUT_TAG: ${{ inputs.tag }} + + - name: Vulnerability scanner + id: trivy + uses: aquasecurity/trivy-action@0.22.0 + with: + image-ref: hyperledger/besu:${{ steps.tag.outputs.TAG }} + format: sarif + output: 'trivy-results.sarif' + + # Check the vulnerabilities via GitHub security tab + - name: Upload results + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results.sarif' From 1837f46080f4403ebe8bc85c3b1bb9cd1f1ab2d5 Mon Sep 17 00:00:00 2001 From: Chaminda Divitotawela Date: Fri, 14 Jun 2024 10:06:40 +1000 Subject: [PATCH 14/22] fix: pin github actions (#7228) Repository follow standard to use git hash to pin the GitHub actions. Updated the container security scan workflow actions with their git hashes Signed-off-by: Chaminda Divitotawela --- .github/workflows/container-security-scan.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/container-security-scan.yml b/.github/workflows/container-security-scan.yml index e88689a06db..85065c828cc 100644 --- a/.github/workflows/container-security-scan.yml +++ b/.github/workflows/container-security-scan.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Shell parameter expansion does not support directly on a step # Adding a separate step to set the image tag. This allows running @@ -31,7 +31,7 @@ jobs: - name: Vulnerability scanner id: trivy - uses: aquasecurity/trivy-action@0.22.0 + uses: aquasecurity/trivy-action@595be6a0f6560a0a8fc419ddf630567fc623531d with: image-ref: hyperledger/besu:${{ steps.tag.outputs.TAG }} format: sarif @@ -39,6 +39,6 @@ jobs: # Check the vulnerabilities via GitHub security tab - name: Upload results - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 with: sarif_file: 'trivy-results.sarif' From ad98f6d6edebaf2b6d464a9ee7977ad0ce20fba2 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Fri, 14 Jun 2024 14:11:28 +1000 Subject: [PATCH 15/22] Changelog download links for 24.6.0 release and next release changelog (#7230) Signed-off-by: Jason Frame --- CHANGELOG.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cae8bb60b2..3724ed84a43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## Next Release + +### Breaking Changes + +### Additions and Improvements +- Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) +- Improve genesis state performance at startup [#6977](https://github.com/hyperledger/besu/pull/6977) +- Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) +- A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) +- Add Subnet-Based Peer Permissions. [#7168](https://github.com/hyperledger/besu/pull/7168) +- Reduce lock contention on transaction pool when building a block [#7180](https://github.com/hyperledger/besu/pull/7180) + +### Bug fixes +- Validation errors ignored in accounts-allowlist and empty list [#7138](https://github.com/hyperledger/besu/issues/7138) +- Fix "Invalid block detected" for BFT chains using Bonsai DB [#7204](https://github.com/hyperledger/besu/pull/7204) + ## 24.6.0 ### Breaking Changes @@ -13,26 +29,22 @@ - PKI-backed QBFT will be removed in a future version of Besu. Other forms of QBFT will remain unchanged. - --Xbonsai-limit-trie-logs-enabled is deprecated, use --bonsai-limit-trie-logs-enabled instead - --Xbonsai-trie-logs-pruning-window-size is deprecated, use --bonsai-trie-logs-pruning-window-size instead -- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. ### Additions and Improvements - Add two counters to DefaultBlockchain in order to be able to calculate TPS and Mgas/s [#7105](https://github.com/hyperledger/besu/pull/7105) -- Improve genesis state performance at startup [#6977](https://github.com/hyperledger/besu/pull/6977) - Enable --Xbonsai-limit-trie-logs-enabled by default, unless sync-mode=FULL [#7181](https://github.com/hyperledger/besu/pull/7181) - Promote experimental --Xbonsai-limit-trie-logs-enabled to production-ready, --bonsai-limit-trie-logs-enabled [#7192](https://github.com/hyperledger/besu/pull/7192) - Promote experimental --Xbonsai-trie-logs-pruning-window-size to production-ready, --bonsai-trie-logs-pruning-window-size [#7192](https://github.com/hyperledger/besu/pull/7192) - `admin_nodeInfo` JSON/RPC call returns the currently active EVM version [#7127](https://github.com/hyperledger/besu/pull/7127) - Improve the selection of the most profitable built block [#7174](https://github.com/hyperledger/besu/pull/7174) -- Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) -- Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) -- A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) -- Add Subnet-Based Peer Permissions. [#7168](https://github.com/hyperledger/besu/pull/7168) -- Reduce lock contention on transaction pool when building a block [#7180](https://github.com/hyperledger/besu/pull/7180) ### Bug fixes - Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) -- Validation errors ignored in accounts-allowlist and empty list [#7138](https://github.com/hyperledger/besu/issues/7138) -- Fix "Invalid block detected" for BFT chains using Bonsai DB [#7204](https://github.com/hyperledger/besu/pull/7204) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.6.0 +https://github.com/hyperledger/besu/releases/download/24.6.0/besu-24.6.0.tar.gz / sha256 fa86e5c6873718cd568e3326151ce06957a5e7546b52df79a831ea9e39b857ab +https://github.com/hyperledger/besu/releases/download/24.6.0/besu-24.6.0.zip / sha256 8b2d3a674cd7ead68b9ca68fea21e46d5ec9b278bbadc73f8c13c6a1e1bc0e4d ## 24.5.2 From 529bd336fcf0a9f6c9b7c8d5dc9a698b2e4c40ab Mon Sep 17 00:00:00 2001 From: Chaminda Divitotawela Date: Fri, 14 Jun 2024 23:06:47 +1000 Subject: [PATCH 16/22] fix: update artifacts hash on release page (#7231) Release workflow publish step was missing the depepndency of artifacts jobs. Due to this reason it could not collect the artifact hashes from the artifacts job. This was introduced in the release workflow consolidation Signed-off-by: Chaminda Divitotawela --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 496691293dc..b1f8fb2fc5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: publish: runs-on: ubuntu-22.04 - needs: [testWindows] + needs: [testWindows, artifacts] permissions: contents: write steps: From db9710b2aafde17ea850cde88c97779f66102eec Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Sun, 16 Jun 2024 01:43:51 -0600 Subject: [PATCH 17/22] check initcode size earlier (#7233) Fail earlier with the initcode size check Signed-off-by: Danno Ferrin --- .../besu/evm/operation/AbstractCreateOperation.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index a484f28ceb6..180eac27993 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -104,6 +104,11 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { Code code = codeSupplier.get(); + if (code != null && code.getSize() > maxInitcodeSize) { + frame.popStackItems(getStackItemsConsumed()); + return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); + } + if (value.compareTo(account.getBalance()) > 0 || frame.getDepth() >= 1024 || account.getNonce() == -1 @@ -113,14 +118,9 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } else { account.incrementNonce(); - if (code.getSize() > maxInitcodeSize) { - frame.popStackItems(getStackItemsConsumed()); - return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); - } if (!code.isValid()) { fail(frame); } else { - frame.decrementRemainingGas(cost); spawnChildMessage(frame, code, evm); frame.incrementRemainingGas(cost); From aef938964d0fc421517547cd58482ca53f94b8b8 Mon Sep 17 00:00:00 2001 From: Chaminda Divitotawela Date: Tue, 18 Jun 2024 11:42:50 +1000 Subject: [PATCH 18/22] fix: workflow permission to upload trivy sarif report (#7234) Trivy scan result upload to GitHub fails due to permission issue. Added permission security-events=write to the workflow file as a fix. Since workflow permission explicitly defined, it requires contents=read explicity set as well Signed-off-by: Chaminda Divitotawela --- .github/workflows/container-security-scan.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/container-security-scan.yml b/.github/workflows/container-security-scan.yml index 85065c828cc..f945d13220d 100644 --- a/.github/workflows/container-security-scan.yml +++ b/.github/workflows/container-security-scan.yml @@ -14,6 +14,9 @@ on: jobs: scan-sarif: runs-on: ubuntu-latest + permissions: + contents: read + security-events: write steps: - name: Checkout From 86b9c38015590923c3c24d0599a17385c55c7332 Mon Sep 17 00:00:00 2001 From: Chaminda Divitotawela Date: Wed, 19 Jun 2024 10:28:05 +1000 Subject: [PATCH 19/22] container verify GitHub workflow (#7239) Container verification step in release process automated with the container verify GitHub workflow. New workflow is triggered at the end of the release workflow which will check the release container images starts successfully. Verification test only checks container starts and reach the Ethereum main loop Signed-off-by: Chaminda Divitotawela --- .github/workflows/BesuContainerVerify.sh | 70 ++++++++++++++++++++++++ .github/workflows/container-verify.yml | 57 +++++++++++++++++++ .github/workflows/release.yml | 14 +++++ 3 files changed, 141 insertions(+) create mode 100644 .github/workflows/BesuContainerVerify.sh create mode 100644 .github/workflows/container-verify.yml diff --git a/.github/workflows/BesuContainerVerify.sh b/.github/workflows/BesuContainerVerify.sh new file mode 100644 index 00000000000..81537f32648 --- /dev/null +++ b/.github/workflows/BesuContainerVerify.sh @@ -0,0 +1,70 @@ +#!/bin/bash +## +## Copyright contributors to Hyperledger Besu. +## +## 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 +## + +CONTAINER_NAME=${CONTAINER_NAME:-besu} +VERSION=${VERSION} +TAG=${TAG} +CHECK_LATEST=${CHECK_LATEST} +RETRY=${RETRY:-10} +SLEEP=${SLEEP:-5} + +# Helper function to throw error +log_error() { + echo "::error $1" + exit 1 +} + +# Check container is in running state +_RUN_STATE=$(docker inspect --type=container -f={{.State.Status}} ${CONTAINER_NAME}) +if [[ "${_RUN_STATE}" != "running" ]] +then + log_error "container is not running" +fi + +# Check for specific log message in container logs to verify besu started +_SUCCESS=false +while [[ ${_SUCCESS} != "true" && $RETRY -gt 0 ]] +do + docker logs ${CONTAINER_NAME} | grep -q "Ethereum main loop is up" && { + _SUCCESS=true + continue + } + echo "Waiting for the besu to start. Remaining retries $RETRY ..." + RETRY=$(expr $RETRY - 1) + sleep $SLEEP +done + +# Log entry does not present after all retries, fail the script with a message +if [[ ${_SUCCESS} != "true" ]] +then + docker logs --tail=100 ${CONTAINER_NAME} + log_error "could not find the log message 'Ethereum main loop is up'" +else + echo "Besu container started and entered main loop" +fi + +# For the latest tag check the version match +if [[ ${TAG} == "latest" && ${CHECK_LATEST} == "true" ]] +then + _VERSION_IN_LOG=$(docker logs ${CONTAINER_NAME} | grep "#" | grep "Besu version" | cut -d " " -f 4 | sed 's/\s//g') + echo "Extracted version from logs [$_VERSION_IN_LOG]" + if [[ "$_VERSION_IN_LOG" != "${VERSION}" ]] + then + log_error "version [$_VERSION_IN_LOG] extracted from container logs does not match the expected version [${VERSION}]" + else + echo "Latest Besu container version matches" + fi +fi diff --git a/.github/workflows/container-verify.yml b/.github/workflows/container-verify.yml new file mode 100644 index 00000000000..c8f5726af75 --- /dev/null +++ b/.github/workflows/container-verify.yml @@ -0,0 +1,57 @@ +name: container verify + +on: + workflow_dispatch: + inputs: + version: + description: 'Besu version' + required: true + verify-latest-version: + description: 'Check latest container version' + required: false + type: choice + default: "true" + options: + - "true" + - "false" + +jobs: + verify: + timeout-minutes: 4 + strategy: + matrix: + combination: + - tag: ${{ inputs.version }} + platform: '' + runner: ubuntu-latest + - tag: ${{ inputs.version }}-amd64 + platform: 'linux/amd64' + runner: ubuntu-latest + - tag: latest + platform: '' + runner: ubuntu-latest + - tag: ${{ inputs.version }}-arm64 + platform: '' + runner: besu-arm64 + runs-on: ${{ matrix.combination.runner }} + env: + CONTAINER_NAME: besu-check + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + - name: Start container + run: | + PLATFORM_OPT="" + [[ x${{ matrix.combination.platform }} != 'x' ]] && PLATFORM_OPT="--platform ${{ matrix.combination.platform }}" + docker run -d $PLATFORM_OPT --name ${{ env.CONTAINER_NAME }} hyperledger/besu:${{ matrix.combination.tag }} + + - name: Verify besu container + run: bash .github/workflows/BesuContainerVerify.sh + env: + TAG: ${{ matrix.combination.tag }} + VERSION: ${{ inputs.version }} + CHECK_LATEST: ${{ inputs.verify-latest-version }} + + - name: Stop container + run: docker stop ${{ env.CONTAINER_NAME }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1f8fb2fc5e..2aff0bb48e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -265,3 +265,17 @@ jobs: run: ./gradlew "-Prelease.releaseVersion=${{ github.event.release.name }}" "-PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }}" dockerUploadRelease - name: Docker manifest run: ./gradlew "-Prelease.releaseVersion=${{ github.event.release.name }}" "-PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }}" manifestDockerRelease + + verifyContainer: + needs: dockerPromoteX64 + runs-on: ubuntu-22.04 + permissions: + contents: read + actions: write + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Trigger container verify + run: echo '{"version":"${{ github.event.release.name }}","verify-latest-version":"true"}' | gh workflow run container-verify.yml --json + env: + GH_TOKEN: ${{ github.token }} From f5e5ad53e770715f5438d3e9c74a77cfe8cfc97b Mon Sep 17 00:00:00 2001 From: Stefan Pingel <16143240+pinges@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:54:20 +1000 Subject: [PATCH 20/22] Investigate chain halts when syncing (#7162) Fix some reasons for chain download halts when syncing Signed-off-by: stefan.pingel@consensys.net Signed-off-by: Stefan Pingel <16143240+pinges@users.noreply.github.com> Co-authored-by: Sally MacFarlane --- CHANGELOG.md | 2 + .../MergeBesuControllerBuilder.java | 2 +- .../besu/ethereum/eth/manager/EthPeers.java | 51 ++++++++++++------- .../ethereum/eth/manager/EthScheduler.java | 5 -- .../RetryingGetAccountRangeFromPeerTask.java | 6 ++- .../task/AbstractRetryingPeerTask.java | 11 ++-- .../sync/fastsync/PivotBlockRetriever.java | 2 +- .../eth/sync/fastsync/SyncTargetManager.java | 21 +++++--- .../fullsync/BetterSyncTargetEvaluator.java | 2 +- .../eth/sync/range/RangeHeadersFetcher.java | 38 +++++++++++--- .../eth/sync/range/SyncTargetRangeSource.java | 23 ++++++--- .../eth/sync/tasks/CompleteBlocksTask.java | 2 +- .../tasks/DownloadHeaderSequenceTask.java | 2 +- .../sync/tasks/GetReceiptsForHeadersTask.java | 2 +- .../BetterSyncTargetEvaluatorTest.java | 2 +- .../connections/AbstractPeerConnection.java | 23 +++++---- .../rlpx/wire/messages/DisconnectMessage.java | 1 + 17 files changed, 130 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3724ed84a43..bac2d7c555d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ ### Bug fixes - Validation errors ignored in accounts-allowlist and empty list [#7138](https://github.com/hyperledger/besu/issues/7138) - Fix "Invalid block detected" for BFT chains using Bonsai DB [#7204](https://github.com/hyperledger/besu/pull/7204) +- Fix "Could not confirm best peer had pivot block" [#7109](https://github.com/hyperledger/besu/issues/7109) +- Fix "Chain Download Halt" [#6884](https://github.com/hyperledger/besu/issues/6884) ## 24.6.0 diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java index e391f920ceb..1e8da674a50 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java @@ -104,7 +104,7 @@ protected EthProtocolManager createEthProtocolManager( var mergeBestPeerComparator = new TransitionBestPeerComparator( genesisConfigOptions.getTerminalTotalDifficulty().map(Difficulty::of).orElseThrow()); - ethPeers.setBestChainComparator(mergeBestPeerComparator); + ethPeers.setBestPeerComparator(mergeBestPeerComparator); mergeContext.observeNewIsPostMergeState(mergeBestPeerComparator); Optional filterToUse = Optional.of(new MergePeerFilter()); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java index 2a4469220e0..bef7a1a038f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java @@ -198,9 +198,12 @@ private boolean registerDisconnect(final EthPeer peer, final PeerConnection conn peer.handleDisconnect(); abortPendingRequestsAssignedToDisconnectedPeers(); if (peer.getReputation().getScore() > USEFULL_PEER_SCORE_THRESHOLD) { - LOG.debug("Disconnected USEFULL peer {}", peer); + LOG.atDebug().setMessage("Disconnected USEFULL peer {}").addArgument(peer).log(); } else { - LOG.debug("Disconnected EthPeer {}", peer.getLoggableId()); + LOG.atDebug() + .setMessage("Disconnected EthPeer {}") + .addArgument(peer.getLoggableId()) + .log(); } } } @@ -318,11 +321,11 @@ public Stream streamAvailablePeers() { public Stream streamBestPeers() { return streamAvailablePeers() .filter(EthPeer::isFullyValidated) - .sorted(getBestChainComparator().reversed()); + .sorted(getBestPeerComparator().reversed()); } public Optional bestPeer() { - return streamAvailablePeers().max(getBestChainComparator()); + return streamAvailablePeers().max(getBestPeerComparator()); } public Optional bestPeerWithHeightEstimate() { @@ -331,15 +334,15 @@ public Optional bestPeerWithHeightEstimate() { } public Optional bestPeerMatchingCriteria(final Predicate matchesCriteria) { - return streamAvailablePeers().filter(matchesCriteria).max(getBestChainComparator()); + return streamAvailablePeers().filter(matchesCriteria).max(getBestPeerComparator()); } - public void setBestChainComparator(final Comparator comparator) { + public void setBestPeerComparator(final Comparator comparator) { LOG.info("Updating the default best peer comparator"); bestPeerComparator = comparator; } - public Comparator getBestChainComparator() { + public Comparator getBestPeerComparator() { return bestPeerComparator; } @@ -394,8 +397,7 @@ public boolean shouldConnect(final Peer peer, final boolean inbound) { public void disconnectWorstUselessPeer() { streamAvailablePeers() - .sorted(getBestChainComparator()) - .findFirst() + .min(getBestPeerComparator()) .ifPresent( peer -> { LOG.atDebug() @@ -551,10 +553,11 @@ private boolean addPeerToEthPeers(final EthPeer peer) { if (!randomPeerPriority) { // Disconnect if too many peers if (!canExceedPeerLimits(id) && peerCount() >= peerUpperBound) { - LOG.trace( - "Too many peers. Disconnect connection: {}, max connections {}", - connection, - peerUpperBound); + LOG.atTrace() + .setMessage("Too many peers. Disconnect connection: {}, max connections {}") + .addArgument(connection) + .addArgument(peerUpperBound) + .log(); connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); return false; } @@ -562,18 +565,28 @@ private boolean addPeerToEthPeers(final EthPeer peer) { if (connection.inboundInitiated() && !canExceedPeerLimits(id) && remoteConnectionLimitReached()) { - LOG.trace( - "Too many remotely-initiated connections. Disconnect incoming connection: {}, maxRemote={}", - connection, - maxRemotelyInitiatedConnections); + LOG.atTrace() + .setMessage( + "Too many remotely-initiated connections. Disconnect incoming connection: {}, maxRemote={}") + .addArgument(connection) + .addArgument(maxRemotelyInitiatedConnections) + .log(); connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); return false; } final boolean added = (completeConnections.putIfAbsent(id, peer) == null); if (added) { - LOG.trace("Added peer {} with connection {} to completeConnections", id, connection); + LOG.atTrace() + .setMessage("Added peer {} with connection {} to completeConnections") + .addArgument(id) + .addArgument(connection) + .log(); } else { - LOG.trace("Did not add peer {} with connection {} to completeConnections", id, connection); + LOG.atTrace() + .setMessage("Did not add peer {} with connection {} to completeConnections") + .addArgument(id) + .addArgument(connection) + .log(); } return added; } else { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java index 717a0bb24ef..dcb66696662 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java @@ -47,7 +47,6 @@ public class EthScheduler { private static final Logger LOG = LoggerFactory.getLogger(EthScheduler.class); - private final Duration defaultTimeout = Duration.ofSeconds(5); private final AtomicBoolean stopped = new AtomicBoolean(false); private final CountDownLatch shutdown = new CountDownLatch(1); private static final int TX_WORKER_CAPACITY = 1_000; @@ -219,10 +218,6 @@ public CompletableFuture scheduleBlockCreationTask(final Runnable task) { return CompletableFuture.runAsync(task, blockCreationExecutor); } - public CompletableFuture timeout(final EthTask task) { - return timeout(task, defaultTimeout); - } - public CompletableFuture timeout(final EthTask task, final Duration timeout) { final CompletableFuture future = task.run(); final CompletableFuture result = timeout(future, timeout); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java index 103172b0870..36d6b75e6ee 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java @@ -30,6 +30,7 @@ public class RetryingGetAccountRangeFromPeerTask extends AbstractRetryingPeerTask { + public static final int MAX_RETRIES = 4; private final EthContext ethContext; private final Bytes32 startKeyHash; private final Bytes32 endKeyHash; @@ -43,7 +44,10 @@ private RetryingGetAccountRangeFromPeerTask( final BlockHeader blockHeader, final MetricsSystem metricsSystem) { super( - ethContext, 4, data -> data.accounts().isEmpty() && data.proofs().isEmpty(), metricsSystem); + ethContext, + MAX_RETRIES, + data -> data.accounts().isEmpty() && data.proofs().isEmpty(), + metricsSystem); this.ethContext = ethContext; this.startKeyHash = startKeyHash; this.endKeyHash = endKeyHash; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java index cf48d69847d..46c3cf1b226 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java @@ -122,15 +122,18 @@ protected void handleTaskError(final Throwable error) { () -> ethContext .getScheduler() + // wait for a new peer for up to 5 seconds .timeout(waitTask, Duration.ofSeconds(5)) + // execute the task again .whenComplete((r, t) -> executeTaskTimed())); return; } - LOG.debug( - "Retrying after recoverable failure from peer task {}: {}", - this.getClass().getSimpleName(), - cause.getMessage()); + LOG.atDebug() + .setMessage("Retrying after recoverable failure from peer task {}: {}") + .addArgument(this.getClass().getSimpleName()) + .addArgument(cause.getMessage()) + .log(); // Wait before retrying on failure executeSubTask( () -> diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java index 6ddd7db9012..d8ff3627101 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java @@ -39,7 +39,7 @@ public class PivotBlockRetriever { private static final Logger LOG = LoggerFactory.getLogger(PivotBlockRetriever.class); - public static final int MAX_QUERY_RETRIES_PER_PEER = 4; + public static final int MAX_QUERY_RETRIES_PER_PEER = 5; private static final int DEFAULT_MAX_PIVOT_BLOCK_RESETS = 250; private static final int SUSPICIOUS_NUMBER_OF_RETRIES = 5; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java index c19e06f993b..b587b18f264 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.plugin.services.MetricsSystem; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -41,6 +42,7 @@ public class SyncTargetManager extends AbstractSyncTargetManager { private static final Logger LOG = LoggerFactory.getLogger(SyncTargetManager.class); + private static final int SECONDS_PER_REQUEST = 6; // 5s per request + 1s wait between retries private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final ProtocolSchedule protocolSchedule; @@ -93,7 +95,9 @@ protected CompletableFuture> selectBestAvailableSyncTarget() { return completedFuture(Optional.empty()); } else { final EthPeer bestPeer = maybeBestPeer.get(); - if (bestPeer.chainState().getEstimatedHeight() < pivotBlockHeader.getNumber()) { + // Do not check the best peers estimated height if we are doing PoS + if (!protocolSchedule.getByBlockHeader(pivotBlockHeader).isPoS() + && bestPeer.chainState().getEstimatedHeight() < pivotBlockHeader.getNumber()) { LOG.info( "Best peer {} has chain height {} below pivotBlock height {}. Waiting for better peers. Current {} of max {}", maybeBestPeer.map(EthPeer::getLoggableId).orElse("none"), @@ -121,7 +125,8 @@ private CompletableFuture> confirmPivotBlockHeader(final EthPe task.assignPeer(bestPeer); return ethContext .getScheduler() - .timeout(task) + // Task is a retrying task. Make sure that the timeout is long enough to allow for retries. + .timeout(task, Duration.ofSeconds(MAX_QUERY_RETRIES_PER_PEER * SECONDS_PER_REQUEST + 2)) .thenCompose( result -> { if (peerHasDifferentPivotBlock(result)) { @@ -147,11 +152,13 @@ private CompletableFuture> confirmPivotBlockHeader(final EthPe }) .exceptionally( error -> { - LOG.debug( - "Could not confirm best peer {} had pivot block {}", - bestPeer.getLoggableId(), - pivotBlockHeader.getNumber(), - error); + LOG.atDebug() + .setMessage("Could not confirm best peer {} had pivot block {}, {}") + .addArgument(bestPeer.getLoggableId()) + .addArgument(pivotBlockHeader.getNumber()) + .addArgument(error) + .log(); + bestPeer.disconnect(DisconnectReason.USELESS_PEER_CANNOT_CONFIRM_PIVOT_BLOCK); return Optional.empty(); }); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java index 68aa5ec95f8..1df59c654c1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java @@ -40,7 +40,7 @@ public boolean shouldSwitchSyncTarget(final EthPeer currentSyncTarget) { return maybeBestPeer .map( bestPeer -> { - if (ethPeers.getBestChainComparator().compare(bestPeer, currentSyncTarget) <= 0) { + if (ethPeers.getBestPeerComparator().compare(bestPeer, currentSyncTarget) <= 0) { // Our current target is better or equal to the best peer return false; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java index 8ee1dc28a43..e73c5aa9f5f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java @@ -68,6 +68,10 @@ public RangeHeadersFetcher( public CompletableFuture> getNextRangeHeaders( final EthPeer peer, final BlockHeader previousRangeHeader) { + LOG.atTrace() + .setMessage("Requesting next range headers from peer {}") + .addArgument(peer.getLoggableId()) + .log(); final int skip = syncConfig.getDownloaderChainSegmentSize() - 1; final int maximumHeaderRequestSize = syncConfig.getDownloaderHeaderRequestSize(); final long previousRangeNumber = previousRangeHeader.getNumber(); @@ -78,11 +82,20 @@ public CompletableFuture> getNextRangeHeaders( final BlockHeader targetHeader = finalRangeHeader.get(); final long blocksUntilTarget = targetHeader.getNumber() - previousRangeNumber; if (blocksUntilTarget <= 0) { + LOG.atTrace() + .setMessage("Requesting next range headers: no blocks until target: {}") + .addArgument(blocksUntilTarget) + .log(); return completedFuture(emptyList()); } final long maxHeadersToRequest = blocksUntilTarget / (skip + 1); additionalHeaderCount = (int) Math.min(maxHeadersToRequest, maximumHeaderRequestSize); if (additionalHeaderCount == 0) { + LOG.atTrace() + .setMessage( + "Requesting next range headers: additional header count is 0, blocks until target: {}") + .addArgument(blocksUntilTarget) + .log(); return completedFuture(singletonList(targetHeader)); } } else { @@ -97,11 +110,12 @@ private CompletableFuture> requestHeaders( final BlockHeader referenceHeader, final int headerCount, final int skip) { - LOG.trace( - "Requesting {} range headers, starting from {}, {} blocks apart", - headerCount, - referenceHeader.getNumber(), - skip); + LOG.atTrace() + .setMessage("Requesting {} range headers, starting from {}, {} blocks apart") + .addArgument(headerCount) + .addArgument(referenceHeader.getNumber()) + .addArgument(skip) + .log(); return GetHeadersFromPeerByHashTask.startingAtHash( protocolSchedule, ethContext, @@ -114,7 +128,19 @@ private CompletableFuture> requestHeaders( .assignPeer(peer) .run() .thenApply(PeerTaskResult::getResult) - .thenApply(headers -> stripExistingRangeHeaders(referenceHeader, headers)); + .thenApply( + headers -> { + if (headers.size() < headerCount) { + LOG.atTrace() + .setMessage( + "Peer {} returned fewer headers than requested. Expected: {}, Actual: {}") + .addArgument(peer) + .addArgument(headerCount) + .addArgument(headers.size()) + .log(); + } + return stripExistingRangeHeaders(referenceHeader, headers); + }); } private List stripExistingRangeHeaders( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java index 8c6186448ed..05d7899226b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java @@ -40,6 +40,7 @@ public class SyncTargetRangeSource implements Iterator { private static final Logger LOG = LoggerFactory.getLogger(SyncTargetRangeSource.class); private static final Duration RETRY_DELAY_DURATION = Duration.ofSeconds(2); + public static final int DEFAULT_TIME_TO_WAIT_IN_SECONDS = 6; private final RangeHeadersFetcher fetcher; private final SyncTargetChecker syncTargetChecker; @@ -70,7 +71,7 @@ public SyncTargetRangeSource( peer, commonAncestor, retriesPermitted, - Duration.ofSeconds(5), + Duration.ofSeconds(DEFAULT_TIME_TO_WAIT_IN_SECONDS), terminationCondition); } @@ -153,7 +154,7 @@ private SyncTargetRange getRangeFromPendingRequest() { if (retryCount >= retriesPermitted) { LOG.atDebug() .setMessage( - "Disconnecting target peer for providing useless or empty range header: {}.") + "Disconnecting target peer {} for providing useless or empty range headers.") .addArgument(peer) .log(); peer.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER_USELESS_RESPONSES); @@ -169,12 +170,20 @@ private SyncTargetRange getRangeFromPendingRequest() { } catch (final InterruptedException e) { LOG.trace("Interrupted while waiting for new range headers", e); return null; - } catch (final ExecutionException e) { - LOG.debug("Failed to retrieve new range headers", e); - this.pendingRequests = Optional.empty(); + } catch (final ExecutionException | TimeoutException e) { + if (e instanceof ExecutionException) { + this.pendingRequests = Optional.empty(); + } retryCount++; - return null; - } catch (final TimeoutException e) { + if (retryCount >= retriesPermitted) { + LOG.atDebug() + .setMessage( + "Disconnecting target peer {} for not providing useful range headers: Exception: {}.") + .addArgument(peer) + .addArgument(e) + .log(); + peer.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER_USELESS_RESPONSES); + } return null; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java index bdd59260e72..524851af5f6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java @@ -51,7 +51,7 @@ public class CompleteBlocksTask extends AbstractRetryingPeerTask> { private static final Logger LOG = LoggerFactory.getLogger(CompleteBlocksTask.class); private static final int MIN_SIZE_INCOMPLETE_LIST = 1; - private static final int DEFAULT_RETRIES = 4; + private static final int DEFAULT_RETRIES = 5; private final EthContext ethContext; private final ProtocolSchedule protocolSchedule; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java index 3049d3f1ac2..8ca376902e4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java @@ -56,7 +56,7 @@ */ public class DownloadHeaderSequenceTask extends AbstractRetryingPeerTask> { private static final Logger LOG = LoggerFactory.getLogger(DownloadHeaderSequenceTask.class); - private static final int DEFAULT_RETRIES = 4; + private static final int DEFAULT_RETRIES = 5; private final EthContext ethContext; private final ProtocolContext protocolContext; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java index 699bf9c4b17..58c4d3a7afa 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java @@ -42,7 +42,7 @@ public class GetReceiptsForHeadersTask extends AbstractRetryingPeerTask>> { private static final Logger LOG = LoggerFactory.getLogger(GetReceiptsForHeadersTask.class); - private static final int DEFAULT_RETRIES = 4; + private static final int DEFAULT_RETRIES = 5; private final EthContext ethContext; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java index 9acf45965d6..0bc98aaa9ad 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java @@ -49,7 +49,7 @@ public class BetterSyncTargetEvaluatorTest { @BeforeEach public void setupMocks() { - when(ethPeers.getBestChainComparator()).thenReturn(EthPeers.HEAVIEST_CHAIN); + when(ethPeers.getBestPeerComparator()).thenReturn(EthPeers.HEAVIEST_CHAIN); } @Test diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java index 7fcc02687db..59cf3490566 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java @@ -185,15 +185,20 @@ public void terminateConnection(final DisconnectReason reason, final boolean pee @Override public void disconnect(final DisconnectReason reason) { if (disconnected.compareAndSet(false, true)) { - connectionEventDispatcher.dispatchDisconnect(this, reason, false); - doSend(null, DisconnectMessage.create(reason)); - LOG.atDebug() - .setMessage("Disconnecting connection {}, peer {} reason {}") - .addArgument(this.hashCode()) - .addArgument(peer.getLoggableId()) - .addArgument(reason) - .log(); - closeConnection(); + try { + // send the disconnect message first, in case the dispatchDisconnect throws an exception + doSend(null, DisconnectMessage.create(reason)); + LOG.atDebug() + .setMessage("Disconnecting connection {}, peer {} reason {}") + .addArgument(this.hashCode()) + .addArgument(peer.getLoggableId()) + .addArgument(reason) + .log(); + connectionEventDispatcher.dispatchDisconnect(this, reason, false); + } finally { + // always close the connection + closeConnection(); + } } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java index 66337903358..87df88c40ea 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java @@ -131,6 +131,7 @@ public enum DisconnectReason { USELESS_PEER_MISMATCHED_PIVOT_BLOCK((byte) 0x03, "Mismatched pivot block"), USELESS_PEER_FAILED_TO_RETRIEVE_CHAIN_STATE( (byte) 0x03, "Failed to retrieve header for chain state"), + USELESS_PEER_CANNOT_CONFIRM_PIVOT_BLOCK((byte) 0x03, "Peer failed to confirm pivot block"), USELESS_PEER_BY_REPUTATION((byte) 0x03, "Lowest reputation score"), USELESS_PEER_BY_CHAIN_COMPARATOR((byte) 0x03, "Lowest by chain height comparator"), TOO_MANY_PEERS((byte) 0x04), From 3026268f1542da4d329cf016e81b6455d82ddc61 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Thu, 20 Jun 2024 10:20:40 -0600 Subject: [PATCH 21/22] Check for EOFCreate subcontainer rules (#7232) Check and test for the unused container rule, and only returncontract targets can have truncated data rule. Also test the other subcontainer rules in unit tests. Signed-off-by: Danno Ferrin --- .../ethereum/eof/EOFReferenceTestTools.java | 18 + .../besu/evm/code/CodeV1Validation.java | 12 + .../hyperledger/besu/evm/code/EOFLayout.java | 10 + .../besu/evm/code/CodeFactoryTest.java | 438 +++++++++++++++++- .../besu/evm/code/EOFLayoutTest.java | 16 +- 5 files changed, 480 insertions(+), 14 deletions(-) diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java index cf7ce66b076..22bf3839010 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java @@ -72,6 +72,24 @@ public class EOFReferenceTestTools { // TXCREATE still in tests, but has been removed params.ignore("EOF1_undefined_opcodes_186"); + + // embedded containers rules changed + params.ignore("EOF1_embedded_container"); + + // truncated data is only allowed in embedded containers + params.ignore("ori/validInvalid-Prague\\[validInvalid_48\\]"); + params.ignore("efExample/validInvalid-Prague\\[validInvalid_1\\]"); + params.ignore("efValidation/EOF1_truncated_section-Prague\\[EOF1_truncated_section_3\\]"); + params.ignore("efValidation/EOF1_truncated_section-Prague\\[EOF1_truncated_section_4\\]"); + params.ignore("EIP3540/validInvalid-Prague\\[validInvalid_2\\]"); + params.ignore("EIP3540/validInvalid-Prague\\[validInvalid_3\\]"); + + // Orphan containers are no longer allowed + params.ignore("efValidation/EOF1_returncontract_valid-Prague\\[EOF1_returncontract_valid_1\\]"); + params.ignore("efValidation/EOF1_returncontract_valid-Prague\\[EOF1_returncontract_valid_2\\]"); + params.ignore("efValidation/EOF1_eofcreate_valid-Prague\\[EOF1_eofcreate_valid_1\\]"); + params.ignore("efValidation/EOF1_eofcreate_valid-Prague\\[EOF1_eofcreate_valid_2\\]"); + params.ignore("efValidation/EOF1_section_order-Prague\\[EOF1_section_order_6\\]"); } private EOFReferenceTestTools() { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java index b52f1dfebbd..c940a7d930b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java @@ -67,6 +67,8 @@ private CodeV1Validation() { * @param layout The parsed EOFLayout of the code * @return either null, indicating no error, or a String describing the validation error. */ + @SuppressWarnings( + "ReferenceEquality") // comparison `container != layout` is deliberate and correct public static String validate(final EOFLayout layout) { Queue workList = new ArrayDeque<>(layout.getSubcontainerCount()); workList.add(layout); @@ -74,6 +76,16 @@ public static String validate(final EOFLayout layout) { while (!workList.isEmpty()) { EOFLayout container = workList.poll(); workList.addAll(List.of(container.subContainers())); + if (container != layout && container.containerMode().get() == null) { + return "Unreferenced container #" + layout.indexOfSubcontainer(container); + } + if (container.containerMode().get() != RUNTIME + && container.data().size() != container.dataLength()) { + return "Incomplete data section " + + (container == layout + ? " at root" + : " in container #" + layout.indexOfSubcontainer(container)); + } final String codeValidationError = CodeV1Validation.validateCode(container); if (codeValidationError != null) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java index 95ad7f95dd2..1cce49b4531 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java @@ -405,6 +405,16 @@ public EOFLayout getSubcontainer(final int i) { return subContainers[i]; } + /** + * Finds the first instance of the subcontainer in the list of container, or -1 if not present + * + * @param container the container to search for + * @return the index of the container, or -1 if not found. + */ + public int indexOfSubcontainer(final EOFLayout container) { + return Arrays.asList(subContainers).indexOf(container); + } + /** * Is valid. * diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java index d3335d077cf..75ec9eb01bf 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java @@ -15,10 +15,10 @@ package org.hyperledger.besu.evm.code; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.EOFTestConstants.bytesFromPrettyPrint; import org.hyperledger.besu.evm.Code; -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; class CodeFactoryTest { @@ -178,13 +178,445 @@ void invalidCodeUnknownSectionId3() { invalidCode("0xEF0001010002030004006000AABBCCDD"); } + @Test + void invalidDataTruncated() { + invalidCode("EF0001 010004 0200010001 040003 00 00800000 FE BEEF", "Incomplete data section"); + } + + @Test + void validComboEOFCreateReturnContract() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0032 # Sub container 0, 50 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0006 # Code section 0 , 6 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + ee00 # [4] RETURNCONTRACT(0) + # Subcontainer 0.0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0.0 ends + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboReturnContactStop() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboReturnContactReturn() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + f3 # [4] RETURN + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboEOFCreateRevert() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + fd # [4] REVERT + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboReturncontractRevert() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + fd # [4] REVERT + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void invalidComboEOFCreateStop() { + invalidCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """, + "STOP is only a valid opcode in containers used for runtime operations."); + } + + @Test + void invalidComboEOFCretateReturn() { + invalidCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + f3 # [4] RETURN + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """, + "RETURN is only a valid opcode in containers used for runtime operations."); + } + + @Test + void invalidReturncontractReturncontract() { + invalidCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0032 # Sub container 0, 50 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0006 # Code section 0 , 6 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + ee00 # [4] RETURNCONTRACT(0) + # Subcontainer 0.0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0.0 ends + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """, + "RETURNCONTRACT is only a valid opcode in containers used for initcode"); + } + + // // valid subcontainer references + // // invalid subcontainer references + // + // { + // "EF0001 010004 0200010001 040003 00 00800000 FE BEEF", + // "Incomplete data section", + // "Incomplete data section", + // 1 + // }, + // + + private static void validCode(final String str) { + Code code = CodeFactory.createCode(bytesFromPrettyPrint(str), 1); + assertThat(code.isValid()).isTrue(); + } + + private static void invalidCode(final String str, final String error) { + Code code = CodeFactory.createCode(bytesFromPrettyPrint(str), 1); + assertThat(code.isValid()).isFalse(); + assertThat(((CodeInvalid) code).getInvalidReason()).contains(error); + } + private static void invalidCode(final String str) { - Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1); + Code code = CodeFactory.createCode(bytesFromPrettyPrint(str), 1); assertThat(code.isValid()).isFalse(); } private static void invalidCode(final String str, final boolean legacy) { - Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1, legacy, false); + Code code = CodeFactory.createCode(bytesFromPrettyPrint(str), 1, legacy, false); assertThat(code.isValid()).isFalse(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java index 94f1d9598e0..5324d7adf05 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java @@ -143,16 +143,10 @@ public static Collection containersWithFormatErrors() { }, { "EF0001 010004 0200010001 040003 00 00800000 FE DEADBEEF", - "Incomplete data section", + "Excess data section", "Dangling data after end of all sections", 1 }, - { - "EF0001 010004 0200010001 040003 00 00800000 FE BEEF", - "Incomplete data section", - "Incomplete data section", - 1 - }, { "EF0001 0200010001 040001 00 FE DA", "type section missing", @@ -343,9 +337,9 @@ public static Collection subContainers() { @ParameterizedTest(name = "{1}") @MethodSource({ - // "correctContainers", - // "containersWithFormatErrors", - // "typeSectionTests", + "correctContainers", + "containersWithFormatErrors", + "typeSectionTests", "subContainers" }) void test( @@ -354,7 +348,7 @@ void test( final String failureReason, final int expectedVersion) { final Bytes container = Bytes.fromHexString(containerString.replaceAll("[^a-fxA-F0-9]", "")); - final EOFLayout layout = EOFLayout.parseEOF(container); + final EOFLayout layout = EOFLayout.parseEOF(container, true); assertThat(layout.version()).isEqualTo(expectedVersion); assertThat(layout.invalidReason()).isEqualTo(failureReason); From 8c04d0ae36065617012a5d9857086fbab0962c86 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Fri, 21 Jun 2024 12:56:43 +1000 Subject: [PATCH 22/22] Remove deprecation message for `--Xp2p-peer-lower-bound` (#7247) Signed-off-by: Gabriel-Trintinalia --- CHANGELOG.md | 1 + .../org/hyperledger/besu/cli/BesuCommand.java | 4 ---- .../cli/options/unstable/NetworkingOptions.java | 15 +-------------- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac2d7c555d..a082a5b007b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Next Release ### Breaking Changes +- `Xp2p-peer-lower-bound` has been removed. [#7247](https://github.com/hyperledger/besu/pull/7247) ### Additions and Improvements - Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 9e612fea3b2..4262ff9644e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -20,7 +20,6 @@ import static java.util.Collections.singletonList; import static org.hyperledger.besu.cli.DefaultCommandValues.getDefaultBesuDataPath; import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; -import static org.hyperledger.besu.cli.options.unstable.NetworkingOptions.PEER_LOWER_BOUND_FLAG; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; import static org.hyperledger.besu.cli.util.CommandLineUtils.isOptionSet; import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; @@ -1659,9 +1658,6 @@ private void ensureValidPeerBoundParams() { maxPeers = p2PDiscoveryOptionGroup.maxPeers; final Boolean isLimitRemoteWireConnectionsEnabled = p2PDiscoveryOptionGroup.isLimitRemoteWireConnectionsEnabled; - if (isOptionSet(commandLine, PEER_LOWER_BOUND_FLAG)) { - logger.warn(PEER_LOWER_BOUND_FLAG + " is deprecated and will be removed soon."); - } if (isLimitRemoteWireConnectionsEnabled) { final float fraction = Fraction.fromPercentage(p2PDiscoveryOptionGroup.maxRemoteConnectionsPercentage) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java index a66bb6fb618..aed7b079d87 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NetworkingOptions.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.cli.options.unstable; -import org.hyperledger.besu.cli.DefaultCommandValues; import org.hyperledger.besu.cli.options.CLIOptions; import org.hyperledger.besu.cli.options.OptionParser; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; @@ -27,9 +26,6 @@ /** The Networking Cli options. */ public class NetworkingOptions implements CLIOptions { - /** The constant PEER_LOWER_BOUND_FLAG */ - public static final String PEER_LOWER_BOUND_FLAG = "--Xp2p-peer-lower-bound"; - private final String INITIATE_CONNECTIONS_FREQUENCY_FLAG = "--Xp2p-initiate-connections-frequency"; private final String CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_FLAG = @@ -77,13 +73,6 @@ public class NetworkingOptions implements CLIOptions { description = "Whether to enable filtering of peers based on the ENR field ForkId)") private final Boolean filterOnEnrForkId = NetworkingConfiguration.DEFAULT_FILTER_ON_ENR_FORK_ID; - @CommandLine.Option( - hidden = true, - names = PEER_LOWER_BOUND_FLAG, - description = - "(Deprecated) Lower bound on the target number of P2P connections (default: ${DEFAULT-VALUE})") - private final Integer peerLowerBoundConfig = DefaultCommandValues.DEFAULT_MAX_PEERS; - private NetworkingOptions() {} /** @@ -130,9 +119,7 @@ public List getCLIOptions() { CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_FLAG, OptionParser.format(checkMaintainedConnectionsFrequencySec), INITIATE_CONNECTIONS_FREQUENCY_FLAG, - OptionParser.format(initiateConnectionsFrequencySec), - PEER_LOWER_BOUND_FLAG, - OptionParser.format((peerLowerBoundConfig))); + OptionParser.format(initiateConnectionsFrequencySec)); if (dnsDiscoveryServerOverride.isPresent()) { retval.add(DNS_DISCOVERY_SERVER_OVERRIDE_FLAG);