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