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 2479c3a  add block processor lib
     new b6daad7  Merge pull request #383 from atoulme/block_processor
2479c3a is described below

commit 2479c3abe01e58fa80da87e86aec8ffcbf04c926
Author: Antoine Toulme <anto...@lunar-ocean.com>
AuthorDate: Wed Mar 16 22:24:01 2022 -0700

    add block processor lib
---
 eth-blockprocessor/build.gradle                    |  52 +++++++
 .../apache/tuweni/blockprocessor/BlockProcessor.kt | 159 +++++++++++++++++++++
 .../org/apache/tuweni/blockprocessor/ProtoBlock.kt | 114 +++++++++++++++
 .../tuweni/blockprocessor/BlockProcessorTest.kt    |  46 ++++++
 settings.gradle                                    |   3 +-
 5 files changed, 373 insertions(+), 1 deletion(-)

diff --git a/eth-blockprocessor/build.gradle b/eth-blockprocessor/build.gradle
new file mode 100644
index 0000000..c656367
--- /dev/null
+++ b/eth-blockprocessor/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding 
copyright ownership. The ASF licenses this file
+ * to You under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+description = 'Ethereum Block Processor'
+
+dependencies {
+  implementation 'com.fasterxml.jackson.core:jackson-databind'
+  implementation 'dnsjava:dnsjava'
+  implementation 'io.opentelemetry:opentelemetry-api'
+  implementation 'io.opentelemetry:opentelemetry-sdk'
+  implementation 'io.opentelemetry:opentelemetry-sdk-metrics'
+  implementation 'io.vertx:vertx-core'
+  implementation 'org.fusesource.leveldbjni:leveldbjni-all'
+  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
+  implementation 'org.apache.lucene:lucene-core'
+  implementation 'org.infinispan:infinispan-core'
+  implementation 'org.infinispan:infinispan-cachestore-rocksdb'
+
+  implementation project(':bytes')
+  implementation project(':concurrent-coroutines')
+  implementation project(':crypto')
+  implementation project(':eth')
+  implementation project(':genesis')
+  implementation project(':eth-repository')
+  implementation project(':evm')
+  implementation project(':kv')
+  implementation project(':metrics')
+  implementation project(':rlp')
+  implementation project(':units')
+  implementation project(':merkle-trie')
+
+  testImplementation project(':junit')
+  testImplementation 'org.bouncycastle:bcprov-jdk15on'
+  testImplementation 'org.junit.jupiter:junit-jupiter-api'
+  testImplementation 'org.junit.jupiter:junit-jupiter-params'
+  testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin'
+  testImplementation 'org.mockito:mockito-junit-jupiter'
+  testImplementation 'ch.qos.logback:logback-classic'
+
+  testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
+
+  runtimeOnly 'ch.qos.logback:logback-classic'
+}
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
new file mode 100644
index 0000000..baf537a
--- /dev/null
+++ 
b/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/BlockProcessor.kt
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tuweni.blockprocessor
+
+import org.apache.tuweni.eth.AccountState
+import org.apache.tuweni.eth.Address
+import org.apache.tuweni.eth.Block
+import org.apache.tuweni.eth.Hash
+import org.apache.tuweni.eth.LogsBloomFilter
+import org.apache.tuweni.eth.Transaction
+import org.apache.tuweni.eth.TransactionReceipt
+import org.apache.tuweni.eth.repository.BlockchainRepository
+import org.apache.tuweni.evm.EVMExecutionStatusCode
+import org.apache.tuweni.evm.EthereumVirtualMachine
+import org.apache.tuweni.evm.impl.EvmVmImpl
+import org.apache.tuweni.rlp.RLP
+import org.apache.tuweni.trie.MerklePatriciaTrie
+import org.apache.tuweni.trie.MerkleTrie
+import org.apache.tuweni.units.bigints.UInt256
+import org.apache.tuweni.units.ethereum.Gas
+import org.apache.tuweni.units.ethereum.Wei
+import java.time.Instant
+
+/**
+ * A block processor executing blocks, executing in sequence transactions
+ * and committing data to a blockchain repository.
+ */
+class BlockProcessor {
+
+  suspend fun execute(parentBlock: Block, transactions: List<Transaction>, 
repository: BlockchainRepository): ProtoBlock {
+    val vm = EthereumVirtualMachine(repository, EvmVmImpl::create)
+    vm.start()
+    var index = 0L
+
+    val bloomFilter = LogsBloomFilter()
+
+    val transactionsTrie = MerklePatriciaTrie.storingBytes()
+    val receiptsTrie = MerklePatriciaTrie.storingBytes()
+    val allReceipts = mutableListOf<TransactionReceipt>()
+
+    var counter = 0L
+    var allGasUsed = Gas.ZERO
+    for (tx in transactions) {
+      val indexKey = 
RLP.encodeValue(UInt256.valueOf(counter).trimLeadingZeros())
+      transactionsTrie.put(indexKey, tx.toBytes())
+      if (null == tx.to) {
+        val contractAddress = Address.fromBytes(
+          Hash.hash(
+            RLP.encodeList {
+              it.writeValue(tx.sender!!)
+              it.writeValue(tx.nonce)
+            }
+          ).slice(12)
+        )
+        val state = AccountState(
+          UInt256.ONE,
+          Wei.valueOf(0),
+          Hash.fromBytes(MerkleTrie.EMPTY_TRIE_ROOT_HASH),
+          org.apache.tuweni.eth.Hash.hash(tx.payload)
+        )
+        repository.storeAccount(contractAddress, state)
+        repository.storeCode(tx.payload)
+        val receipt = TransactionReceipt(
+          1,
+          0, // TODO
+          LogsBloomFilter(),
+          emptyList()
+        )
+        allReceipts.add(receipt)
+        receiptsTrie.put(indexKey, receipt.toBytes())
+        counter++
+      } else {
+        val code = repository.getAccountCode(tx.to!!)
+        val result = vm.execute(
+          tx.sender!!,
+          tx.to!!,
+          tx.value,
+          code!!,
+          tx.payload,
+          parentBlock.header.gasLimit,
+          tx.gasPrice,
+          Address.ZERO,
+          index,
+          Instant.now().toEpochMilli(),
+          tx.gasLimit.toLong(),
+          parentBlock.header.difficulty
+        )
+        if (result.statusCode != EVMExecutionStatusCode.SUCCESS) {
+          throw Exception("invalid transaction result")
+        }
+        for (balanceChange in result.changes.getBalanceChanges()) {
+          val state = repository.getAccount(balanceChange.key)?.let {
+            AccountState(it.nonce, balanceChange.value, it.storageRoot, 
it.codeHash)
+          } ?: repository.newAccountState()
+          repository.storeAccount(balanceChange.key, state)
+        }
+
+        for (storageChange in result.changes.getAccountChanges()) {
+          for (oneStorageChange in storageChange.value) {
+            repository.storeAccountValue(storageChange.key, 
oneStorageChange.key, oneStorageChange.value)
+          }
+        }
+
+        for (accountToDestroy in result.changes.accountsToDestroy()) {
+          repository.destroyAccount(accountToDestroy)
+        }
+        for (log in result.changes.getLogs()) {
+          bloomFilter.insertLog(log)
+        }
+
+        val txLogsBloomFilter = LogsBloomFilter()
+        for (log in result.changes.getLogs()) {
+          bloomFilter.insertLog(log)
+        }
+        val receipt = TransactionReceipt(
+          1,
+          result.gasManager.gasCost.toLong(),
+          txLogsBloomFilter,
+          result.changes.getLogs()
+        )
+        allReceipts.add(receipt)
+        receiptsTrie.put(indexKey, receipt.toBytes())
+        counter++
+
+        allGasUsed = allGasUsed.add(result.gasManager.gasCost)
+      }
+    }
+
+    val block = ProtoBlock(
+      SealableHeader(
+        parentBlock.header.hash,
+        Hash.fromBytes(repository.worldState!!.rootHash()),
+        Hash.fromBytes(transactionsTrie.rootHash()),
+        Hash.fromBytes(receiptsTrie.rootHash()),
+        bloomFilter.toBytes(),
+        parentBlock.header.number.add(1),
+        parentBlock.header.gasLimit,
+        allGasUsed,
+      ),
+      ProtoBlockBody(transactions),
+      allReceipts
+    )
+    return block
+  }
+}
diff --git 
a/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/ProtoBlock.kt
 
