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

Reply via email to