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 1b023b3 Add bootnode script new 0c800b5 Merge pull request #357 from atoulme/bootnode 1b023b3 is described below commit 1b023b323e70dcea8c026e5ffe4186840135ad5b Author: Antoine Toulme <anto...@lunar-ocean.com> AuthorDate: Sun Jan 9 00:37:25 2022 -0800 Add bootnode script --- eth-client-app/build.gradle | 9 ++++ .../org/apache/tuweni/ethclient/BootnodeApp.kt | 53 +++++++++++++++++++ eth-client-app/src/main/resources/bootnode.toml | 21 ++++++++ .../org/apache/tuweni/ethclient/EthereumClient.kt | 28 +++++++++- .../tuweni/ethclient/EthereumClientConfig.kt | 60 ++++++++++++++++++++++ 5 files changed, 170 insertions(+), 1 deletion(-) diff --git a/eth-client-app/build.gradle b/eth-client-app/build.gradle index 20068de..26685dc 100644 --- a/eth-client-app/build.gradle +++ b/eth-client-app/build.gradle @@ -39,3 +39,12 @@ application { mainClassName = 'org.apache.tuweni.ethclient.EthereumClientAppKt' applicationName = 'tuweni' } + +tasks.register("bootnode", CreateStartScripts) { + applicationName = "bootnode" + outputDir = file("build/scripts") + mainClassName = 'org.apache.tuweni.ethclient.BootnodeAppKt' + classpath = project.tasks.getAt(JavaPlugin.JAR_TASK_NAME).outputs.files.plus(project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)) +} + +assemble.dependsOn "bootnode" diff --git a/eth-client-app/src/main/kotlin/org/apache/tuweni/ethclient/BootnodeApp.kt b/eth-client-app/src/main/kotlin/org/apache/tuweni/ethclient/BootnodeApp.kt new file mode 100644 index 0000000..741d3fb --- /dev/null +++ b/eth-client-app/src/main/kotlin/org/apache/tuweni/ethclient/BootnodeApp.kt @@ -0,0 +1,53 @@ +/* + * 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.ethclient + +import io.vertx.core.Vertx +import kotlinx.coroutines.runBlocking +import org.apache.tuweni.app.commons.ApplicationUtils +import org.bouncycastle.jce.provider.BouncyCastleProvider +import picocli.CommandLine +import java.security.Security +import kotlin.system.exitProcess + +fun main(args: Array<String>) = runBlocking { + ApplicationUtils.renderBanner("Apache Tuweni bootnode loading") + Security.addProvider(BouncyCastleProvider()) + val opts = CommandLine.populateCommand(BootnodeAppOptions(), *args) + + if (opts.help) { + CommandLine(opts).usage(System.out) + exitProcess(0) + } + if (opts.version) { + println("Apache Tuweni Bootnode #{ApplicationUtils.version}") + exitProcess(0) + } + + val config = EthereumClientConfig.fromString(this.javaClass.getResource("/bootnode.toml")!!.readText()) + val ethClient = EthereumClient(Vertx.vertx(), config) + Runtime.getRuntime().addShutdownHook(Thread { ethClient.stop() }) + ethClient.start() +} + +class BootnodeAppOptions { + @CommandLine.Option(names = ["-h", "--help"], description = ["Prints usage prompt"]) + var help: Boolean = false + + @CommandLine.Option(names = ["-v", "--version"], description = ["Prints version"]) + var version: Boolean = false +} diff --git a/eth-client-app/src/main/resources/bootnode.toml b/eth-client-app/src/main/resources/bootnode.toml new file mode 100644 index 0000000..bad2924 --- /dev/null +++ b/eth-client-app/src/main/resources/bootnode.toml @@ -0,0 +1,21 @@ +# 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. +[metrics] +networkInterface="127.0.0.1" +port=9092 +[discovery.default] +networkInterface="0.0.0.0" +port=30303 +peerRepository="default" \ No newline at end of file diff --git a/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClient.kt b/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClient.kt index 48f729b..5c95ba7 100644 --- a/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClient.kt +++ b/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClient.kt @@ -27,6 +27,7 @@ import org.apache.lucene.store.NIOFSDirectory import org.apache.tuweni.bytes.Bytes import org.apache.tuweni.concurrent.AsyncCompletion import org.apache.tuweni.concurrent.coroutines.await +import org.apache.tuweni.devp2p.DiscoveryService import org.apache.tuweni.devp2p.eth.EthRequestsManager import org.apache.tuweni.devp2p.eth.EthSubprotocol import org.apache.tuweni.devp2p.eth.EthSubprotocol.Companion.ETH66 @@ -39,11 +40,11 @@ import org.apache.tuweni.eth.genesis.GenesisFile import org.apache.tuweni.eth.repository.BlockchainIndex import org.apache.tuweni.eth.repository.BlockchainRepository import org.apache.tuweni.eth.repository.MemoryTransactionPool -import org.apache.tuweni.metrics.MetricsService import org.apache.tuweni.kv.InfinispanKeyValueStore import org.apache.tuweni.kv.LevelDBKeyValueStore import org.apache.tuweni.kv.MapKeyValueStore import org.apache.tuweni.kv.PersistenceMarshaller +import org.apache.tuweni.metrics.MetricsService import org.apache.tuweni.rlpx.RLPxService import org.apache.tuweni.rlpx.vertx.VertxRLPxService import org.apache.tuweni.units.bigints.UInt256 @@ -76,6 +77,7 @@ class EthereumClient( private val storageRepositories = HashMap<String, BlockchainRepository>() val peerRepositories = HashMap<String, EthereumPeerRepository>() private val dnsClients = HashMap<String, DNSClient>() + private val discoveryServices = HashMap<String, DiscoveryService>() private val synchronizers = HashMap<String, Synchronizer>() private val managerHandler = mutableListOf<DefaultCacheManager>() @@ -151,6 +153,29 @@ class EthereumClient( logger.info("Started DNS client ${it.getName()} for ${it.enrLink()}") } + config.discoveryServices().forEach { + val peerRepository = peerRepositories[it.getPeerRepository()] + if (peerRepository == null) { + val message = ( + if (peerRepositories.isEmpty()) "none" else peerRepositories.keys.joinToString( + "," + ) + ) + " defined" + throw IllegalArgumentException( + "Repository $peerRepository not found, $message" + ) + } + // TODO right now this doesn't use the peer repository since there is no impl satisfying both libraries. + val discoveryService = DiscoveryService.open( + vertx, + keyPair = it.getIdentity(), + port = it.getPort(), + host = it.getNetworkInterface() + ) + discoveryServices[it.getName()] = discoveryService + logger.info("Started discovery service ${it.getName()}") + } + AsyncCompletion.allOf( config.rlpxServices().map { rlpxConfig -> logger.info("Creating RLPx service ${rlpxConfig.getName()}") @@ -273,6 +298,7 @@ class EthereumClient( fun stop() = runBlocking { dnsClients.values.forEach(DNSClient::stop) + AsyncCompletion.allOf(discoveryServices.values.map(DiscoveryService::shutdownAsync)).await() synchronizers.values.forEach { it.stop() } diff --git a/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClientConfig.kt b/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClientConfig.kt index 454f7bb..fc367e8 100644 --- a/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClientConfig.kt +++ b/eth-client/src/main/kotlin/org/apache/tuweni/ethclient/EthereumClientConfig.kt @@ -25,6 +25,7 @@ import org.apache.tuweni.config.Schema import org.apache.tuweni.config.SchemaBuilder import org.apache.tuweni.crypto.SECP256K1 import org.apache.tuweni.eth.genesis.GenesisFile +import org.slf4j.LoggerFactory import java.io.FileNotFoundException import java.net.URI import java.nio.file.Files @@ -116,6 +117,32 @@ class EthereumClientConfig(private var config: Configuration = Configuration.emp } } + fun discoveryServices(): List<DiscoveryConfiguration> { + val discoverySections = config.sections("discovery") + if (discoverySections == null || discoverySections.isEmpty()) { + return emptyList() + } + return discoverySections.map { section -> + val sectionConfig = config.getConfigurationSection("discovery.$section") + + val secretKey = sectionConfig.getString("identity") + val keypair = if (secretKey == "") { + SECP256K1.KeyPair.random() + } else { + SECP256K1.KeyPair.fromSecretKey(SECP256K1.SecretKey.fromBytes(Bytes32.fromHexString(secretKey))) + } + logger.info("Using () for discovery ()", keypair.publicKey().toHexString(), section) + + DiscoveryConfigurationImpl( + section, + sectionConfig.getString("peerRepository"), + sectionConfig.getInteger("port"), + sectionConfig.getString("networkInterface"), + keypair + ) + } + } + fun staticPeers(): List<StaticPeersConfiguration> { val staticPeersSections = config.sections("static") if (staticPeersSections == null || staticPeersSections.isEmpty()) { @@ -146,6 +173,9 @@ class EthereumClientConfig(private var config: Configuration = Configuration.emp } companion object { + + val logger = LoggerFactory.getLogger(EthereumClientConfig::class.java) + fun createSchema(): Schema { val metricsSection = SchemaBuilder.create() metricsSection.addInteger("port", 9090, "Port to expose Prometheus metrics", PropertyValidator.isValidPort()) @@ -166,6 +196,12 @@ class EthereumClientConfig(private var config: Configuration = Configuration.emp staticPeers.addListOfString("enodes", Collections.emptyList(), "Static enodes to connect to in enode://publickey@host:port format", null) staticPeers.addString("peerRepository", "default", "Peer repository to which static nodes should go", null) + val discoverySection = SchemaBuilder.create() + discoverySection.addString("identity", "", "Node identity", null) + discoverySection.addString("networkInterface", "127.0.0.1", "Network interface to bind", null) + discoverySection.addInteger("port", 0, "Port to expose the discovery service on", PropertyValidator.isValidPortOrZero()) + discoverySection.addString("peerRepository", "default", "Peer repository to which records should go", null) + val genesis = SchemaBuilder.create() genesis.addString("path", "classpath:/default.json", "Path to the genesis file", PropertyValidator.isURL()) @@ -203,6 +239,7 @@ class EthereumClientConfig(private var config: Configuration = Configuration.emp builder.addSection("storage", storageSection.toSchema()) builder.addSection("dns", dnsSection.toSchema()) builder.addSection("static", staticPeers.toSchema()) + builder.addSection("discovery", discoverySection.toSchema()) builder.addSection("rlpx", rlpx.toSchema()) builder.addSection("genesis", genesis.toSchema()) builder.addSection("proxy", proxiesSection.toSchema()) @@ -264,6 +301,14 @@ interface DNSConfiguration { fun peerRepository(): String } +interface DiscoveryConfiguration { + fun getName(): String + fun getIdentity(): SECP256K1.KeyPair + fun getNetworkInterface(): String + fun getPort(): Int + fun getPeerRepository(): String +} + interface StaticPeersConfiguration { fun enodes(): List<String> fun peerRepository(): String @@ -354,6 +399,21 @@ data class DNSConfigurationImpl( override fun pollingPeriod(): Long = pollingPeriod } +data class DiscoveryConfigurationImpl( + private val name: String, + private val peerRepository: String, + private val port: Int, + private val networkInterface: String, + private val identity: SECP256K1.KeyPair +) : + DiscoveryConfiguration { + override fun getName() = name + override fun getIdentity() = identity + override fun getNetworkInterface() = networkInterface + override fun getPeerRepository() = peerRepository + override fun getPort() = port +} + data class StaticPeersConfigurationImpl(private val enodes: List<String>, private val peerRepository: String) : StaticPeersConfiguration { override fun enodes(): List<String> = enodes --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@tuweni.apache.org For additional commands, e-mail: commits-h...@tuweni.apache.org