b/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/ProtoBlock.kt
new file mode 100644
index 0000000..c747840
--- /dev/null
+++ 
b/eth-blockprocessor/src/main/kotlin/org/apache/tuweni/blockprocessor/ProtoBlock.kt
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tuweni.blockprocessor
+
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.eth.Address
+import org.apache.tuweni.eth.Block
+import org.apache.tuweni.eth.BlockBody
+import org.apache.tuweni.eth.BlockHeader
+import org.apache.tuweni.eth.Hash
+import org.apache.tuweni.eth.Transaction
+import org.apache.tuweni.eth.TransactionReceipt
+import org.apache.tuweni.rlp.RLP
+import org.apache.tuweni.units.bigints.UInt256
+import org.apache.tuweni.units.bigints.UInt64
+import org.apache.tuweni.units.ethereum.Gas
+import java.time.Instant
+
+/**
+ * A block header that has not finished being sealed.
+ */
+data class SealableHeader(
+  val parentHash: Hash,
+  val stateRoot: Hash,
+  val transactionsRoot: Hash,
+  val receiptsRoot: Hash,
+  val logsBloom: Bytes,
+  val number: UInt256,
+  val gasLimit: Gas,
+  val gasUsed: Gas,
+) {
+
+  /**
+   * Seals the header into a block header
+   */
+  fun toHeader(
+    ommersHash: Hash,
+    coinbase: Address,
+    difficulty: UInt256,
+    timestamp: Instant,
+    extraData: Bytes,
+    mixHash: Hash,
+    nonce: UInt64,
+  ): BlockHeader {
+    return BlockHeader(
+      parentHash,
+      ommersHash,
+      coinbase,
+      stateRoot,
+      transactionsRoot,
+      receiptsRoot,
+      logsBloom,
+      difficulty,
+      number,
+      gasLimit,
+      gasUsed,
+      timestamp,
+      extraData,
+      mixHash,
+      nonce
+    )
+  }
+}
+
+/**
+ * A proto-block body is the representation of the intermediate form of a 
block body before being sealed.
+ */
+data class ProtoBlockBody(val transactions: List<Transaction>) {
+  /**
+   * Transforms the proto-block body into a valid block body by adding ommers.
+   */
+  fun toBlockBody(ommers: List<BlockHeader>): BlockBody {
+    return BlockBody(transactions, ommers)
+  }
+}
+
+/**
+ * A proto-block is a block that has been executed but has not been sealed.
+ * The header is missing the nonce and mixhash, and can still accept extra 
data.
+ *
+ * Proto-blocks are produced when transactions are executed, and can be turned 
into full valid blocks.
+ */
+class ProtoBlock(val header: SealableHeader, val body: ProtoBlockBody, val 
transactionReceipts: List<TransactionReceipt>) {
+
+  fun toBlock(
+    ommers: List<BlockHeader>,
+    coinbase: Address,
+    difficulty: UInt256,
+    timestamp: Instant,
+    extraData: Bytes,
+    mixHash: Hash,
+    nonce: UInt64,
+  ): Block {
+    val ommersHash = Hash.hash(RLP.encodeList { writer -> ommers.forEach { 
writer.writeValue(it.hash) } })
+    return Block(
+      header.toHeader(ommersHash, coinbase, difficulty, timestamp, extraData, 
mixHash, nonce),
+      body.toBlockBody(ommers)
+    )
+  }
+}
diff --git 
a/eth-blockprocessor/src/test/kotlin/org/apache/tuweni/blockprocessor/BlockProcessorTest.kt
 
