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

Reply via email to