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 776bd698 expose information from the rlpx service in the UI
     new 260b9e00 Merge pull request #426 from atoulme/new_ui_improvements
776bd698 is described below

commit 776bd6981f1b23f39c38807b0ad435923121fb74
Author: Antoine Toulme <anto...@lunar-ocean.com>
AuthorDate: Sat Jul 16 10:01:05 2022 -0700

    expose information from the rlpx service in the UI
---
 eth-client-ui/build.gradle                         |  4 ++
 .../apache/tuweni/ethclientui/UIIntegrationTest.kt | 32 +++++++++++--
 .../org/apache/tuweni/ethclientui/JSONProvider.kt  | 56 ++++++++++++++++++++++
 .../org/apache/tuweni/ethclientui/StateService.kt  | 19 ++++++--
 .../kotlin/org/apache/tuweni/ethclientui/UI.kt     |  2 +-
 .../org/apache/tuweni/ethclient/EthereumClient.kt  | 14 +++---
 6 files changed, 114 insertions(+), 13 deletions(-)

diff --git a/eth-client-ui/build.gradle b/eth-client-ui/build.gradle
index 6f468ef6..64a0bea6 100644
--- a/eth-client-ui/build.gradle
+++ b/eth-client-ui/build.gradle
@@ -26,12 +26,16 @@ dependencies {
   implementation 'javax.servlet:javax.servlet-api'
   implementation 'javax.ws.rs:javax.ws.rs-api'
 
+  implementation project(':bytes')
   implementation project(':concurrent')
   implementation project(':config')
   implementation project(':concurrent-coroutines')
   implementation project(':crypto')
+  implementation project(':eth')
   implementation project(':eth-client')
+  implementation project(':eth-repository')
   implementation project(':peer-repository')
+  implementation project(':units')
 
   testImplementation project(':junit')
   testImplementation 'org.bouncycastle:bcprov-jdk15on'
diff --git 
a/eth-client-ui/src/integrationTest/kotlin/org/apache/tuweni/ethclientui/UIIntegrationTest.kt
 
b/eth-client-ui/src/integrationTest/kotlin/org/apache/tuweni/ethclientui/UIIntegrationTest.kt
index 353fa3e3..19f0e0ba 100644
--- 
a/eth-client-ui/src/integrationTest/kotlin/org/apache/tuweni/ethclientui/UIIntegrationTest.kt
+++ 
b/eth-client-ui/src/integrationTest/kotlin/org/apache/tuweni/ethclientui/UIIntegrationTest.kt
@@ -17,22 +17,42 @@
 package org.apache.tuweni.ethclientui
 
 import io.vertx.core.Vertx
+import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.ethclient.EthereumClient
 import org.apache.tuweni.ethclient.EthereumClientConfig
+import org.apache.tuweni.junit.BouncyCastleExtension
+import org.apache.tuweni.junit.TempDirectory
+import org.apache.tuweni.junit.TempDirectoryExtension
 import org.apache.tuweni.junit.VertxExtension
 import org.apache.tuweni.junit.VertxInstance
+import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
 import java.net.HttpURLConnection
 import java.net.URL
+import java.nio.file.Path
 
-@ExtendWith(VertxExtension::class)
+@ExtendWith(VertxExtension::class, BouncyCastleExtension::class, 
TempDirectoryExtension::class)
 class UIIntegrationTest {
 
   @Test
-  fun testServerComesUp(@VertxInstance vertx: Vertx) {
-    val ui = UI(client = EthereumClient(vertx, 
EthereumClientConfig.fromString("[storage.forui]\npath=\"data\"")))
+  fun testServerComesUp(@VertxInstance vertx: Vertx, @TempDirectory tempDir: 
Path) = runBlocking {
+    val ui = UI(
+      client = EthereumClient(
+        vertx,
+        EthereumClientConfig.fromString(
+          """[storage.default]
+path="${tempDir.toAbsolutePath()}"
+genesis="default"
+[genesis.default]
+path="classpath:/genesis/dev.json"
+[peerRepository.default]
+type="memory""""
+        )
+      )
+    )
+    ui.client.start()
     ui.start()
     val url = URL("http://localhost:"; + ui.actualPort)
     val con = url.openConnection() as HttpURLConnection
@@ -45,6 +65,12 @@ class UIIntegrationTest {
     con2.requestMethod = "GET"
     val response2 = con2.inputStream.readAllBytes()
     assertTrue(response2.isNotEmpty())
+    val url3 = URL("http://localhost:"; + ui.actualPort + "/rest/state")
+    val con3 = url3.openConnection() as HttpURLConnection
+    con3.requestMethod = "GET"
+    val response3 = con3.inputStream.readAllBytes()
+    assertTrue(response3.isNotEmpty())
+    
assertEquals("""{"peerCounts":{"default":0},"bestBlocks":{"default":{"hash":"0xa08d1edb37ba1c62db764ef7c2566cbe368b850f5b3762c6c24114a3fd97b87f","number":"0x0000000000000000000000000000000000000000000000000000000000000000"}}}""",
 String(response3))
     ui.stop()
   }
 }
diff --git 
a/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/JSONProvider.kt 
b/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/JSONProvider.kt
new file mode 100644
index 00000000..365beb51
--- /dev/null
+++ 
b/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/JSONProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.ethclientui
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import jakarta.ws.rs.Produces
+import jakarta.ws.rs.core.Feature
+import jakarta.ws.rs.core.FeatureContext
+import jakarta.ws.rs.core.MediaType
+import jakarta.ws.rs.ext.MessageBodyReader
+import jakarta.ws.rs.ext.MessageBodyWriter
+import jakarta.ws.rs.ext.Provider
+import org.apache.tuweni.eth.EthJsonModule
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.Annotations
+import 
org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider
+
+@Provider
+@Produces(MediaType.APPLICATION_JSON)
+class JSONProvider : JacksonJaxbJsonProvider {
+
+  constructor() : super()
+  constructor(vararg annotationsToUse: Annotations?) : super(mapper, 
annotationsToUse)
+
+  companion object {
+    val mapper = ObjectMapper()
+
+    init {
+      mapper.registerModule(EthJsonModule())
+    }
+  }
+
+  init {
+    setMapper(mapper)
+  }
+}
+
+class MarshallingFeature : Feature {
+  override fun configure(context: FeatureContext): Boolean {
+    context.register(JSONProvider::class.java, MessageBodyReader::class.java, 
MessageBodyWriter::class.java)
+    return true
+  }
+}
diff --git 
a/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/StateService.kt 
b/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/StateService.kt
index f37461e6..9eee076f 100644
--- 
a/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/StateService.kt
+++ 
b/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/StateService.kt
@@ -23,6 +23,13 @@ import jakarta.ws.rs.Path
 import jakarta.ws.rs.Produces
 import jakarta.ws.rs.core.Context
 import jakarta.ws.rs.core.MediaType
+import kotlinx.coroutines.runBlocking
+import org.apache.tuweni.bytes.Bytes32
+import org.apache.tuweni.units.bigints.UInt256
+
+data class BlockHashAndNumber(val hash: Bytes32, val number: UInt256)
+
+data class State(val peerCounts: Map<String, Long>, val bestBlocks: 
Map<String, BlockHashAndNumber>)
 
 @Path("state")
 class StateService {
@@ -32,12 +39,18 @@ class StateService {
 
   @GET
   @Produces(MediaType.APPLICATION_JSON)
-  fun get(): Map<String, Long> {
+  fun get(): State {
     val client = context!!.getAttribute("ethclient") as EthereumClient
 
-    val pairs = client.peerRepositories.entries.map {
+    val peerCounts = client.peerRepositories.entries.map {
       Pair(it.key, it.value.activeConnections().count())
     }
-    return mapOf(*pairs.toTypedArray())
+    val bestBlocks = client.storageRepositories.entries.map {
+      runBlocking {
+        val bestBlock = it.value.retrieveChainHeadHeader()
+        Pair(it.key, BlockHashAndNumber(bestBlock.hash, bestBlock.number))
+      }
+    }
+    return State(mapOf(*peerCounts.toTypedArray()), 
mapOf(*bestBlocks.toTypedArray()))
   }
 }
diff --git a/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/UI.kt 
b/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/UI.kt
index 41570903..fad640c8 100644
--- a/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/UI.kt
+++ b/eth-client-ui/src/main/kotlin/org/apache/tuweni/ethclientui/UI.kt
@@ -45,7 +45,7 @@ class UI(
     ctx.contextPath = path
     newServer.handler = ctx
 
-    val config = ResourceConfig().packages(true, 
"org.apache.tuweni.ethclientui")
+    val config = ResourceConfig().packages(true, 
"org.apache.tuweni.ethclientui").register(MarshallingFeature::class.java)
     val holder = ServletHolder(ServletContainer(config))
     holder.initOrder = 1
     ctx.addServlet(holder, "/rest/*")
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 15bfbedf..061b1028 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
@@ -83,8 +83,8 @@ class EthereumClient(
 
   private var metricsService: MetricsService? = null
   private val genesisFiles = mutableMapOf<String, GenesisFile>()
-  private val services = mutableMapOf<String, RLPxService>()
-  private val storageRepositories = mutableMapOf<String, 
BlockchainRepository>()
+  private val rlpxServices = mutableMapOf<String, RLPxService>()
+  val storageRepositories = mutableMapOf<String, BlockchainRepository>()
   val peerRepositories = mutableMapOf<String, EthereumPeerRepository>()
   private val dnsClients = mutableMapOf<String, DNSClient>()
   private val discoveryServices = mutableMapOf<String, DiscoveryService>()
@@ -198,6 +198,7 @@ class EthereumClient(
       discoveryServices[it.getName()] = discoveryService
       logger.info("Started discovery service ${it.getName()}")
     }
+    val adapters = mutableMapOf<String, WireConnectionPeerRepositoryAdapter>()
 
     AsyncCompletion.allOf(
       config.rlpxServices().map { rlpxConfig ->
@@ -252,7 +253,8 @@ class EthereumClient(
           meter,
           adapter
         )
-        services[rlpxConfig.getName()] = service
+        adapters[rlpxConfig.getName()] = adapter
+        rlpxServices[rlpxConfig.getName()] = service
         peerRepository.addIdentityListener {
           service.connectTo(
             it.publicKey(),
@@ -287,9 +289,9 @@ class EthereumClient(
 
       for (sync in config.synchronizers()) {
         val syncRepository = storageRepositories[sync.getRepository()] ?: 
throw IllegalArgumentException("Repository ${sync.getRepository()} missing for 
synchronizer ${sync.getName()}")
-        val syncService = services[sync.getRlpxService()] ?: throw 
IllegalArgumentException("Service ${sync.getRlpxService()} missing for 
synchronizer ${sync.getName()}")
+        val syncService = rlpxServices[sync.getRlpxService()] ?: throw 
IllegalArgumentException("Service ${sync.getRlpxService()} missing for 
synchronizer ${sync.getName()}")
         val syncPeerRepository = peerRepositories[sync.getPeerRepository()] ?: 
throw IllegalArgumentException("Peer repository ${sync.getPeerRepository()} 
missing for synchronizer ${sync.getName()}")
-        val adapter = WireConnectionPeerRepositoryAdapter(syncPeerRepository)
+        val adapter = adapters[sync.getRlpxService()] ?: throw 
IllegalArgumentException("Service ${sync.getRlpxService()} missing for 
synchronizer ${sync.getName()}")
 
         when (sync.getType()) {
           SynchronizerType.best -> {
@@ -397,7 +399,7 @@ class EthereumClient(
     managerHandler.forEach {
       it.stop()
     }
-    AsyncCompletion.allOf(services.values.map(RLPxService::stop)).await()
+    AsyncCompletion.allOf(rlpxServices.values.map(RLPxService::stop)).await()
     storageRepositories.values.forEach(BlockchainRepository::close)
     metricsService?.close()
     Unit


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

Reply via email to