b/eth-blockprocessor/src/test/kotlin/org/apache/tuweni/blockprocessor/BlockProcessorTest.kt
new file mode 100644
index 0000000..b132836
--- /dev/null
+++ 
b/eth-blockprocessor/src/test/kotlin/org/apache/tuweni/blockprocessor/BlockProcessorTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tuweni.blockprocessor
+
+import kotlinx.coroutines.runBlocking
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.eth.Address
+import org.apache.tuweni.eth.repository.BlockchainRepository
+import org.apache.tuweni.genesis.Genesis
+import org.apache.tuweni.junit.BouncyCastleExtension
+import org.apache.tuweni.units.bigints.UInt256
+import org.apache.tuweni.units.bigints.UInt64
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import java.time.Instant
+
+@ExtendWith(BouncyCastleExtension::class)
+class BlockProcessorTest {
+
+  /**
+   * Demonstrate how the proto block can be created.
+   */
+  @Test
+  fun testValidBlockNoTransactions() = runBlocking {
+    val processor = BlockProcessor()
+    val repository = BlockchainRepository.inMemory(Genesis.dev())
+    val protoBlock = processor.execute(Genesis.dev(), listOf(), repository)
+    val block = protoBlock.toBlock(listOf(), Address.ZERO, UInt256.ONE, 
Instant.now(), Bytes.EMPTY, Genesis.emptyHash, UInt64.random())
+    assertEquals(0, block.body.transactions.size)
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index b798cc2..7c27b1c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -23,16 +23,17 @@ include 'devp2p-proxy'
 include 'dist'
 include 'dns-discovery'
 include 'eth'
+include 'eth-blockprocessor'
 include 'eth-client'
 include 'eth-client-app'
 include 'eth-client-ui'
 include 'eth-crawler'
+include 'eth-faucet'
 include 'eth2-reference-tests'
 include 'eth-reference-tests'
 include 'eth-repository'
 include 'ethstats'
 include 'evm'
-include 'eth-faucet'
 include 'genesis'
 include 'gossip'
 include 'hobbits'

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@tuweni.apache.org
For additional commands, e-mail: commits-h...@tuweni.apache.org

Reply via email to