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 3350ac7 Add a way to halt the EVM execution new 5caadfe Merge pull request #381 from atoulme/evm_halt 3350ac7 is described below commit 3350ac7b282617fccd1e7e05e78b8b8fffc48b45 Author: Antoine Toulme <anto...@lunar-ocean.com> AuthorDate: Tue Mar 15 22:32:25 2022 -0700 Add a way to halt the EVM execution --- .../apache/tuweni/evm/EthereumVirtualMachine.kt | 7 +- .../kotlin/org/apache/tuweni/evm/impl/EvmVmImpl.kt | 44 +++++++-- .../tuweni/evm/EthereumVirtualMachineTest.kt | 110 ++++++++++++++------- 3 files changed, 113 insertions(+), 48 deletions(-) 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 f39de16..655c51c 100644 --- a/evm/src/main/kotlin/org/apache/tuweni/evm/EthereumVirtualMachine.kt +++ b/evm/src/main/kotlin/org/apache/tuweni/evm/EthereumVirtualMachine.kt @@ -22,6 +22,8 @@ import org.apache.tuweni.eth.Address import org.apache.tuweni.eth.Log 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.units.bigints.UInt256 import org.apache.tuweni.units.ethereum.Gas import org.apache.tuweni.units.ethereum.Wei @@ -60,7 +62,8 @@ enum class EVMExecutionStatusCode(val number: Int) { WASM_TRAP(16), INTERNAL_ERROR(-1), REJECTED(-2), - OUT_OF_MEMORY(-3); + OUT_OF_MEMORY(-3), + HALTED(-4); } /** @@ -100,6 +103,8 @@ data class EVMResult( val gasManager: GasManager, val hostContext: HostContext, val changes: ExecutionChanges, + val stack: Stack, + val memory: Memory, val output: Bytes? = null, ) 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 0c9f90d..7857450 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 @@ -34,11 +34,23 @@ data class Result( val validationStatus: EVMExecutionStatusCode? = null, ) -class EvmVmImpl : EvmVm { +/** + * A listener that is executed at the end of each step. + */ +interface StepListener { + /** + * Checks the execution path + * + * @return true to halt the execution + */ + fun halt(executionPath: List<Byte>): Boolean +} + +class EvmVmImpl(val stepListener: StepListener? = null) : EvmVm { companion object { - fun create(): EvmVm { - return EvmVmImpl() + fun create(stepListener: StepListener? = null): EvmVm { + return EvmVmImpl(stepListener) } val registry = OpcodeRegistry.create() val logger = LoggerFactory.getLogger(EvmVmImpl::class.java) @@ -73,7 +85,7 @@ class EvmVmImpl : 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) + return EVMResult(EVMExecutionStatusCode.INVALID_INSTRUCTION, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) } val currentOpcodeByte = code.get(current) current++ @@ -88,27 +100,39 @@ class EvmVmImpl : EvmVm { 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) + return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) } - return EVMResult(result.status, gasManager, hostContext, hostContext as TransactionalEVMHostContext, result.output) + return EVMResult(result.status, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory, result.output) } result?.newCodePosition?.let { current = result.newCodePosition } if (!gasManager.hasGasLeft()) { - return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, gasManager, hostContext, hostContext as TransactionalEVMHostContext) + return EVMResult(EVMExecutionStatusCode.OUT_OF_GAS, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) } if (stack.overflowed()) { - return EVMResult(EVMExecutionStatusCode.STACK_OVERFLOW, gasManager, hostContext, hostContext as TransactionalEVMHostContext) + return EVMResult(EVMExecutionStatusCode.STACK_OVERFLOW, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) } if (result?.validationStatus != null) { - return EVMResult(result.validationStatus, gasManager, hostContext, hostContext as TransactionalEVMHostContext) + return EVMResult(result.validationStatus, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) + } + stepListener?.halt(executionPath)?.let { + if (it) { + return EVMResult( + EVMExecutionStatusCode.HALTED, + gasManager, + hostContext, + hostContext as TransactionalEVMHostContext, + stack, + memory + ) + } } } if (logger.isTraceEnabled) { logger.trace(executionPath.map { opcodes[it] ?: it.toString(16) }.joinToString(">")) } - return EVMResult(EVMExecutionStatusCode.SUCCESS, gasManager, hostContext, hostContext as TransactionalEVMHostContext) + return EVMResult(EVMExecutionStatusCode.SUCCESS, gasManager, hostContext, hostContext as TransactionalEVMHostContext, stack, memory) } override fun capabilities(): Int { 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 a9e1f24..59bde0c 100644 --- a/evm/src/test/kotlin/org/apache/tuweni/evm/EthereumVirtualMachineTest.kt +++ b/evm/src/test/kotlin/org/apache/tuweni/evm/EthereumVirtualMachineTest.kt @@ -23,6 +23,8 @@ import org.apache.tuweni.eth.Address import org.apache.tuweni.eth.repository.BlockchainIndex import org.apache.tuweni.eth.repository.BlockchainRepository import org.apache.tuweni.evm.impl.EvmVmImpl +import org.apache.tuweni.evm.impl.StepListener +import org.apache.tuweni.genesis.Genesis import org.apache.tuweni.junit.BouncyCastleExtension import org.apache.tuweni.junit.LuceneIndexWriter import org.apache.tuweni.junit.LuceneIndexWriterExtension @@ -37,24 +39,24 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.nio.charset.StandardCharsets -@Disabled @ExtendWith(LuceneIndexWriterExtension::class, BouncyCastleExtension::class) class EthereumVirtualMachineTest { @Test fun testVersion(@LuceneIndexWriter writer: IndexWriter) = runBlocking { - val repository = BlockchainRepository( + val repository = BlockchainRepository.init( MapKeyValueStore(), MapKeyValueStore(), MapKeyValueStore(), MapKeyValueStore(), MapKeyValueStore(), MapKeyValueStore(), - BlockchainIndex(writer) + BlockchainIndex(writer), + Genesis.dev() ) val vm = EthereumVirtualMachine(repository, EvmVmImpl::create) vm.start() - assertEquals("0.0.0", vm.version()) + assertEquals("0.0.1", vm.version()) vm.stop() } @@ -62,30 +64,31 @@ class EthereumVirtualMachineTest { fun testExecuteCall(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x30600052596000f3")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(0, result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(199984), result.gasManager.gasLeft()) } @Test fun testExecuteCounter(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x600160005401600055")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(0, result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(179488), result.gasManager.gasLeft()) } @Test fun testExecuteReturnBlockNumber(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x43600052596000f3")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(100000, result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(199984), result.gasManager.gasLeft()) } @Test fun testExecuteSaveReturnBlockNumber(@LuceneIndexWriter writer: IndexWriter) { val result = runCode(writer, Bytes.fromHexString("0x4360005543600052596000f3")) assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) - assertEquals(100000, result.gasManager.gasLeft()) + assertEquals(Gas.valueOf(197779), result.gasManager.gasLeft()) } + @Disabled @Test @Throws(Exception::class) fun testGetCapabilities(@LuceneIndexWriter writer: IndexWriter) = runBlocking { @@ -104,6 +107,7 @@ class EthereumVirtualMachineTest { vm.stop() } + @Disabled @Test @Throws(Exception::class) fun testSetOption(@LuceneIndexWriter writer: IndexWriter) = runBlocking { @@ -159,36 +163,68 @@ class EthereumVirtualMachineTest { } } - private fun runCode(writer: IndexWriter, code: Bytes): EVMResult = runBlocking { - val repository = BlockchainRepository( - MapKeyValueStore(), - MapKeyValueStore(), - MapKeyValueStore(), - MapKeyValueStore(), - MapKeyValueStore(), - MapKeyValueStore(), - BlockchainIndex(writer) - ) - - val vm = EthereumVirtualMachine(repository, EvmVmImpl::create) - vm.start() - try { - val sender = Address.fromHexString("0x3339626637316465316237643762653362353100") - val destination = Address.fromBytes(Bytes.fromHexString("3533636637373230346545656639353265323500")) - val value = Bytes.fromHexString("0x3100") - val inputData = Bytes.wrap("hello w\u0000".toByteArray(StandardCharsets.UTF_8)) - val gas = Gas.valueOf(200000) - vm.execute( - sender, destination, value, code, inputData, gas, - Wei.valueOf(0), - Address.fromBytes(Bytes.random(20)), - 0, - 0, - 2, - UInt256.valueOf(1) + private fun runCode(writer: IndexWriter, code: Bytes, vmFn: () -> EvmVm = { EvmVmImpl.create() }): EVMResult = + runBlocking { + val repository = BlockchainRepository.init( + MapKeyValueStore(), + MapKeyValueStore(), + MapKeyValueStore(), + MapKeyValueStore(), + MapKeyValueStore(), + MapKeyValueStore(), + BlockchainIndex(writer), + Genesis.dev() ) - } finally { - vm.stop() + + val vm = EthereumVirtualMachine(repository, vmFn) + vm.start() + try { + val sender = Address.fromHexString("0x3339626637316465316237643762653362353100") + val destination = Address.fromBytes(Bytes.fromHexString("3533636637373230346545656639353265323500")) + val value = Bytes.fromHexString("0x3100") + val inputData = Bytes.wrap("hello w\u0000".toByteArray(StandardCharsets.UTF_8)) + val gas = Gas.valueOf(200000) + vm.execute( + sender, destination, value, code, inputData, gas, + Wei.valueOf(0), + Address.fromBytes(Bytes.random(20)), + 0, + 0, + 2, + UInt256.valueOf(1) + ) + } finally { + vm.stop() + } } + + @Test + fun snapshotExecution(@LuceneIndexWriter writer: IndexWriter) { + val listener = object : StepListener { + override fun halt(executionPath: List<Byte>): 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(Gas.valueOf(199987), result.gasManager.gasLeft()) + } + + @Test + fun snapshotExecutionTooFar(@LuceneIndexWriter writer: IndexWriter) { + val listener = object : StepListener { + override fun halt(executionPath: List<Byte>): Boolean { + if (executionPath.size > 255) { + return true + } + return false + } + } + val result = runCode(writer, Bytes.fromHexString("0x30600052596000f3"), { EvmVmImpl.create(listener) }) + assertEquals(EVMExecutionStatusCode.SUCCESS, result.statusCode) + assertEquals(Gas.valueOf(199984), result.gasManager.gasLeft()) } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@tuweni.apache.org For additional commands, e-mail: commits-h...@tuweni.apache.org