diff --git a/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java index 5e076232ed..e93607c5e4 100644 --- a/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java @@ -72,7 +72,7 @@ protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableB } catch (final InterruptedException ex) { throw new CancellationException(); } catch (final ExecutionException ex) { - throw new RuntimeException("Failure occurred during nonce calculations."); + throw new RuntimeException("Failure occurred during nonce calculations.", ex); } return BlockHeaderBuilder.create() .populateFrom(sealableBlockHeader) diff --git a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java index 5d2c138b36..09a94a039e 100644 --- a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java @@ -19,6 +19,7 @@ import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver; import tech.pegasys.pantheon.ethereum.mainnet.EthHasher.Light; +import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ValidationTestUtils; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -41,7 +42,9 @@ public class EthHashBlockCreatorTest { BytesValue.fromHexString("0x476574682f76312e302e302f6c696e75782f676f312e342e32"); private final ExecutionContextTestFixture executionContextTestFixture = - new ExecutionContextTestFixture(); + ExecutionContextTestFixture.builder() + .protocolSchedule(MainnetProtocolSchedule.create(2, 3, 10, 11, 12, -1, 42)) + .build(); @Test public void createMainnetBlock1() throws IOException { diff --git a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java index 2a5c4c3f7e..c5fa8fd9e5 100644 --- a/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java +++ b/ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashMiningCoordinatorTest.java @@ -30,7 +30,7 @@ public class EthHashMiningCoordinatorTest { - private final ExecutionContextTestFixture executionContext = new ExecutionContextTestFixture(); + private final ExecutionContextTestFixture executionContext = ExecutionContextTestFixture.create(); private final SyncState syncState = mock(SyncState.class); private final EthHashMinerExecutor executor = mock(EthHashMinerExecutor.class); private final EthHashBlockMiner miner = mock(EthHashBlockMiner.class); diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 1996d47491..b54b31891a 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -50,6 +50,15 @@ dependencies { testSupportImplementation 'org.assertj:assertj-core' testSupportImplementation 'org.mockito:mockito-core' testSupportImplementation 'junit:junit' + + jmhImplementation project(':util') + jmhImplementation project( path: ':ethereum:core', configuration: 'testSupportArtifacts') + jmhImplementation project(':crypto') + jmhImplementation project(':ethereum:rlp') + jmhImplementation project(':ethereum:trie') + jmhImplementation project(':services:kvstore') + jmhImplementation 'com.google.guava:guava' + jmhImplementation 'org.openjdk.jmh:jmh-generator-annprocess' } configurations { testArtifacts } diff --git a/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/BlockHashOperationBenchmark.java b/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/BlockHashOperationBenchmark.java new file mode 100644 index 0000000000..7250837953 --- /dev/null +++ b/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/BlockHashOperationBenchmark.java @@ -0,0 +1,72 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.vm.operations; + +import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator; +import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup; +import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.util.bytes.Bytes32; +import tech.pegasys.pantheon.util.uint.UInt256; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; + +@State(Scope.Thread) +public class BlockHashOperationBenchmark { + + @Param({ + "1", // Worst-case scenario + "125", // Must iterate up the chain + "255" // Hash available directly via current header's parentHash + }) + public long blockNumber; + + private OperationBenchmarkHelper operationBenchmarkHelper; + private BlockHashOperation operation; + private MessageFrame frame; + + @Setup + public void prepare() throws Exception { + operationBenchmarkHelper = OperationBenchmarkHelper.create(); + operation = new BlockHashOperation(new ConstantinopleGasCalculator()); + frame = operationBenchmarkHelper.createMessageFrame(); + } + + @TearDown + public void cleanUp() throws Exception { + operationBenchmarkHelper.cleanUp(); + } + + @Benchmark + public Bytes32 executeOperation() { + frame.pushStackItem(UInt256.of(blockNumber).getBytes()); + operation.execute(frame); + return frame.popStackItem(); + } + + @Benchmark + public Bytes32 executeOperationWithEmptyHashCache() { + final MessageFrame cleanFrame = + operationBenchmarkHelper + .createMessageFrameBuilder() + .blockHashLookup(new BlockHashLookup(frame.getBlockHeader(), frame.getBlockchain())) + .build(); + cleanFrame.pushStackItem(UInt256.of(blockNumber).getBytes()); + operation.execute(cleanFrame); + return cleanFrame.popStackItem(); + } +} diff --git a/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java b/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java new file mode 100644 index 0000000000..a2bace71d8 --- /dev/null +++ b/ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/OperationBenchmarkHelper.java @@ -0,0 +1,113 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * 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. + */ +package tech.pegasys.pantheon.ethereum.vm.operations; + +import static java.util.Collections.emptyList; + +import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain; +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.core.BlockBody; +import tech.pegasys.pantheon.ethereum.core.BlockHeaderTestFixture; +import tech.pegasys.pantheon.ethereum.core.ExecutionContextTestFixture; +import tech.pegasys.pantheon.ethereum.core.MessageFrameTestFixture; +import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.services.kvstore.RocksDbKeyValueStorage; +import tech.pegasys.pantheon.util.uint.UInt256; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.common.io.MoreFiles; +import com.google.common.io.RecursiveDeleteOption; + +public class OperationBenchmarkHelper { + + private final Path storageDirectory; + private final RocksDbKeyValueStorage keyValueStorage; + private final MessageFrame messageFrame; + + private OperationBenchmarkHelper( + final Path storageDirectory, + final RocksDbKeyValueStorage keyValueStorage, + final MessageFrame messageFrame) { + this.storageDirectory = storageDirectory; + this.keyValueStorage = keyValueStorage; + this.messageFrame = messageFrame; + } + + public static OperationBenchmarkHelper create() throws IOException { + final Path storageDirectory = Files.createTempDirectory("benchmark"); + final RocksDbKeyValueStorage keyValueStorage = RocksDbKeyValueStorage.create(storageDirectory); + + final ExecutionContextTestFixture executionContext = + ExecutionContextTestFixture.builder().keyValueStorage(keyValueStorage).build(); + final MutableBlockchain blockchain = executionContext.getBlockchain(); + + for (int i = 1; i < 256; i++) { + blockchain.appendBlock( + new Block( + new BlockHeaderTestFixture() + .parentHash(blockchain.getChainHeadHash()) + .number(i) + .difficulty(UInt256.ONE) + .buildHeader(), + new BlockBody(emptyList(), emptyList())), + emptyList()); + } + final MessageFrame messageFrame = + new MessageFrameTestFixture() + .executionContextTestFixture(executionContext) + .blockHeader( + new BlockHeaderTestFixture() + .parentHash(blockchain.getChainHeadHash()) + .number(blockchain.getChainHeadBlockNumber() + 1) + .difficulty(UInt256.ONE) + .buildHeader()) + .build(); + return new OperationBenchmarkHelper(storageDirectory, keyValueStorage, messageFrame); + } + + public MessageFrame createMessageFrame() { + return createMessageFrameBuilder().build(); + } + + public MessageFrame.Builder createMessageFrameBuilder() { + return MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .messageFrameStack(messageFrame.getMessageFrameStack()) + .blockchain(messageFrame.getBlockchain()) + .worldState(messageFrame.getWorldState()) + .initialGas(messageFrame.getRemainingGas()) + .address(messageFrame.getContractAddress()) + .originator(messageFrame.getOriginatorAddress()) + .contract(messageFrame.getRecipientAddress()) + .gasPrice(messageFrame.getGasPrice()) + .inputData(messageFrame.getInputData()) + .sender(messageFrame.getSenderAddress()) + .value(messageFrame.getValue()) + .apparentValue(messageFrame.getApparentValue()) + .code(messageFrame.getCode()) + .blockHeader(messageFrame.getBlockHeader()) + .depth(messageFrame.getMessageStackDepth()) + .isStatic(messageFrame.isStatic()) + .completer(messageFrame -> {}) + .miningBeneficiary(messageFrame.getMiningBeneficiary()) + .blockHashLookup(messageFrame.getBlockHashLookup()); + } + + public void cleanUp() throws IOException { + keyValueStorage.close(); + MoreFiles.deleteRecursively(storageDirectory, RecursiveDeleteOption.ALLOW_INSECURE); + } +} diff --git a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java index ce166606b3..28ca603e35 100644 --- a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java +++ b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java @@ -27,25 +27,37 @@ public class ExecutionContextTestFixture { - private final Block genesis = GenesisConfig.mainnet().getBlock(); - private final KeyValueStorage keyValueStorage = new InMemoryKeyValueStorage(); - private final MutableBlockchain blockchain = - new DefaultMutableBlockchain(genesis, keyValueStorage, MainnetBlockHashFunction::createHash); - private final WorldStateArchive stateArchive = - new WorldStateArchive(new KeyValueStorageWorldStateStorage(keyValueStorage)); - - ProtocolSchedule protocolSchedule; - ProtocolContext protocolContext = new ProtocolContext<>(blockchain, stateArchive, null); - - public ExecutionContextTestFixture() { - this(MainnetProtocolSchedule.create(2, 3, 10, 11, 12, -1, 42)); - } + private final Block genesis; + private final KeyValueStorage keyValueStorage; + private final MutableBlockchain blockchain; + private final WorldStateArchive stateArchive; + + private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; - public ExecutionContextTestFixture(final ProtocolSchedule protocolSchedule) { - GenesisConfig.mainnet() - .writeStateTo( - new DefaultMutableWorldState(new KeyValueStorageWorldStateStorage(keyValueStorage))); + private ExecutionContextTestFixture( + final ProtocolSchedule protocolSchedule, final KeyValueStorage keyValueStorage) { + final GenesisConfig genesisConfig = GenesisConfig.mainnet(); + this.genesis = genesisConfig.getBlock(); + this.keyValueStorage = keyValueStorage; + this.blockchain = + new DefaultMutableBlockchain( + genesis, keyValueStorage, MainnetBlockHashFunction::createHash); + this.stateArchive = + new WorldStateArchive(new KeyValueStorageWorldStateStorage(keyValueStorage)); this.protocolSchedule = protocolSchedule; + this.protocolContext = new ProtocolContext<>(blockchain, stateArchive, null); + + genesisConfig.writeStateTo( + new DefaultMutableWorldState(new KeyValueStorageWorldStateStorage(keyValueStorage))); + } + + public static ExecutionContextTestFixture create() { + return new Builder().build(); + } + + public static Builder builder() { + return new Builder(); } public Block getGenesis() { @@ -71,4 +83,30 @@ public ProtocolSchedule getProtocolSchedule() { public ProtocolContext getProtocolContext() { return protocolContext; } + + public static class Builder { + + private KeyValueStorage keyValueStorage; + private ProtocolSchedule protocolSchedule; + + public Builder keyValueStorage(final KeyValueStorage keyValueStorage) { + this.keyValueStorage = keyValueStorage; + return this; + } + + public Builder protocolSchedule(final ProtocolSchedule protocolSchedule) { + this.protocolSchedule = protocolSchedule; + return this; + } + + public ExecutionContextTestFixture build() { + if (protocolSchedule == null) { + protocolSchedule = MainnetProtocolSchedule.create(0, 0, 0, 0, 0, 0, 42); + } + if (keyValueStorage == null) { + keyValueStorage = new InMemoryKeyValueStorage(); + } + return new ExecutionContextTestFixture(protocolSchedule, keyValueStorage); + } + } } diff --git a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MessageFrameTestFixture.java b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MessageFrameTestFixture.java index 9c7b09f117..af981703f3 100644 --- a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MessageFrameTestFixture.java +++ b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/MessageFrameTestFixture.java @@ -59,6 +59,12 @@ public MessageFrameTestFixture messageFrameStack(final Deque messa return this; } + public MessageFrameTestFixture executionContextTestFixture( + final ExecutionContextTestFixture executionContextTestFixture) { + this.executionContextTestFixture = executionContextTestFixture; + return this; + } + public MessageFrameTestFixture blockchain(final Blockchain blockchain) { this.blockchain = Optional.of(blockchain); return this; @@ -181,7 +187,7 @@ private Blockchain createDefaultBlockchain() { private ExecutionContextTestFixture getOrCreateExecutionContextTestFixture() { // Avoid creating a test fixture if the test supplies the blockchain and worldstate. if (executionContextTestFixture == null) { - executionContextTestFixture = new ExecutionContextTestFixture(); + executionContextTestFixture = ExecutionContextTestFixture.create(); } return executionContextTestFixture; } diff --git a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/TestCodeExecutor.java b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/TestCodeExecutor.java index 8da3bed6ec..a07aa4f410 100644 --- a/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/TestCodeExecutor.java +++ b/ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/TestCodeExecutor.java @@ -35,7 +35,7 @@ public class TestCodeExecutor { private static final Address SENDER_ADDRESS = AddressHelpers.ofValue(244259721); public TestCodeExecutor(final ProtocolSchedule protocolSchedule) { - fixture = new ExecutionContextTestFixture(protocolSchedule); + fixture = ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); } public MessageFrame executeCode(