diff --git a/testing/src/main/java/net/consensys/linea/testing/ExecutionEnvironment.java b/testing/src/main/java/net/consensys/linea/testing/ExecutionEnvironment.java index 0c47cbe0d0..29ea5117c7 100644 --- a/testing/src/main/java/net/consensys/linea/testing/ExecutionEnvironment.java +++ b/testing/src/main/java/net/consensys/linea/testing/ExecutionEnvironment.java @@ -15,14 +15,21 @@ package net.consensys.linea.testing; +import static net.consensys.linea.zktracer.module.constants.GlobalConstants.*; + import java.math.BigInteger; import java.util.Optional; import java.util.OptionalLong; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecFactory; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -35,6 +42,23 @@ public class ExecutionEnvironment { static GenesisConfigFile GENESIS_CONFIG = GenesisConfigFile.fromSource(GenesisConfigFile.class.getResource("/linea.json")); + public static BlockHeaderBuilder getLineaBlockHeaderBuilder( + Optional parentBlockHeader) { + BlockHeaderBuilder blockHeaderBuilder = + parentBlockHeader.isPresent() + ? BlockHeaderBuilder.fromHeader(parentBlockHeader.get()) + .number(parentBlockHeader.get().getNumber() + 1) + .timestamp(parentBlockHeader.get().getTimestamp() + 100) + .parentHash(parentBlockHeader.get().getHash()) + .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) + : BlockHeaderBuilder.createDefault(); + + return blockHeaderBuilder + .baseFee(Wei.of(LINEA_BASE_FEE)) + .gasLimit(LINEA_BLOCK_GAS_LIMIT) + .difficulty(Difficulty.of(LINEA_DIFFICULTY)); + } + public static ProtocolSpec getProtocolSpec(BigInteger chainId) { BadBlockManager badBlockManager = new BadBlockManager(); diff --git a/testing/src/main/java/net/consensys/linea/testing/MultiBlockExecutionEnvironment.java b/testing/src/main/java/net/consensys/linea/testing/MultiBlockExecutionEnvironment.java new file mode 100644 index 0000000000..650a3e645b --- /dev/null +++ b/testing/src/main/java/net/consensys/linea/testing/MultiBlockExecutionEnvironment.java @@ -0,0 +1,98 @@ +/* + * Copyright Consensys Software Inc. + * + * 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 net.consensys.linea.testing; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import lombok.Builder; +import lombok.Singular; +import lombok.extern.slf4j.Slf4j; +import net.consensys.linea.blockcapture.snapshots.*; +import org.hyperledger.besu.ethereum.core.*; + +@Builder +@Slf4j +public class MultiBlockExecutionEnvironment { + @Singular("addAccount") + private final List accounts; + + private final List blocks; + + public static class MultiBlockExecutionEnvironmentBuilder { + + private List blocks = new ArrayList<>(); + + public MultiBlockExecutionEnvironmentBuilder addBlock(List transactions) { + BlockHeaderBuilder blockHeaderBuilder = + this.blocks.isEmpty() + ? ExecutionEnvironment.getLineaBlockHeaderBuilder(Optional.empty()) + : ExecutionEnvironment.getLineaBlockHeaderBuilder( + Optional.of(this.blocks.getLast().header().toBlockHeader())); + blockHeaderBuilder.coinbase(ToyExecutionEnvironmentV2.DEFAULT_COINBASE_ADDRESS); + BlockBody blockBody = new BlockBody(transactions, Collections.emptyList()); + this.blocks.add(BlockSnapshot.of(blockHeaderBuilder.buildBlockHeader(), blockBody)); + + return this; + } + } + + public void run() { + ReplayExecutionEnvironment.builder() + .build() + .replay(ToyExecutionEnvironmentV2.CHAIN_ID, this.buildConflationSnapshot()); + } + + private ConflationSnapshot buildConflationSnapshot() { + List accountSnapshots = + accounts.stream() + .map( + toyAccount -> + new AccountSnapshot( + toyAccount.getAddress().toHexString(), + toyAccount.getNonce(), + toyAccount.getBalance().toHexString(), + toyAccount.getCode().toHexString())) + .toList(); + + List storageSnapshots = + accounts.stream() + .flatMap( + account -> + account.storage.entrySet().stream() + .map( + storageEntry -> + new StorageSnapshot( + account.getAddress().toHexString(), + storageEntry.getKey().toHexString(), + storageEntry.getValue().toHexString()))) + .toList(); + + List blockHashSnapshots = + blocks.stream() + .map( + blockSnapshot -> { + BlockHeader blockHeader = blockSnapshot.header().toBlockHeader(); + return BlockHashSnapshot.of(blockHeader.getNumber(), blockHeader.getBlockHash()); + }) + .toList(); + + return new ConflationSnapshot( + this.blocks, accountSnapshots, storageSnapshots, blockHashSnapshots); + } +} diff --git a/testing/src/main/java/net/consensys/linea/testing/ReplayExecutionEnvironment.java b/testing/src/main/java/net/consensys/linea/testing/ReplayExecutionEnvironment.java index f688c52dd4..a308e8be2b 100644 --- a/testing/src/main/java/net/consensys/linea/testing/ReplayExecutionEnvironment.java +++ b/testing/src/main/java/net/consensys/linea/testing/ReplayExecutionEnvironment.java @@ -133,6 +133,11 @@ public void replay(BigInteger chainId, final Reader replayFile, String inputFile this.checkTracer(inputFilePath); } + public void replay(BigInteger chainId, ConflationSnapshot conflation) { + this.executeFrom(chainId, conflation); + this.checkTracer(); + } + /** * Loads the states and the conflation defined in a {@link ConflationSnapshot}, mimick the * accounts, storage and blocks state as it was on the blockchain before the conflation played diff --git a/testing/src/main/java/net/consensys/linea/testing/ToyAccount.java b/testing/src/main/java/net/consensys/linea/testing/ToyAccount.java index 77e83448f8..d66bf1c0bf 100644 --- a/testing/src/main/java/net/consensys/linea/testing/ToyAccount.java +++ b/testing/src/main/java/net/consensys/linea/testing/ToyAccount.java @@ -48,7 +48,7 @@ public class ToyAccount implements MutableAccount { private Wei balance; private Bytes code; private Supplier codeHash = Suppliers.memoize(() -> Hash.hash(code)); - private final Map storage = new HashMap<>(); + final Map storage = new HashMap<>(); @Builder public ToyAccount( diff --git a/testing/src/main/java/net/consensys/linea/testing/ToyExecutionEnvironmentV2.java b/testing/src/main/java/net/consensys/linea/testing/ToyExecutionEnvironmentV2.java index 432037acf6..74a5333fc1 100644 --- a/testing/src/main/java/net/consensys/linea/testing/ToyExecutionEnvironmentV2.java +++ b/testing/src/main/java/net/consensys/linea/testing/ToyExecutionEnvironmentV2.java @@ -15,10 +15,6 @@ package net.consensys.linea.testing; -import static net.consensys.linea.zktracer.module.constants.GlobalConstants.LINEA_BASE_FEE; -import static net.consensys.linea.zktracer.module.constants.GlobalConstants.LINEA_BLOCK_GAS_LIMIT; -import static net.consensys.linea.zktracer.module.constants.GlobalConstants.LINEA_DIFFICULTY; - import java.math.BigInteger; import java.util.*; import java.util.function.Consumer; @@ -45,6 +41,7 @@ public class ToyExecutionEnvironmentV2 { public static final Address DEFAULT_COINBASE_ADDRESS = Address.fromHexString("0xc019ba5e00000000c019ba5e00000000c019ba5e"); private static final long DEFAULT_BLOCK_NUMBER = 6678980; + private static final long DEFAULT_TIME_STAMP = 1347310; private static final Hash DEFAULT_HASH = Hash.fromHexStringLenient("0xdeadbeef123123666dead666dead666"); @@ -86,10 +83,7 @@ public GeneralStateTestCaseEipSpec buildGeneralStateTestCaseSpec(ProtocolSpec pr ReferenceTestWorldState referenceTestWorldState = ReferenceTestWorldState.create(accountMockMap, protocolSpec.getEvm().getEvmConfiguration()); BlockHeader blockHeader = - BlockHeaderBuilder.createDefault() - .baseFee(Wei.of(LINEA_BASE_FEE)) - .gasLimit(LINEA_BLOCK_GAS_LIMIT) - .difficulty(Difficulty.of(LINEA_DIFFICULTY)) + ExecutionEnvironment.getLineaBlockHeaderBuilder(Optional.empty()) .number(DEFAULT_BLOCK_NUMBER) .coinbase(DEFAULT_COINBASE_ADDRESS) .timestamp(DEFAULT_TIME_STAMP) diff --git a/testing/src/test/java/net/consensys/linea/testing/ExampleMultiBlockTest.java b/testing/src/test/java/net/consensys/linea/testing/ExampleMultiBlockTest.java new file mode 100644 index 0000000000..0dc94328a4 --- /dev/null +++ b/testing/src/test/java/net/consensys/linea/testing/ExampleMultiBlockTest.java @@ -0,0 +1,192 @@ +/* + * Copyright Consensys Software Inc. + * + * 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 net.consensys.linea.testing; + +import java.util.List; + +import net.consensys.linea.zktracer.opcode.OpCode; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.datatypes.AccessListEntry; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.junit.jupiter.api.Test; + +class ExampleMultiBlockTest { + + @Test + void test() { + final ToyAccount receiverAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(116) + .address(Address.fromHexString("0xdead000000000000000000000000000beef")) + .build(); + + final KeyPair senderKeyPair1 = new SECP256K1().generateKeyPair(); + final Address senderAddress1 = + Address.extract(Hash.hash(senderKeyPair1.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount1 = + ToyAccount.builder().balance(Wei.fromEth(123)).nonce(5).address(senderAddress1).build(); + + final KeyPair senderKeyPair2 = new SECP256K1().generateKeyPair(); + final Address senderAddress2 = + Address.extract(Hash.hash(senderKeyPair2.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount2 = + ToyAccount.builder().balance(Wei.fromEth(1231)).nonce(15).address(senderAddress2).build(); + + final KeyPair senderKeyPair3 = new SECP256K1().generateKeyPair(); + final Address senderAddress3 = + Address.extract(Hash.hash(senderKeyPair3.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount3 = + ToyAccount.builder().balance(Wei.fromEth(1231)).nonce(15).address(senderAddress3).build(); + + final KeyPair senderKeyPair4 = new SECP256K1().generateKeyPair(); + final Address senderAddress4 = + Address.extract(Hash.hash(senderKeyPair4.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount4 = + ToyAccount.builder().balance(Wei.fromEth(11)).nonce(115).address(senderAddress4).build(); + + final KeyPair senderKeyPair5 = new SECP256K1().generateKeyPair(); + final Address senderAddress5 = + Address.extract(Hash.hash(senderKeyPair5.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount5 = + ToyAccount.builder().balance(Wei.fromEth(12)).nonce(0).address(senderAddress5).build(); + + final KeyPair senderKeyPair6 = new SECP256K1().generateKeyPair(); + final Address senderAddress6 = + Address.extract(Hash.hash(senderKeyPair6.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount6 = + ToyAccount.builder().balance(Wei.fromEth(12)).nonce(6).address(senderAddress6).build(); + + final KeyPair senderKeyPair7 = new SECP256K1().generateKeyPair(); + final Address senderAddress7 = + Address.extract(Hash.hash(senderKeyPair7.getPublicKey().getEncodedBytes())); + final ToyAccount senderAccount7 = + ToyAccount.builder().balance(Wei.fromEth(231)).nonce(21).address(senderAddress7).build(); + + final Transaction pureTransfer = + ToyTransaction.builder() + .sender(senderAccount1) + .to(receiverAccount) + .keyPair(senderKeyPair1) + .value(Wei.of(123)) + .build(); + + final Transaction pureTransferWoValue = + ToyTransaction.builder() + .sender(senderAccount2) + .to(receiverAccount) + .keyPair(senderKeyPair2) + .value(Wei.of(0)) + .build(); + + final List listOfKeys = + List.of("0x0123", "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + final List accessList = + List.of( + AccessListEntry.createAccessListEntry( + Address.fromHexString("0x1234567890"), listOfKeys)); + + final Transaction pureTransferWithUselessAccessList = + ToyTransaction.builder() + .sender(senderAccount3) + .to(receiverAccount) + .keyPair(senderKeyPair3) + .gasLimit(100000L) + .transactionType(TransactionType.ACCESS_LIST) + .accessList(accessList) + .value(Wei.of(546)) + .build(); + + final Transaction pureTransferWithUselessCalldata = + ToyTransaction.builder() + .sender(senderAccount4) + .to(receiverAccount) + .keyPair(senderKeyPair4) + .gasLimit(1000001L) + .value(Wei.of(546)) + .payload(Bytes.minimalBytes(0xdeadbeefL)) + .build(); + + final Transaction pureTransferWithUselessCalldataAndAccessList = + ToyTransaction.builder() + .sender(senderAccount5) + .to(receiverAccount) + .gasLimit(1000020L) + .transactionType(TransactionType.EIP1559) + .keyPair(senderKeyPair5) + .value(Wei.of(546)) + .accessList(accessList) + .payload(Bytes.minimalBytes(0xdeadbeefL)) + .build(); + + MultiBlockExecutionEnvironment.MultiBlockExecutionEnvironmentBuilder builder = + MultiBlockExecutionEnvironment.builder(); + builder + .accounts( + List.of( + senderAccount1, + senderAccount2, + senderAccount3, + senderAccount4, + senderAccount5, + senderAccount6, + senderAccount7, + receiverAccount)) + .addBlock(List.of(pureTransfer)) + .addBlock(List.of(pureTransferWoValue, pureTransferWithUselessAccessList)) + .addBlock( + List.of(pureTransferWithUselessCalldata, pureTransferWithUselessCalldataAndAccessList)) + .build() + .run(); + } + + @Test + void test2() { + KeyPair keyPair = new SECP256K1().generateKeyPair(); + Address senderAddress = Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes())); + + ToyAccount senderAccount = + ToyAccount.builder().balance(Wei.fromEth(1)).nonce(5).address(senderAddress).build(); + + ToyAccount receiverAccount = + ToyAccount.builder() + .balance(Wei.ONE) + .nonce(6) + .address(Address.fromHexString("0x111111")) + .code( + BytecodeCompiler.newProgram() + .push(32, 0xbeef) + .push(32, 0xdead) + .op(OpCode.ADD) + .compile()) + .build(); + + Transaction tx = + ToyTransaction.builder().sender(senderAccount).to(receiverAccount).keyPair(keyPair).build(); + + MultiBlockExecutionEnvironment.builder() + .accounts(List.of(senderAccount, receiverAccount)) + .addBlock(List.of(tx)) + .build() + .run(); + } +} diff --git a/testing/src/main/java/net/consensys/linea/testing/ExampleTxTest.java b/testing/src/test/java/net/consensys/linea/testing/ExampleTxTest.java similarity index 100% rename from testing/src/main/java/net/consensys/linea/testing/ExampleTxTest.java rename to testing/src/test/java/net/consensys/linea/testing/ExampleTxTest.java