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

Reply via email to