This is an automated email from the ASF dual-hosted git repository. toulmean pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
commit 42cf5ad1cc353499bd04a9d1200006805eeb6e3f Author: Antoine Toulme <[email protected]> AuthorDate: Sun May 31 23:03:00 2020 -0700 Add tests for getting headers --- dependency-versions.gradle | 2 + devp2p-eth/build.gradle | 1 + .../org/apache/tuweni/devp2p/eth/EthHandler.kt | 39 +++--- .../org/apache/tuweni/devp2p/eth/Messages.kt | 8 +- .../org/apache/tuweni/devp2p/eth/EthHandlerTest.kt | 138 +++++++++++++++++++-- .../tuweni/eth/repository/BlockchainIndex.kt | 4 +- .../tuweni/eth/repository/BlockchainRepository.kt | 9 +- 7 files changed, 161 insertions(+), 40 deletions(-) diff --git a/dependency-versions.gradle b/dependency-versions.gradle index bbe202d..f7f75f8 100644 --- a/dependency-versions.gradle +++ b/dependency-versions.gradle @@ -66,6 +66,8 @@ dependencyManagement { } dependency('org.mapdb:mapdb:3.0.7') dependency('org.miracl.milagro.amcl:milagro-crypto-java:0.4.0') + dependency('org.mockito:mockito-junit-jupiter:3.3.3') + dependency('org.rocksdb:rocksdbjni:5.17.2') dependency('org.slf4j:slf4j-api:1.7.30') diff --git a/devp2p-eth/build.gradle b/devp2p-eth/build.gradle index ffd74ac..fb31487 100644 --- a/devp2p-eth/build.gradle +++ b/devp2p-eth/build.gradle @@ -33,6 +33,7 @@ dependencies { testCompile 'org.bouncycastle:bcprov-jdk15on' testCompile 'org.junit.jupiter:junit-jupiter-api' testCompile 'org.junit.jupiter:junit-jupiter-params' + testCompile 'org.mockito:mockito-junit-jupiter' testRuntime 'org.junit.jupiter:junit-jupiter-engine' testRuntime 'ch.qos.logback:logback-classic' diff --git a/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/EthHandler.kt b/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/EthHandler.kt index 8293270..ae16b05 100644 --- a/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/EthHandler.kt +++ b/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/EthHandler.kt @@ -130,30 +130,29 @@ internal class EthHandler( } private suspend fun handleGetBlockHeaders(connectionId: String, blockHeaderRequest: GetBlockHeaders) { - val matches = repository.findBlockByHashOrNumber(blockHeaderRequest.hash) - if (matches.isEmpty()) { - return - } + val matches = repository.findBlockByHashOrNumber(blockHeaderRequest.block) val headers = ArrayList<BlockHeader>() - val header = repository.retrieveBlockHeader(matches[0]) - header?.let { - headers.add(it) - var blockNumber = it.number - for (i in 1..blockHeaderRequest.maxHeaders) { - blockNumber = if (blockHeaderRequest.reverse) { - blockNumber.subtract(blockHeaderRequest.skip) - } else { - blockNumber.add(blockHeaderRequest.skip) - } - val nextMatches = repository.findBlockByHashOrNumber(blockNumber.toBytes()) - if (nextMatches.isEmpty()) { - break + if (matches.isNotEmpty()) { + val header = repository.retrieveBlockHeader(matches[0]) + header?.let { + headers.add(it) + var blockNumber = it.number + for (i in 2..blockHeaderRequest.maxHeaders) { + blockNumber = if (blockHeaderRequest.reverse) { + blockNumber.subtract(blockHeaderRequest.skip + 1) + } else { + blockNumber.add(blockHeaderRequest.skip + 1) + } + val nextMatches = repository.findBlockByHashOrNumber(blockNumber.toBytes()) + if (nextMatches.isEmpty()) { + break + } + val nextHeader = repository.retrieveBlockHeader(nextMatches[0]) ?: break + headers.add(nextHeader) } - val nextHeader = repository.retrieveBlockHeader(nextMatches[0]) ?: break - headers.add(nextHeader) } - service.send(EthSubprotocol.ETH64, MessageType.BlockHeaders.code, connectionId, BlockHeaders(headers).toBytes()) } + service.send(EthSubprotocol.ETH64, MessageType.BlockHeaders.code, connectionId, BlockHeaders(headers).toBytes()) } private suspend fun handleNewBlockHashes(message: NewBlockHashes) { diff --git a/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/Messages.kt b/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/Messages.kt index c3ea031..356b043 100644 --- a/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/Messages.kt +++ b/devp2p-eth/src/main/kotlin/org/apache/tuweni/devp2p/eth/Messages.kt @@ -112,20 +112,20 @@ data class NewBlockHashes(val hashes: List<Pair<Hash, Long>>) { } } -data class GetBlockHeaders(val hash: Hash, val maxHeaders: Long, val skip: Long, val reverse: Boolean) { +data class GetBlockHeaders(val block: Bytes, val maxHeaders: Long, val skip: Long, val reverse: Boolean) { companion object { fun read(payload: Bytes): GetBlockHeaders = RLP.decodeList(payload) { - val hash = Hash.fromBytes(it.readValue()) + val block = it.readValue() val maxHeaders = it.readLong() val skip = it.readLong() val reverse = it.readInt() == 1 - GetBlockHeaders(hash, maxHeaders, skip, reverse) + GetBlockHeaders(block, maxHeaders, skip, reverse) } } fun toBytes(): Bytes = RLP.encodeList { writer -> - writer.writeValue(hash) + writer.writeValue(block) writer.writeLong(maxHeaders) writer.writeLong(skip) writer.writeInt(if (reverse) 1 else 0) diff --git a/devp2p-eth/src/test/kotlin/org/apache/tuweni/devp2p/eth/EthHandlerTest.kt b/devp2p-eth/src/test/kotlin/org/apache/tuweni/devp2p/eth/EthHandlerTest.kt index 86b1ffb..22148ea 100644 --- a/devp2p-eth/src/test/kotlin/org/apache/tuweni/devp2p/eth/EthHandlerTest.kt +++ b/devp2p-eth/src/test/kotlin/org/apache/tuweni/devp2p/eth/EthHandlerTest.kt @@ -16,31 +16,151 @@ */ package org.apache.tuweni.devp2p.eth -import io.vertx.core.Vertx import kotlinx.coroutines.runBlocking import org.apache.lucene.index.IndexWriter +import org.apache.tuweni.bytes.Bytes +import org.apache.tuweni.bytes.Bytes32 import org.apache.tuweni.concurrent.coroutines.await import org.apache.tuweni.crypto.SECP256K1 -import org.apache.tuweni.eth.genesis.GenesisFile +import org.apache.tuweni.devp2p.eth.EthSubprotocol.Companion.ETH64 +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.repository.BlockchainIndex import org.apache.tuweni.eth.repository.BlockchainRepository import org.apache.tuweni.junit.BouncyCastleExtension import org.apache.tuweni.junit.LuceneIndexWriter import org.apache.tuweni.junit.LuceneIndexWriterExtension import org.apache.tuweni.junit.VertxExtension -import org.apache.tuweni.junit.VertxInstance import org.apache.tuweni.kv.MapKeyValueStore -import org.apache.tuweni.rlpx.vertx.VertxRLPxService +import org.apache.tuweni.rlp.RLP +import org.apache.tuweni.rlpx.RLPxService import org.apache.tuweni.units.bigints.UInt256 -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Disabled +import org.apache.tuweni.units.bigints.UInt64 +import org.apache.tuweni.units.ethereum.Gas +import org.apache.tuweni.units.ethereum.Wei +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import java.net.InetSocketAddress +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import java.time.Instant +import java.time.temporal.ChronoUnit @ExtendWith(LuceneIndexWriterExtension::class, VertxExtension::class, BouncyCastleExtension::class) class EthHandlerTest { + private lateinit var genesisBlock: Block + private lateinit var handler: EthHandler + private lateinit var repository: BlockchainRepository + private lateinit var service: RLPxService + + @BeforeEach + fun setup(@LuceneIndexWriter writer: IndexWriter) = runBlocking { + val header = BlockHeader( + Hash.fromBytes(Bytes32.random()), + Hash.fromBytes(Bytes32.random()), + Address.fromBytes(Bytes.random(20)), + Hash.fromBytes(Bytes32.random()), + Hash.fromBytes(Bytes32.random()), + Hash.fromBytes(Bytes32.random()), + Bytes32.random(), + UInt256.fromBytes(Bytes32.random()), + UInt256.ZERO, + Gas.valueOf(3000), + Gas.valueOf(2000), + Instant.now().plusSeconds(30).truncatedTo(ChronoUnit.SECONDS), + Bytes.of(2, 3, 4, 5, 6, 7, 8, 9, 10), + Hash.fromBytes(Bytes32.random()), + UInt64.ZERO + ) + val body = BlockBody( + listOf( + Transaction( + UInt256.valueOf(1), + Wei.valueOf(2), + Gas.valueOf(2), + Address.fromBytes(Bytes.random(20)), + Wei.valueOf(2), + Bytes.random(12), + SECP256K1.KeyPair.random() + ) + ), + listOf(header) + ) + genesisBlock = Block(header, body) + repository = BlockchainRepository.init( + MapKeyValueStore(), + MapKeyValueStore(), + MapKeyValueStore(), + MapKeyValueStore(), + BlockchainIndex(writer), + genesisBlock + ) + service = mock(RLPxService::class.java) + handler = EthHandler( + blockchainInfo = SimpleBlockchainInformation( + UInt256.valueOf(42L), + UInt256.ONE, + genesisBlock.header.hash, + genesisBlock.header.hash, + emptyList() + ), + service = service, + repository = repository + ) + } + + private fun createChildBlockHeader(parentBlock: BlockHeader): BlockHeader { + val emptyListHash = Hash.hash(RLP.encodeList { }) + val emptyHash = Hash.hash(RLP.encode { writer -> writer.writeValue(Bytes.EMPTY) }) + return BlockHeader( + parentBlock.hash, + emptyListHash, + Address.fromBytes(Bytes.random(20)), + parentBlock.stateRoot, + emptyHash, + emptyHash, + Bytes.wrap(ByteArray(256)), + parentBlock.difficulty, + parentBlock.number.add(1), + parentBlock.gasLimit, + Gas.valueOf(0), + Instant.now(), + Bytes.random(45), + Hash.fromBytes(Bytes32.random()), + UInt64.ZERO + ) + } + + @Test + fun testGetHeaders() = runBlocking { + var header = repository.retrieveGenesisBlock().header + for (i in 1..10) { + val newHeader = createChildBlockHeader(header) + repository.storeBlockHeader(newHeader) + header = newHeader + } + + handler.handle( + "foo", + MessageType.GetBlockHeaders.code, + GetBlockHeaders(genesisBlock.header.hash, 3, 1, false).toBytes() + ).await() + + val messageCapture = ArgumentCaptor.forClass(Bytes::class.java) + verify(service).send(eq(ETH64), eq(MessageType.BlockHeaders.code), eq("foo"), messageCapture.capture()) + + val messageRead = BlockHeaders.read(messageCapture.value) + assertEquals(3, messageRead.headers.size) + assertEquals(UInt256.ZERO, messageRead.headers[0].number) + assertEquals(UInt256.valueOf(2), messageRead.headers[1].number) + assertEquals(UInt256.valueOf(4), messageRead.headers[2].number) + } } diff --git a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt index ce7cf6a..a4b0edd 100644 --- a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt +++ b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainIndex.kt @@ -143,7 +143,7 @@ interface BlockchainIndexReader { * @param hashOrNumber the hash of a block header, or its number as a 32-byte word * @return the matching block header hashes. */ - fun findByHashOrNumber(hashOrNumber: Bytes32): List<Hash> + fun findByHashOrNumber(hashOrNumber: Bytes): List<Hash> /** * Find a value in a range. @@ -582,7 +582,7 @@ class BlockchainIndex(private val indexWriter: IndexWriter) : BlockchainIndexWri ).firstOrNull() } - override fun findByHashOrNumber(hashOrNumber: Bytes32): List<Hash> { + override fun findByHashOrNumber(hashOrNumber: Bytes): List<Hash> { val query = BooleanQuery.Builder() .setMinimumNumberShouldMatch(1) .add(BooleanClause(TermQuery(Term("_id", toBytesRef(hashOrNumber))), BooleanClause.Occur.SHOULD)) diff --git a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt index aa9f1ca..6cb0887 100644 --- a/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt +++ b/eth-repository/src/main/kotlin/org/apache/tuweni/eth/repository/BlockchainRepository.kt @@ -17,7 +17,6 @@ package org.apache.tuweni.eth.repository import org.apache.tuweni.bytes.Bytes -import org.apache.tuweni.bytes.Bytes32 import org.apache.tuweni.eth.Block import org.apache.tuweni.eth.BlockBody import org.apache.tuweni.eth.BlockHeader @@ -235,7 +234,7 @@ class BlockchainRepository */ suspend fun retrieveChainHeadHeader(): BlockHeader? { return blockchainIndex.findByLargest(BlockHeaderFields.TOTAL_DIFFICULTY) - ?.let { retrieveBlockHeader(it) } ?: retrieveGenesisBlock()?.getHeader() + ?.let { retrieveBlockHeader(it) } ?: retrieveGenesisBlock().getHeader() } /** @@ -243,8 +242,8 @@ class BlockchainRepository * * @return the genesis block */ - suspend fun retrieveGenesisBlock(): Block? { - return chainMetadata.get(GENESIS_BLOCK)?.let { retrieveBlock(it) } + suspend fun retrieveGenesisBlock(): Block { + return chainMetadata.get(GENESIS_BLOCK).let { retrieveBlock(it!!)!! } } /** @@ -284,7 +283,7 @@ class BlockchainRepository * @param blockNumberOrBlockHash the number or hash of the block * @return the matching blocks */ - fun findBlockByHashOrNumber(blockNumberOrBlockHash: Bytes32): List<Hash> { + fun findBlockByHashOrNumber(blockNumberOrBlockHash: Bytes): List<Hash> { return blockchainIndex.findByHashOrNumber(blockNumberOrBlockHash) } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
