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