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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]