diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java index 91ab41b636d..511b94d3ae5 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.core; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; import org.hyperledger.besu.config.GenesisConfigFile; @@ -31,10 +32,12 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.math.BigInteger; +import java.util.Optional; import java.util.function.Function; public class ExecutionContextTestFixture { @@ -52,7 +55,8 @@ private ExecutionContextTestFixture( final GenesisConfigFile genesisConfigFile, final ProtocolSchedule protocolSchedule, final KeyValueStorage blockchainKeyValueStorage, - final KeyValueStorage variablesKeyValueStorage) { + final KeyValueStorage variablesKeyValueStorage, + final Optional dataStorageFormat) { final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, protocolSchedule); this.genesis = genesisState.getBlock(); this.blockchainKeyValueStorage = blockchainKeyValueStorage; @@ -67,7 +71,9 @@ private ExecutionContextTestFixture( false), new NoOpMetricsSystem(), 0); - this.stateArchive = createInMemoryWorldStateArchive(); + if (dataStorageFormat.isPresent() && dataStorageFormat.get().equals(DataStorageFormat.BONSAI)) + this.stateArchive = createBonsaiInMemoryWorldStateArchive(blockchain); + else this.stateArchive = createInMemoryWorldStateArchive(); this.protocolSchedule = protocolSchedule; this.protocolContext = new ProtocolContext(blockchain, stateArchive, null, new BadBlockManager()); @@ -115,6 +121,7 @@ public static class Builder { private KeyValueStorage variablesKeyValueStorage; private KeyValueStorage blockchainKeyValueStorage; private ProtocolSchedule protocolSchedule; + private Optional dataStorageFormat = Optional.empty(); public Builder(final GenesisConfigFile genesisConfigFile) { this.genesisConfigFile = genesisConfigFile; @@ -135,6 +142,11 @@ public Builder protocolSchedule(final ProtocolSchedule protocolSchedule) { return this; } + public Builder dataStorageFormat(final DataStorageFormat dataStorageFormat) { + this.dataStorageFormat = Optional.of(dataStorageFormat); + return this; + } + public ExecutionContextTestFixture build() { if (protocolSchedule == null) { protocolSchedule = @@ -157,8 +169,13 @@ public ExecutionContextTestFixture build() { if (variablesKeyValueStorage == null) { variablesKeyValueStorage = new InMemoryKeyValueStorage(); } + return new ExecutionContextTestFixture( - genesisConfigFile, protocolSchedule, variablesKeyValueStorage, blockchainKeyValueStorage); + genesisConfigFile, + protocolSchedule, + variablesKeyValueStorage, + blockchainKeyValueStorage, + dataStorageFormat); } } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java new file mode 100644 index 00000000000..c9e43327d96 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java @@ -0,0 +1,794 @@ +/* + * 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.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPPrivateKey; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +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.BlockProcessingResult; +import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.parallelization.MainnetParallelBlockProcessor; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Uint256; + +@SuppressWarnings("rawtypes") +class AbstractBlockProcessorIntegrationTest { + + private static final String ACCOUNT_GENESIS_1 = "0x627306090abab3a6e1400e9345bc60c78a8bef57"; + private static final String ACCOUNT_GENESIS_2 = "0x7f2d653f56ea8de6ffa554c7a0cd4e03af79f3eb"; + + private static final String ACCOUNT_2 = "0x0000000000000000000000000000000000000002"; + private static final String ACCOUNT_3 = "0x0000000000000000000000000000000000000003"; + private static final String ACCOUNT_4 = "0x0000000000000000000000000000000000000004"; + private static final String ACCOUNT_5 = "0x0000000000000000000000000000000000000005"; + private static final String ACCOUNT_6 = "0x0000000000000000000000000000000000000006"; + private static final String CONTRACT_ADDRESS = "0x00000000000000000000000000000000000fffff"; + + private static final KeyPair ACCOUNT_GENESIS_1_KEYPAIR = + generateKeyPair("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3"); + + private static final KeyPair ACCOUNT_GENESIS_2_KEYPAIR = + generateKeyPair("fc5141e75bf622179f8eedada7fab3e2e6b3e3da8eb9df4f46d84df22df7430e"); + + private WorldStateArchive worldStateArchive; + private DefaultBlockchain blockchain; + private Address coinbase; + + @BeforeEach + public void setUp() { + final ExecutionContextTestFixture contextTestFixture = + ExecutionContextTestFixture.builder( + GenesisConfigFile.fromResource( + "/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json")) + .dataStorageFormat(DataStorageFormat.BONSAI) + .build(); + final BlockHeader blockHeader = new BlockHeaderTestFixture().number(0L).buildHeader(); + coinbase = blockHeader.getCoinbase(); + worldStateArchive = contextTestFixture.getStateArchive(); + blockchain = (DefaultBlockchain) contextTestFixture.getBlockchain(); + } + + private static Stream blockProcessorProvider() { + final ExecutionContextTestFixture contextTestFixture = + ExecutionContextTestFixture.builder( + GenesisConfigFile.fromResource( + "/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json")) + .dataStorageFormat(DataStorageFormat.BONSAI) + .build(); + final ProtocolSchedule protocolSchedule = contextTestFixture.getProtocolSchedule(); + final BlockHeader blockHeader = new BlockHeaderTestFixture().number(0L).buildHeader(); + final MainnetTransactionProcessor transactionProcessor = + protocolSchedule.getByBlockHeader(blockHeader).getTransactionProcessor(); + + final BlockProcessor sequentialBlockProcessor = + new MainnetBlockProcessor( + transactionProcessor, + protocolSchedule + .getByBlockHeader(new BlockHeaderTestFixture().number(0L).buildHeader()) + .getTransactionReceiptFactory(), + Wei.of(2_000_000_000_000_000L), + BlockHeader::getCoinbase, + false, + protocolSchedule); + + final BlockProcessor parallelBlockProcessor = + new MainnetParallelBlockProcessor( + transactionProcessor, + protocolSchedule + .getByBlockHeader(new BlockHeaderTestFixture().number(0L).buildHeader()) + .getTransactionReceiptFactory(), + Wei.of(2_000_000_000_000_000L), + BlockHeader::getCoinbase, + false, + protocolSchedule, + new NoOpMetricsSystem()); + + return Stream.of( + Arguments.of("sequential", sequentialBlockProcessor), + Arguments.of("parallel", parallelBlockProcessor)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testBlockProcessingWithTransfers( + final String ignoredName, final BlockProcessor blockProcessor) { + processSimpleTransfers(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessConflictedSimpleTransfersSameSender( + final String ignoredName, final BlockProcessor blockProcessor) { + processConflictedSimpleTransfersSameSender(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessConflictedSimpleTransfersSameAddressReceiverAndSender( + final String ignoredName, final BlockProcessor blockProcessor) { + processConflictedSimpleTransfersSameAddressReceiverAndSender(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessConflictedSimpleTransfersWithCoinbase( + final String ignoredName, final BlockProcessor blockProcessor) { + processConflictedSimpleTransfersWithCoinbase(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessContractSlotUpdateThenReadTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processContractSlotUpdateThenReadTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessSlotReadThenUpdateTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processSlotReadThenUpdateTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountReadThenUpdateTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountReadThenUpdateTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountUpdateThenReadTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountUpdateThenReadTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountReadThenUpdateTxWithTwoAccounts( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountReadThenUpdateTxWithTwoAccounts(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountUpdateThenReadTeTxWithTwoAccounts( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountUpdateThenReadTxWithTwoAccounts(blockProcessor); + } + + private void processSimpleTransfers(final BlockProcessor blockProcessor) { + // Create two non conflicted transactions + Transaction transactionTransfer1 = // ACCOUNT_GENESIS_1 -> ACCOUNT_2 + createTransferTransaction( + 0, 1_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_2, ACCOUNT_GENESIS_1_KEYPAIR); + Transaction transactionTransfer2 = // ACCOUNT_GENESIS_2 -> ACCOUNT_3 + createTransferTransaction( + 0, 2_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_3, ACCOUNT_GENESIS_2_KEYPAIR); + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount senderAccount1 = (BonsaiAccount) worldState.get(transactionTransfer1.getSender()); + BonsaiAccount senderAccount2 = (BonsaiAccount) worldState.get(transactionTransfer2.getSender()); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x4ca6e755674a1df696e5365361a0c352422934ba3ad0a74c9e6b0b56e4f80b4c", + transactionTransfer1, + transactionTransfer2); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount1 = + (BonsaiAccount) worldState.get(transactionTransfer1.getSender()); + BonsaiAccount updatedSenderAccount2 = + (BonsaiAccount) worldState.get(transactionTransfer2.getSender()); + + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + BonsaiAccount updatedAccount0x3 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_3)); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(1_000_000_000_000_000_000L)); + assertThat(updatedAccount0x3.getBalance()).isEqualTo(Wei.of(2_000_000_000_000_000_000L)); + assertThat(updatedSenderAccount1.getBalance()).isLessThan(senderAccount1.getBalance()); + assertThat(updatedSenderAccount2.getBalance()).isLessThan(senderAccount2.getBalance()); + } + + private void processConflictedSimpleTransfersSameSender(final BlockProcessor blockProcessor) { + // Create three transactions with the same sender + Transaction transferTransaction1 = // ACCOUNT_GENESIS_1 -> ACCOUNT_4 + createTransferTransaction( + 0, 1_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_4, ACCOUNT_GENESIS_1_KEYPAIR); + Transaction transferTransaction2 = // ACCOUNT_GENESIS_1 -> ACCOUNT_5 + createTransferTransaction( + 1, 2_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_5, ACCOUNT_GENESIS_1_KEYPAIR); + Transaction transferTransaction3 = // ACCOUNT_GENESIS_1 -> ACCOUNT_6 + createTransferTransaction( + 2, 3_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_6, ACCOUNT_GENESIS_1_KEYPAIR); + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount senderAccount = (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x7420935ee980cb06060f119ee3ee3dcd5a96989985938a3b3ca096558ad61484", + transferTransaction1, + transferTransaction2, + transferTransaction3); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + BonsaiAccount updatedAccount0x4 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_4)); + BonsaiAccount updatedAccount0x5 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_5)); + BonsaiAccount updatedAccount0x6 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_6)); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedAccount0x4.getBalance()).isEqualTo(Wei.of(1_000_000_000_000_000_000L)); + assertThat(updatedAccount0x5.getBalance()).isEqualTo(Wei.of(2_000_000_000_000_000_000L)); + assertThat(updatedAccount0x6.getBalance()).isEqualTo(Wei.of(3_000_000_000_000_000_000L)); + + assertThat(updatedSenderAccount.getBalance()).isLessThan(senderAccount.getBalance()); + } + + private void processConflictedSimpleTransfersSameAddressReceiverAndSender( + final BlockProcessor blockProcessor) { + // Create conflicted transfer transactions + Transaction transferTransaction1 = + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + ACCOUNT_GENESIS_2, + ACCOUNT_GENESIS_1_KEYPAIR); // ACCOUNT_GENESIS_1 -> ACCOUNT_GENESIS_2 + Transaction transferTransaction2 = + createTransferTransaction( + 0, + 2_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + ACCOUNT_2, + ACCOUNT_GENESIS_2_KEYPAIR); // ACCOUNT_GENESIS_2 -> ACCOUNT_2 + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount transferTransaction1Sender = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x5c0158e79b66c86cf5e5256390b95add0c2e6891c24e72d71b9dbea5845fea72", + transferTransaction1, + transferTransaction2); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount1 = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + BonsaiAccount updatedGenesisAccount1 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_GENESIS_1)); + BonsaiAccount updatedGenesisAccount2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_GENESIS_2)); + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedGenesisAccount1.getBalance()) + .isEqualTo( + Wei.of( + UInt256.fromHexString( + ("0x00000000000000000000000000000000000000000000003627e8f7123739c1c8")))); + assertThat(updatedGenesisAccount2.getBalance()) + .isEqualTo( + Wei.of( + UInt256.fromHexString( + ("0x00000000000000000000000000000000000000000000003627e8f7123739c024")))); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(2_000_000_000_000_000_000L)); + assertThat(updatedSenderAccount1.getBalance()) + .isLessThan(transferTransaction1Sender.getBalance()); + } + + private void processConflictedSimpleTransfersWithCoinbase(final BlockProcessor blockProcessor) { + // Create conflicted transactions using coinbase + Transaction transferTransaction1 = + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + ACCOUNT_2, + ACCOUNT_GENESIS_1_KEYPAIR); // ACCOUNT_GENESIS_1 -> ACCOUNT_2 + Transaction transferTransaction2 = + createTransferTransaction( + 0, + 2_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + coinbase.toHexString(), + ACCOUNT_GENESIS_2_KEYPAIR); // ACCOUNT_GENESIS_2 -> COINBASE + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount transferTransaction1Sender = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + Block blockWithTransactions = + createBlockWithTransactions( + "0xd9544f389692face27352d23494dd1446d9af025067bc11b29e0eb83e258676a", + transferTransaction1, + transferTransaction2); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount1 = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + + BonsaiAccount updatedCoinbase = (BonsaiAccount) worldState.get(coinbase); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(1_000_000_000_000_000_000L)); + assertThat(updatedCoinbase.getBalance()) + .isEqualTo( + Wei.of( + UInt256.fromHexString( + ("0x0000000000000000000000000000000000000000000000001bc8886498566008")))); + + assertThat(updatedSenderAccount1.getBalance()) + .isLessThan(transferTransaction1Sender.getBalance()); + } + + void processContractSlotUpdateThenReadTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + + // create conflicted transactions on the same slot (update then read) + Transaction setSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "setSlot1", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(100)); + Transaction getSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "getSlot1", ACCOUNT_GENESIS_2_KEYPAIR, Optional.empty()); + Transaction setSlot3Transaction = + createContractUpdateSlotTransaction( + 1, contractAddress, "setSlot2", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(200)); + Transaction setSlot4Transaction = + createContractUpdateSlotTransaction( + 2, contractAddress, "setSlot3", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(300)); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x51d59f64426ea986b1323aa22b9881c83f67947b4f90c2c302b21d3f8c459aff", + setSlot1Transaction, + getSlot1Transaction, + setSlot3Transaction, + setSlot4Transaction); + + MutableWorldState worldState = worldStateArchive.getMutable(); + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + assertContractStorage(worldState, contractAddress, 0, 100); + assertContractStorage(worldState, contractAddress, 1, 200); + assertContractStorage(worldState, contractAddress, 2, 300); + } + + void processSlotReadThenUpdateTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + + Transaction getSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "getSlot1", ACCOUNT_GENESIS_1_KEYPAIR, Optional.empty()); + Transaction setSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "setSlot1", ACCOUNT_GENESIS_2_KEYPAIR, Optional.of(1000)); + Transaction setSlo2Transaction = + createContractUpdateSlotTransaction( + 1, contractAddress, "setSlot2", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(2000)); + Transaction setSlot3Transaction = + createContractUpdateSlotTransaction( + 2, contractAddress, "setSlot3", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(3000)); + + Block blockWithTransactions = + createBlockWithTransactions( + "0xdf21d4fef211d7a905022dc87f2a68f4bf9cb273fcf9745cfa7f7c2f258c03f3", + getSlot1Transaction, + setSlot1Transaction, + setSlo2Transaction, + setSlot3Transaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + assertContractStorage(worldState, contractAddress, 0, 1000); + assertContractStorage(worldState, contractAddress, 1, 2000); + assertContractStorage(worldState, contractAddress, 2, 3000); + } + + void processAccountReadThenUpdateTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = // ACCOUNT_GENESIS_1 -> CONTRACT_ADDRESS + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 1, contractAddress, "getBalance", ACCOUNT_GENESIS_1_KEYPAIR, ACCOUNT_2); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 0, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_2_KEYPAIR, + ACCOUNT_2, + 500_000_000_000_000_000L); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x91966cdde619acb05a1d9fef2f8801432a30edde7131f1f194002b0a766026c7", + transactionTransfer, + getcontractBalanceTransaction, + sendEthFromContractTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + void processAccountUpdateThenReadTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 1, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_1_KEYPAIR, + ACCOUNT_2, + 500_000_000_000_000_000L); + + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 0, contractAddress, "getBalance", ACCOUNT_GENESIS_2_KEYPAIR, ACCOUNT_2); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x375af730c0f9e04666659fc419fda74cc0cb29936607c08adf21d3b236c6b7f6", + transactionTransfer, + sendEthFromContractTransaction, + getcontractBalanceTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + void processAccountReadThenUpdateTxWithTwoAccounts(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = // ACCOUNT_GENESIS_1 -> CONTRACT_ADDRESS + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 1, contractAddress, "getBalance", ACCOUNT_GENESIS_1_KEYPAIR, ACCOUNT_2); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 0, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_2_KEYPAIR, + ACCOUNT_3, + 500_000_000_000_000_000L); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x3c2366a28dadbcef39ba04cde7bc30a5dccfce1e478a5c2602f5a28ab9498e6c", + transactionTransfer, + getcontractBalanceTransaction, + sendEthFromContractTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x3 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_3)); + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x3.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + void processAccountUpdateThenReadTxWithTwoAccounts(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = // ACCOUNT_GENESIS_1 -> CONTRACT_ADDRESS + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 0, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_2_KEYPAIR, + ACCOUNT_3, + 500_000_000_000_000_000L); + + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 1, contractAddress, "getBalance", ACCOUNT_GENESIS_1_KEYPAIR, ACCOUNT_2); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x3c2366a28dadbcef39ba04cde7bc30a5dccfce1e478a5c2602f5a28ab9498e6c", + transactionTransfer, + sendEthFromContractTransaction, + getcontractBalanceTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x3 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_3)); + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x3.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + private static KeyPair generateKeyPair(final String privateKeyHex) { + final KeyPair keyPair = + SignatureAlgorithmFactory.getInstance() + .createKeyPair( + SECPPrivateKey.create( + Bytes32.fromHexString(privateKeyHex), SignatureAlgorithm.ALGORITHM)); + return keyPair; + } + + private Transaction createContractUpdateSlotTransaction( + final int nonce, + final Address contractAddress, + final String methodSignature, + final KeyPair keyPair, + final Optional value) { + Bytes payload = encodeFunctionCall(methodSignature, value); + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(5)) + .maxFeePerGas(Wei.of(7)) + .gasLimit(3000000L) + .to(contractAddress) + .value(Wei.ZERO) + .payload(payload) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } + + private Transaction createContractReadAccountTransaction( + final int nonce, + final Address contractAddress, + final String methodSignature, + final KeyPair keyPair, + final String address) { + Bytes payload = encodeFunctionCall(methodSignature, address); + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(5)) + .maxFeePerGas(Wei.of(7)) + .gasLimit(3000000L) + .to(contractAddress) + .value(Wei.ZERO) + .payload(payload) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } + + private Transaction createContractUpdateAccountTransaction( + final int nonce, + final Address contractAddress, + final String methodSignature, + final KeyPair keyPair, + final String address, + final long value) { + Bytes payload = encodeFunctionCall(methodSignature, address, value); + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(5)) + .maxFeePerGas(Wei.of(7)) + .gasLimit(3000000L) + .to(contractAddress) + .value(Wei.ZERO) + .payload(payload) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } + + private Bytes encodeFunctionCall(final String methodSignature, final Optional value) { + List inputParameters = + value.isPresent() ? Arrays.asList(new Uint256(value.get())) : List.of(); + Function function = new Function(methodSignature, inputParameters, List.of()); + return Bytes.fromHexString(FunctionEncoder.encode(function)); + } + + private Bytes encodeFunctionCall(final String methodSignature, final String address) { + List inputParameters = Arrays.asList(new org.web3j.abi.datatypes.Address(address)); + Function function = new Function(methodSignature, inputParameters, List.of()); + return Bytes.fromHexString(FunctionEncoder.encode(function)); + } + + private Bytes encodeFunctionCall( + final String methodSignature, final String address, final long value) { + List inputParameters = + Arrays.asList(new org.web3j.abi.datatypes.Address(address), new Uint256(value)); + Function function = new Function(methodSignature, inputParameters, List.of()); + return Bytes.fromHexString(FunctionEncoder.encode(function)); + } + + private Block createBlockWithTransactions( + final String stateRoot, final Transaction... transactions) { + BlockHeader blockHeader = + new BlockHeaderTestFixture() + .number(1L) + .stateRoot(Hash.fromHexString(stateRoot)) + .gasLimit(30_000_000L) + .baseFeePerGas(Wei.of(5)) + .buildHeader(); + BlockBody blockBody = new BlockBody(Arrays.asList(transactions), Collections.emptyList()); + return new Block(blockHeader, blockBody); + } + + private void assertContractStorage( + final MutableWorldState worldState, + final Address contractAddress, + final int slot, + final int expectedValue) { + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + UInt256 actualValue = contractAccount.getStorageValue(UInt256.valueOf(slot)); + assertThat(actualValue).isEqualTo(UInt256.valueOf(expectedValue)); + } + + private Transaction createTransferTransaction( + final long nonce, + final long value, + final long gasLimit, + final long maxPriorityFeePerGas, + final long maxFeePerGas, + final String hexAddress, + final KeyPair keyPair) { + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(maxPriorityFeePerGas)) + .maxFeePerGas(Wei.of(maxFeePerGas)) + .gasLimit(gasLimit) + .to(Address.fromHexStringStrict(hexAddress)) + .value(Wei.of(value)) + .payload(Bytes.EMPTY) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } +} diff --git a/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/contractUsedInBlockProcessingIT.sol b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/contractUsedInBlockProcessingIT.sol new file mode 100644 index 00000000000..496ae672025 --- /dev/null +++ b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/contractUsedInBlockProcessingIT.sol @@ -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 + */ +pragma solidity ^0.8.19; + +contract SimpleStorage { + uint256 private slot1; //0x0 + uint256 private slot2; //0x1 + uint256 private slot3; //0x2 + + function setSlot1(uint256 value) public { + slot1 = value; + } + + function setSlot2(uint256 value) public { + slot2 = value; + } + + function setSlot3(uint256 value) public { + slot3 = value; + } + + function getSlot1() public view returns (uint256) { + return slot1; + } + + function getSlot2() public view returns (uint256) { + return slot2; + } + + function getSlot3() public view returns (uint256) { + return slot3; + } + + function getBalance(address account) public view returns (uint256) { + return account.balance; + } + + function transferTo(address payable recipient, uint256 amount) public payable { + require(amount <= address(this).balance, "Insufficient balance in contract"); + recipient.transfer(amount); + } + + receive() external payable {} +} diff --git a/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json new file mode 100644 index 00000000000..72fbc561ec7 --- /dev/null +++ b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json @@ -0,0 +1,48 @@ +{ + "config": { + "chainId":42, + "homesteadBlock":0, + "eip150Block":0, + "eip155Block":0, + "eip158Block":0, + "byzantiumBlock":0, + "constantinopleBlock":0, + "petersburgBlock":0, + "istanbulBlock":0, + "muirGlacierBlock":0, + "berlinBlock":0, + "londonBlock":0, + "shanghaiTime": 0, + "terminalTotalDifficulty":0, + "cancunTime":0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0000001", + "extraData": "", + "gasLimit": "0x1C9C380", + "nonce": "0x0000000000000107", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00", + "alloc": { + "627306090abab3a6e1400e9345bc60c78a8bef57": { + "balance": "0x3635C9ADC5DEA00000", + "privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored" + }, + "7f2d653f56ea8de6ffa554c7a0cd4e03af79f3eb": { + "balance": "0x3635C9ADC5DEA00000", + "privateKey": "fc5141e75bf622179f8eedada7fab3e2e6b3e3da8eb9df4f46d84df22df7430e", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored" + },"0x00000000000000000000000000000000000fffff": { + "balance": "0", + "code": "60806040526004361061007e575f3560e01c8063713ba21b1161004d578063713ba21b14610121578063bad4ba0b14610149578063cda7be6114610171578063f8b2cb4f1461019b57610085565b8063250976e7146100895780632ccb1b30146100b35780635e8ad6c7146100cf5780636c190cd1146100f757610085565b3661008557005b5f80fd5b348015610094575f80fd5b5061009d6101d7565b6040516100aa91906102d1565b60405180910390f35b6100cd60048036038101906100c89190610372565b6101e0565b005b3480156100da575f80fd5b506100f560048036038101906100f091906103b0565b61026b565b005b348015610102575f80fd5b5061010b610274565b60405161011891906102d1565b60405180910390f35b34801561012c575f80fd5b50610147600480360381019061014291906103b0565b61027d565b005b348015610154575f80fd5b5061016f600480360381019061016a91906103b0565b610287565b005b34801561017c575f80fd5b50610185610291565b60405161019291906102d1565b60405180910390f35b3480156101a6575f80fd5b506101c160048036038101906101bc9190610416565b610299565b6040516101ce91906102d1565b60405180910390f35b5f600254905090565b47811115610223576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161021a9061049b565b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015610266573d5f803e3d5ffd5b505050565b805f8190555050565b5f600154905090565b8060018190555050565b8060028190555050565b5f8054905090565b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f819050919050565b6102cb816102b9565b82525050565b5f6020820190506102e45f8301846102c2565b92915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610317826102ee565b9050919050565b6103278161030d565b8114610331575f80fd5b50565b5f813590506103428161031e565b92915050565b610351816102b9565b811461035b575f80fd5b50565b5f8135905061036c81610348565b92915050565b5f8060408385031215610388576103876102ea565b5b5f61039585828601610334565b92505060206103a68582860161035e565b9150509250929050565b5f602082840312156103c5576103c46102ea565b5b5f6103d28482850161035e565b91505092915050565b5f6103e5826102ee565b9050919050565b6103f5816103db565b81146103ff575f80fd5b50565b5f81359050610410816103ec565b92915050565b5f6020828403121561042b5761042a6102ea565b5b5f61043884828501610402565b91505092915050565b5f82825260208201905092915050565b7f496e73756666696369656e742062616c616e636520696e20636f6e74726163745f82015250565b5f610485602083610441565b915061049082610451565b602082019050919050565b5f6020820190508181035f8301526104b281610479565b905091905056fea264697066735822122023baf1931f9dfdc95c00818838ff0de5f17ac26981187c1407ae4f1d111ff57464736f6c634300081a0033", + "comment": "This bytecode corresponds to the smart contract contractUsedInBlockProcessingIT.sol, visible in the resource files", + "storage": { + "0x0": "0x2a", + "0x1": "0x54", + "0x2": "0x7e" + } + } + } +} \ No newline at end of file