This is an automated email from the ASF dual-hosted git repository. toulmean pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
The following commit(s) were added to refs/heads/main by this push: new 1abe688 Add a way to dump state to bytes new 96de55f Merge pull request #389 from atoulme/evm_state 1abe688 is described below commit 1abe688017cf76b30aada42b5ded4043d82a3959 Author: Antoine Toulme <anto...@lunar-ocean.com> AuthorDate: Tue Mar 22 22:12:31 2022 -0700 Add a way to dump state to bytes --- .../apache/tuweni/blockprocessor/BlockProcessor.kt | 22 +++++++-- evm/build.gradle | 1 + .../apache/tuweni/evm/EthereumVirtualMachine.kt | 55 ++++++++++++++++++---- .../kotlin/org/apache/tuweni/evm/impl/EvmVmImpl.kt | 30 +++++++----- .../org/apache/tuweni/evm/EVMReferenceTest.kt | 4 +- .../tuweni/evm/EthereumVirtualMachineTest.kt | 36 ++++++++++---- 6 files changed, 111 insertions(+), 37 deletions(-) diff --git a/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/BlockProcessor.kt b/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/BlockProcessor.kt index 2846fdb..71123c8 100644 --- a/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/BlockProcessor.kt +++ b/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/BlockProcessor.kt @@ -28,6 +28,7 @@ import org.apache.tuweni.eth.repository.TransientStateRepository import org.apache.tuweni.evm.EVMExecutionStatusCode import org.apache.tuweni.evm.EthereumVirtualMachine import org.apache.tuweni.evm.impl.EvmVmImpl +import org.apache.tuweni.evm.impl.StepListener import org.apache.tuweni.rlp.RLP import org.apache.tuweni.trie.MerklePatriciaTrie import org.apache.tuweni.trie.MerkleTrie @@ -42,9 +43,22 @@ import java.time.Instant */ class BlockProcessor { - suspend fun execute(parentBlock: Block, transactions: List<Transaction>, repository: BlockchainRepository): ProtoBlock { + /** + * Executes a state transition. + * + * @param parentBlock the parent block + * @param transactions the list of transactions to execute + * @param repository the blockchain repository to execute against + * @param stepListener an optional listener that can follow the steps of the execution + */ + suspend fun execute( + parentBlock: Block, + transactions: List<Transaction>, + repository: BlockchainRepository, + stepListener: StepListener? = null, + ): ProtoBlock { val stateChanges = TransientStateRepository(repository) - val vm = EthereumVirtualMachine(repository, EvmVmImpl::create) + val vm = EthereumVirtualMachine(repository, { EvmVmImpl.create(stepListener) }) vm.start() var index = 0L @@ -130,7 +144,7 @@ class BlockProcessor { } val receipt = TransactionReceipt( 1, - result.gasManager.gasCost.toLong(), + result.state.gasManager.gasCost.toLong(), txLogsBloomFilter, result.changes.getLogs() ) @@ -138,7 +152,7 @@ class BlockProcessor { receiptsTrie.put(indexKey, receipt.toBytes()) counter++ - allGasUsed = allGasUsed.add(result.gasManager.gasCost) + allGasUsed = allGasUsed.add(result.state.gasManager.gasCost) } } diff --git a/evm/build.gradle b/evm/build.gradle index 7a8a39b..cc5a0e7 100644 --- a/evm/build.gradle +++ b/evm/build.gradle @@ -21,6 +21,7 @@ dependencies { implementation project(':eth-repository') implementation project(':genesis') implementation project(':merkle-trie') + implementation project(':rlp') implementation project(':units') implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core' implementation 'org.apache.lucene:lucene-core' diff --git a/evm/src/main/kotlin/org/apache/tuweni/evm/EthereumVirtualMachine.kt b/evm/src/main/kotlin/org/apache/tuweni/evm/EthereumVirtualMachine.kt index 655c51c..9c4cdff 100644 --- a/evm/src/main/kotlin/org/apache/tuweni/evm/EthereumVirtualMachine.kt +++ b/evm/src/main/kotlin/org/apache/tuweni/evm/EthereumVirtualMachine.kt @@ -24,6 +24,7 @@ import org.apache.tuweni.eth.repository.BlockchainRepository import org.apache.tuweni.evm.impl.GasManager import org.apache.tuweni.evm.impl.Memory import org.apache.tuweni.evm.impl.Stack +import org.apache.tuweni.rlp.RLP import org.apache.tuweni.units.bigints.UInt256 import org.apache.tuweni.units.ethereum.Gas import org.apache.tuweni.units.ethereum.Wei @@ -93,19 +94,55 @@ enum class HardFork(val number: Int) { val latestHardFork = HardFork.BERLIN /** + * State of the EVM + * + * @param gasManager the gas manager + * @param logs the logs of the EVM + * @param stack the stack of the execution + * @param memory the current memory allocation + * @param output the output of the execution + */ +data class EVMState( + val gasManager: GasManager, + val logs: List<Log>, + val stack: Stack, + val memory: Memory, + val output: Bytes? = null, +) { + + /** + * Dumps the EVM execution state into a byte array + */ + fun toBytes(): Bytes = RLP.encodeList { + it.writeString("gas") + it.writeValue(gasManager.gas.toBytes()) + it.writeString("memory") + it.writeValue(memory.memoryData ?: Bytes.EMPTY) + it.writeString("stack") + for (i in 0 until stack.size()) { + it.writeValue(stack.get(i) ?: Bytes.EMPTY) + } + it.writeString("output") + it.writeValue(output ?: Bytes.EMPTY) + it.writeString("logs") + for (log in logs) { + it.writeValue(log.toBytes()) + } + } +} + +/** * Result of EVM execution * @param statusCode the execution result status * @param hostContext the context of changes - * @param output the output of the execution + * @param changes the set of changes + * @param state the EVM state */ data class EVMResult( val statusCode: EVMExecutionStatusCode, - val gasManager: GasManager, val hostContext: HostContext, val changes: ExecutionChanges, - val stack: Stack, - val memory: Memory, - val output: Bytes? = null, + val state: EVMState, ) /** @@ -120,7 +157,7 @@ data class EVMMessage( val sender: Address, val inputData: Bytes, val value: Bytes, - val createSalt: Bytes32 = Bytes32.ZERO + val createSalt: Bytes32 = Bytes32.ZERO, ) /** @@ -133,7 +170,7 @@ data class EVMMessage( class EthereumVirtualMachine( private val repository: BlockchainRepository, private val evmVmFactory: () -> EvmVm, - private val options: Map<String, String> = mapOf() + private val options: Map<String, String> = mapOf(), ) { private var vm: EvmVm? = null @@ -196,7 +233,7 @@ class EthereumVirtualMachine( currentDifficulty: UInt256, callKind: CallKind = CallKind.CALL, revision: HardFork = latestHardFork, - depth: Int = 0 + depth: Int = 0, ): EVMResult { val hostContext = TransactionalEVMHostContext( repository, @@ -241,7 +278,7 @@ class EthereumVirtualMachine( callKind: CallKind = CallKind.CALL, revision: HardFork = latestHardFork, depth: Int = 0, - hostContext: HostContext + hostContext: HostContext, ): EVMResult { val msg = EVMMessage( diff --git a/evm/src/main/kotlin/org/apache/tuweni/evm/impl/EvmVmImpl.kt b/evm/src/main/kotlin/org/apache/tuweni/evm/impl/EvmVmImpl.kt index 7857450..ff1049d 100644 --- a/evm/src/main/kotlin/org/apache/tuweni/evm/impl/EvmVmImpl.kt +++ b/evm/src/main/kotlin/org/apache/tuweni/evm/impl/EvmVmImpl.kt @@ -20,6 +20,7 @@ import org.apache.tuweni.bytes.Bytes import org.apache.tuweni.evm.EVMExecutionStatusCode import org.apache.tuweni.evm.EVMMessage import org.apache.tuweni.evm.EVMResult +import org.apache.tuweni.evm.EVMState import org.apache.tuweni.evm.EvmVm import org.apache.tuweni.evm.HardFork import org.apache.tuweni.evm.HostContext @@ -41,9 +42,11 @@ interface StepListener { /** * Checks the execution path * + * @param executionPath the path of execution + * @param state the state of the EVM * @return true to halt the execution */ - fun halt(executionPath: List<Byte>): Boolean + fun handleStep(executionPath: List<Byte>, state: EVMState): Boolean } class EvmVmImpl(val stepListener: StepListener? = null) : EvmVm { @@ -85,7 +88,7 @@ class EvmVmImpl(val stepListener: StepListener? = null) : EvmVm { val opcode = registry.get(fork, code.get(current)) if (opcode == null) { logger.error("Could not find opcode for ${code.slice(current, 1)} at position $current") - return EVMResult(EVMExecutionStatusCode.INVALID_INSTRUCTION, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + return EVMResult(EVMExecutionStatusCode.INVALID_INSTRUCTION, hostContext, hostContext as TransactionalEVMHostContext, EVMState(gasManager, hostContext.getLogs(), stack, memory)) } val currentOpcodeByte = code.get(current) current++ @@ -95,36 +98,36 @@ class EvmVmImpl(val stepListener: StepListener? = null) : EvmVm { ">> OPCODE: ${opcodes[currentOpcodeByte] ?: currentOpcodeByte.toString(16)} " + "gas: ${gasManager.gasLeft()} cost: ${gasManager.lastGasCost()}" ) + val state = EVMState(gasManager, (hostContext as TransactionalEVMHostContext).getLogs(), stack, memory, result?.output) + if (result?.status != null) { if (logger.isTraceEnabled) { logger.trace(executionPath.map { opcodes[it] ?: it.toString(16) }.joinToString(">")) } if (result.status == EVMExecutionStatusCode.SUCCESS && !gasManager.hasGasLeft()) { - return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, hostContext, hostContext, state) } - return EVMResult(result.status, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory, result.output) + return EVMResult(result.status, hostContext, hostContext, state) } result?.newCodePosition?.let { current = result.newCodePosition } if (!gasManager.hasGasLeft()) { - return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, hostContext, hostContext, state) } if (stack.overflowed()) { - return EVMResult(EVMExecutionStatusCode.STACK_OVERFLOW, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + return EVMResult(EVMExecutionStatusCode.STACK_OVERFLOW, hostContext, hostContext, state) } if (result?.validationStatus != null) { - return EVMResult(result.validationStatus, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + return EVMResult(result.validationStatus, hostContext, hostContext, state) } - stepListener?.halt(executionPath)?.let { + stepListener?.handleStep(executionPath, state)?.let { if (it) { return EVMResult( EVMExecutionStatusCode.HALTED, - gasManager, hostContext, - hostContext as TransactionalEVMHostContext, - stack, - memory + hostContext, + EVMState(gasManager, hostContext.getLogs(), stack, memory, result?.output) ) } } @@ -132,7 +135,8 @@ class EvmVmImpl(val stepListener: StepListener? = null) : EvmVm { if (logger.isTraceEnabled) { logger.trace(executionPath.map { opcodes[it] ?: it.toString(16) }.joinToString(">")) } - return EVMResult(EVMExecutionStatusCode.SUCCESS, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + val state = EVMState(gasManager, (hostContext as TransactionalEVMHostContext).getLogs(), stack, memory) + return EVMResult(EVMExecutionStatusCode.SUCCESS, hostContext, hostContext, state) } override fun capabilities(): Int { diff --git a/evm/src/test/kotlin/org/apache/tuweni/evm/EVMReferenceTest.kt b/evm/src/test/kotlin/org/apache/tuweni/evm/EVMReferenceTest.kt index 3041fdd..7db8d60 100644 --- a/evm/src/test/kotlin/org/apache/tuweni/evm/EVMReferenceTest.kt +++ b/evm/src/test/kotlin/org/apache/tuweni/evm/EVMReferenceTest.kt @@ -248,11 +248,11 @@ class EVMReferenceTest { // assertEquals(test.gas, result.gasManager.gasLeft()) if (test.out?.isEmpty == true) { - assertTrue(result.output == null || result.output?.isEmpty ?: false) + assertTrue(result.state.output == null || result.state.output?.isEmpty ?: false) } else { assertEquals( test.out?.let { if (it.size() < 32) Bytes32.rightPad(it) else it }, - result.output?.let { if (it.size() < 32) Bytes32.rightPad(it) else it } + result.state.output?.let { if (it.size() < 32) Bytes32.rightPad(it) else it } ) } } diff --git a/evm/src/test/kotlin/org/apache/tuweni/evm/EthereumVirtualMachineTest.kt b/evm/src/test/kotlin/org/apache/tuweni/evm/EthereumVirtualMachineTest.kt index 59bde0c..0033c74 100644 --- a/evm/src/test/kotlin/org/apache/tuweni/evm/EthereumVirtualMachineTest.kt +++ b/evm/src/test/kotlin/org/apache/tuweni/evm/EthereumVirtualMachineTest.kt @@ -64,28 +64,28 @@ class EthereumVirtualMachineTest { fun testExecuteCall(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x30600052596000f3")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(Gas.valueOf(199984), result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(199984), result.state.gasManager.gasLeft()) } @Test fun testExecuteCounter(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x600160005401600055")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(Gas.valueOf(179488), result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(179488), result.state.gasManager.gasLeft()) } @Test fun testExecuteReturnBlockNumber(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x43600052596000f3")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(Gas.valueOf(199984), result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(199984), result.state.gasManager.gasLeft()) } @Test fun testExecuteSaveReturnBlockNumber(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x4360005543600052596000f3")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(Gas.valueOf(197779), result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(197779), result.state.gasManager.gasLeft()) } @Disabled @@ -157,7 +157,7 @@ class EthereumVirtualMachineTest { ) } assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(20000, result.gasManager.gasLeft()) + assertEquals(20000, result.state.gasManager.gasLeft()) } finally { vm.stop() } @@ -201,7 +201,7 @@ class EthereumVirtualMachineTest { @Test fun snapshotExecution(@LuceneIndexWriter writer: IndexWriter) { val listener = object : StepListener { - override fun halt(executionPath: List<Byte>): Boolean { + override fun handleStep(executionPath: List<Byte>, state: EVMState): Boolean { if (executionPath.size > 3) { return true } @@ -210,13 +210,13 @@ class EthereumVirtualMachineTest { } val result = runCode(writer, Bytes.fromHexString("0x30600052596000f3"), { EvmVmImpl.create(listener) }) assertEquals(EVMExecutionStatusCode.HALTED, result.statusCode) - assertEquals(Gas.valueOf(199987), result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(199987), result.state.gasManager.gasLeft()) } @Test fun snapshotExecutionTooFar(@LuceneIndexWriter writer: IndexWriter) { val listener = object : StepListener { - override fun halt(executionPath: List<Byte>): Boolean { + override fun handleStep(executionPath: List<Byte>, state: EVMState): Boolean { if (executionPath.size > 255) { return true } @@ -225,6 +225,24 @@ class EthereumVirtualMachineTest { } val result = runCode(writer, Bytes.fromHexString("0x30600052596000f3"), { EvmVmImpl.create(listener) }) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(Gas.valueOf(199984), result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(199984), result.state.gasManager.gasLeft()) + } + + @Test + fun testDump(@LuceneIndexWriter writer: IndexWriter) { + val listener = object : StepListener { + override fun handleStep(executionPath: List<Byte>, state: EVMState): Boolean { + if (executionPath.size > 3) { + return true + } + return false + } + } + val result = runCode(writer, Bytes.fromHexString("0x30600052596000f3"), { EvmVmImpl.create(listener) }) + assertEquals(EVMExecutionStatusCode.HALTED, result.statusCode) + assertEquals( + Bytes.fromHexString("0xf85c866d656d6f7279a0000000000000000000000000353363663737323034654565663935326532350085737461636ba00000000000000000000000000000000000000000000000000000000000000020866f757470757480846c6f6773"), + result.state.toBytes() + ) } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@tuweni.apache.org For additional commands, e-mail: commits-h...@tuweni.apache.org