try signing archives in build (#91)
diff --git a/.github/workflows/master-pr-build.yml b/.github/workflows/master-pr-build.yml
index 9d7d686..1ee27b3 100644
--- a/.github/workflows/master-pr-build.yml
+++ b/.github/workflows/master-pr-build.yml
@@ -27,7 +27,7 @@
   checks:
     runs-on: ubuntu-latest
     container:
-      image: tmio/tuweni-build:1.0
+      image: tmio/tuweni-build:1.1
     steps:
       - uses: actions/checkout@v1
         with:
@@ -43,7 +43,7 @@
   assemble:
     runs-on: ubuntu-latest
     container:
-      image: tmio/tuweni-build:1.0
+      image: tmio/tuweni-build:1.1
     steps:
       - uses: actions/checkout@v1
         with:
@@ -70,10 +70,37 @@
         with:
           name: Libs
           path: build/libs
+  package-checks:
+    runs-on: ubuntu-latest
+    container:
+      image: tmio/tuweni-build:1.1
+    steps:
+      - uses: actions/checkout@v1
+        with:
+          submodules: true
+      - name: Set up GPG key
+        run: gpg --import gradle/tuweni-test.asc
+      - name: Cache Maven Repository
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2
+          key: ${{ runner.os }}-m2-${{ hashFiles('**/dependency-versions.gradle') }}
+          restore-keys: ${{ runner.os }}-m2
+      - name: gradle assemble
+        uses: eskatos/gradle-command-action@v1
+        env:
+          ENABLE_SIGNING: true
+        with:
+          gradle-version: 6.3
+          arguments: assemble -x test -Psignatory.keyId=38F6C7215DD49C32 -Psigning.gnupg.keyName=38F6C7215DD49C32 -Psigning.gnupg.executable=gpg
+      - name: Unzip source
+        run: unzip -o dist/build/distributions/tuweni-src-*.zip -d distsrc
+      - name: Build from source
+        run: cd distsrc/$(ls distsrc) && gradle setup && ./gradlew assemble
   test:
     runs-on: ubuntu-latest
     container:
-      image: tmio/tuweni-build:1.0
+      image: tmio/tuweni-build:1.1
     steps:
       - uses: actions/checkout@v1
         with:
@@ -94,7 +121,39 @@
         uses: eskatos/gradle-command-action@v1
         with:
           gradle-version: 6.3
-          arguments: test
+          arguments: test jacocoTestReport
+      - name: Upload test reports
+        uses: actions/upload-artifact@v2
+        with:
+          name: Reports
+          path: "**/build/reports"
+      - name: Upload to Codecov
+        uses: codecov/codecov-action@v1
+  integration-tests:
+    runs-on: ubuntu-latest
+    container:
+      image: tmio/tuweni-build:1.1
+    steps:
+      - uses: actions/checkout@v1
+        with:
+          submodules: true
+      - name: Cache Gradle packages
+        uses: actions/cache@v1
+        with:
+          path: ~/.gradle/caches
+          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
+          restore-keys: ${{ runner.os }}-gradle
+      - name: Cache Maven Repository
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2
+          key: ${{ runner.os }}-m2-${{ hashFiles('**/dependency-versions.gradle') }}
+          restore-keys: ${{ runner.os }}-m2
+      - name: gradle integrationTest
+        uses: eskatos/gradle-command-action@v1
+        with:
+          gradle-version: 6.3
+          arguments: integrationTest
       - name: Upload test reports
         uses: actions/upload-artifact@v2
         with:
diff --git a/Jenkinsfile b/Jenkinsfile
index 36b4866..a4e4bf9 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -10,6 +10,9 @@
  * 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.
  */
+
+// This pipeline runs on master and pushes builds to repository.apache.org.
+
 pipeline {
     agent { label 'ubuntu' }
 
@@ -33,22 +36,6 @@
                 }
             }
         }
-        stage('Integration tests') {
-            steps {
-                timeout(time: 60, unit: 'MINUTES') {
-                    sh "./gradlew clean integrationTest sourcesDistZip distZip"
-                }
-            }
-        }
-        stage('Check source build') {
-            steps {
-                timeout(time: 60, unit: 'MINUTES') {
-                    sh "unzip dist/build/distributions/tuweni-src-*.zip -d distsrc"
-                    sh "cd distsrc/$(ls distsrc) && ./build.sh"
-                }
-            }
-        }
-        if(env.BRANCH_NAME == 'master'){
         stage('Publish') {
             steps {
                 timeout(time: 30, unit: 'MINUTES') {
@@ -56,11 +43,5 @@
                 }
             }
         }
-        }
-    }
-    post {
-        always {
-           junit '**/build/test-results/test/*.xml'
-        }
     }
 }
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b2145c6..3bcba54 100644
--- a/build.gradle
+++ b/build.gradle
@@ -94,6 +94,7 @@
       // And some of the files that we have checked in should also be excluded from this check
       list.addAll([
         '.*\\.asc',
+        'gradle/tuweni-test.asc',
         '\\w+/out/.*',
         'eth-reference-tests/**',
         'build',
@@ -240,8 +241,6 @@
   sourceCompatibility = '1.11'
   targetCompatibility = '1.11'
 
-  jacoco { toolVersion = '0.8.5' }
-
   dependencies {
     errorprone 'com.google.errorprone:error_prone_core'
     if (JavaVersion.current().isJava8()) {
@@ -283,6 +282,13 @@
 
       disableWarningsInGeneratedCode = true
     }
+
+    jacocoTestReport {
+      reports {
+        xml.enabled true
+        html.enabled true
+      }
+    }
   }
 
   tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
@@ -295,6 +301,13 @@
         '-Xuse-experimental=kotlin.Experimental'
       ]
     }
+
+    jacocoTestReport {
+      reports {
+        xml.enabled true
+        html.enabled true
+      }
+    }
   }
 
 
@@ -565,6 +578,10 @@
       }
     }
 
+    tasks.withType(GenerateModuleMetadata) {
+      enabled = false
+    }
+
     model {
       tasks.generatePomFileForMavenDeploymentPublication {
         destination = file("$buildDir/generated-pom.xml")
diff --git a/build.sh b/build.sh
index a4a9fc8..ef3e2f6 100755
--- a/build.sh
+++ b/build.sh
@@ -13,5 +13,4 @@
 pushd gradle/docker
 docker build -t tuweni_test -f test.Dockerfile ../..
 popd
-docker run tuweni_test bash -c 'gradle --version >> /dev/null && gradle -Dorg.gradle.jvmargs="-Xmx4g -XX:MaxMetaspaceSize=512m" test'
-docker run -v `pwd`/build/libs:/home/gradle/build/libs tuweni_test bash -c 'gradle --version >> /dev/null && gradle -Dorg.gradle.jvmargs="-Xmx4g -XX:MaxMetcaspaceSize=512m" assemble integrationTest -x test'
\ No newline at end of file
+docker run -v `pwd`/build/libs:/home/gradle/build/libs tuweni_test bash -c 'gradle -Dorg.gradle.jvmargs="-Xmx2g -XX:MaxMetaspaceSize=512m" assemble'
diff --git a/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/DiscoveryServiceJavaTest.java b/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/DiscoveryServiceJavaTest.java
index 33272ff..6dbf19b 100644
--- a/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/DiscoveryServiceJavaTest.java
+++ b/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/DiscoveryServiceJavaTest.java
@@ -33,7 +33,7 @@
 
   @Test
   void setUpAndShutDownAsync() throws Exception {
-    DiscoveryService service = DiscoveryService.Companion.open(SECP256K1.KeyPair.random());
+    DiscoveryService service = DiscoveryService.Companion.open(SECP256K1.KeyPair.random(), 0, "127.0.0.1");
     service.shutdown();
     AsyncCompletion completion = service.awaitTerminationAsync();
     completion.join();
@@ -42,7 +42,7 @@
 
   @Test
   void lookupAsync() throws Exception {
-    DiscoveryService service = DiscoveryService.Companion.open(SECP256K1.KeyPair.random());
+    DiscoveryService service = DiscoveryService.Companion.open(SECP256K1.KeyPair.random(), 0, "127.0.0.1");
     AsyncResult<List<Peer>> result = service.lookupAsync(SECP256K1.KeyPair.random().publicKey());
     List<Peer> peers = result.get();
     service.shutdown();
diff --git a/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/v5/NodeDiscoveryServiceTest.java b/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/v5/NodeDiscoveryServiceTest.java
index d4e4654..da1c200 100644
--- a/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/v5/NodeDiscoveryServiceTest.java
+++ b/devp2p/src/integrationTest/java/org/apache/tuweni/devp2p/v5/NodeDiscoveryServiceTest.java
@@ -15,6 +15,8 @@
 import org.apache.tuweni.crypto.SECP256K1;
 import org.apache.tuweni.junit.BouncyCastleExtension;
 
+import java.net.InetSocketAddress;
+
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -23,7 +25,8 @@
 
   @Test
   void testStartAndStop() throws InterruptedException {
-    NodeDiscoveryService service = DefaultNodeDiscoveryService.open(SECP256K1.KeyPair.random(), 10000);
+    NodeDiscoveryService service =
+        DefaultNodeDiscoveryService.open(SECP256K1.KeyPair.random(), 10000, new InetSocketAddress("localhost", 10000));
     service.startAsync().join();
     service.terminateAsync().join();
   }
diff --git a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/DiscoveryServiceTest.kt b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/DiscoveryServiceTest.kt
index e933bfb..e958823 100644
--- a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/DiscoveryServiceTest.kt
+++ b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/DiscoveryServiceTest.kt
@@ -53,7 +53,9 @@
 
   @Test
   fun shouldStartAndShutdownService() {
-    val discoveryService = DiscoveryService.open(SECP256K1.KeyPair.random())
+    val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
+      keyPair = SECP256K1.KeyPair.random())
     assertFalse(discoveryService.isShutdown)
     assertFalse(discoveryService.isTerminated)
     discoveryService.shutdown()
@@ -69,10 +71,11 @@
   fun shouldRespondToPingAndRecordEndpoint() = runBlocking {
     val peerRepository = EphemeralPeerRepository()
     val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
       keyPair = SECP256K1.KeyPair.random(),
       peerRepository = peerRepository
     )
-    val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort)
+    val address = InetSocketAddress(InetAddress.getLoopbackAddress(), discoveryService.localPort)
 
     val clientKeyPair = SECP256K1.KeyPair.random()
     val client = CoroutineDatagramChannel.open()
@@ -94,12 +97,13 @@
   @Test
   fun shouldPingBootstrapNodeAndValidate() = runBlocking {
     val bootstrapKeyPair = SECP256K1.KeyPair.random()
-    val bootstrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0))
+    val bootstrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress("127.0.0.1", 0))
 
     val serviceKeyPair = SECP256K1.KeyPair.random()
     val peerRepository = EphemeralPeerRepository()
     val routingTable = DevP2PPeerRoutingTable(serviceKeyPair.publicKey())
     val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
       keyPair = serviceKeyPair,
       bootstrapURIs = listOf(
         URI("enode://" + bootstrapKeyPair.publicKey().toHexString() + "@127.0.0.1:" + bootstrapClient.localPort)
@@ -107,7 +111,7 @@
       peerRepository = peerRepository,
       routingTable = routingTable
     )
-    val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort)
+    val address = InetSocketAddress(InetAddress.getLoopbackAddress(), discoveryService.localPort)
 
     val ping = bootstrapClient.receivePacket() as PingPacket
     assertEquals(discoveryService.nodeId, ping.nodeId)
@@ -135,12 +139,13 @@
   @Test
   fun shouldIgnoreBootstrapNodeRespondingWithDifferentNodeId() = runBlocking {
     val bootstrapKeyPair = SECP256K1.KeyPair.random()
-    val bootstrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0))
+    val bootstrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress("127.0.0.1", 0))
 
     val serviceKeyPair = SECP256K1.KeyPair.random()
     val peerRepository = EphemeralPeerRepository()
     val routingTable = DevP2PPeerRoutingTable(serviceKeyPair.publicKey())
     val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
       keyPair = serviceKeyPair,
       bootstrapURIs = listOf(
         URI("enode://" + bootstrapKeyPair.publicKey().toHexString() + "@127.0.0.1:" + bootstrapClient.localPort)
@@ -148,7 +153,7 @@
       peerRepository = peerRepository,
       routingTable = routingTable
     )
-    val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort)
+    val address = InetSocketAddress(InetAddress.getLoopbackAddress(), discoveryService.localPort)
 
     val ping = bootstrapClient.receivePacket() as PingPacket
     assertEquals(discoveryService.nodeId, ping.nodeId)
@@ -170,9 +175,10 @@
   @Test
   fun shouldPingBootstrapNodeWithAdvertisedAddress() = runBlocking {
     val bootstrapKeyPair = SECP256K1.KeyPair.random()
-    val boostrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0))
+    val boostrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress("localhost", 0))
 
     val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
       keyPair = SECP256K1.KeyPair.random(),
       bootstrapURIs = listOf(
         URI("enode://" + bootstrapKeyPair.publicKey().bytes().toHexString() + "@127.0.0.1:" + boostrapClient.localPort)
@@ -193,9 +199,10 @@
   @Test
   fun shouldRetryPingsToBootstrapNodes() = runBlocking {
     val bootstrapKeyPair = SECP256K1.KeyPair.random()
-    val boostrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress(0))
+    val boostrapClient = CoroutineDatagramChannel.open().bind(InetSocketAddress("localhost", 0))
 
     val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
       keyPair = SECP256K1.KeyPair.random(),
       bootstrapURIs = listOf(
         URI("enode://" + bootstrapKeyPair.publicKey().bytes().toHexString() + "@127.0.0.1:" + boostrapClient.localPort)
@@ -221,10 +228,11 @@
   fun shouldRequirePingPongBeforeRespondingToFindNodesFromUnverifiedPeer() = runBlocking {
     val peerRepository = EphemeralPeerRepository()
     val discoveryService = DiscoveryService.open(
+      host = "127.0.0.1",
       keyPair = SECP256K1.KeyPair.random(),
       peerRepository = peerRepository
     )
-    val address = InetSocketAddress(InetAddress.getLocalHost(), discoveryService.localPort)
+    val address = InetSocketAddress(InetAddress.getLoopbackAddress(), discoveryService.localPort)
 
     val clientKeyPair = SECP256K1.KeyPair.random()
     val client = CoroutineDatagramChannel.open()
@@ -262,7 +270,8 @@
     ).map { s -> URI.create(s) }
     /* ktlint-enable */
     val discoveryService = DiscoveryService.open(
-      SECP256K1.KeyPair.random(),
+      host = "127.0.0.1",
+      keyPair = SECP256K1.KeyPair.random(),
       bootstrapURIs = boostrapNodes
     )
 
diff --git a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/AbstractIntegrationTest.kt b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/AbstractIntegrationTest.kt
index 621534b..b386bd4 100644
--- a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/AbstractIntegrationTest.kt
+++ b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/AbstractIntegrationTest.kt
@@ -48,9 +48,9 @@
     bootList: List<String> = emptyList(),
     enrStorage: ENRStorage = DefaultENRStorage(),
     keyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random(),
-    enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost(), udp = port),
+    enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLoopbackAddress(), udp = port),
     routingTable: RoutingTable = RoutingTable(enr),
-    address: InetSocketAddress = InetSocketAddress(InetAddress.getLocalHost(), port),
+    address: InetSocketAddress = InetSocketAddress(InetAddress.getLoopbackAddress(), port),
     authenticationProvider: AuthenticationProvider = DefaultAuthenticationProvider(keyPair, routingTable),
     topicTable: TopicTable = TopicTable(),
     ticketHolder: TicketHolder = TicketHolder(),
diff --git a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/DefaultNodeDiscoveryServiceTest.kt b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/DefaultNodeDiscoveryServiceTest.kt
index adb3556..15d6d88 100644
--- a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/DefaultNodeDiscoveryServiceTest.kt
+++ b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/DefaultNodeDiscoveryServiceTest.kt
@@ -39,11 +39,11 @@
 
   private val recipientKeyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
   private val recipientEnr: Bytes =
-    EthereumNodeRecord.toRLP(recipientKeyPair, ip = InetAddress.getLocalHost(), udp = 9001)
+    EthereumNodeRecord.toRLP(recipientKeyPair, ip = InetAddress.getLoopbackAddress(), udp = 9001)
   private val encodedEnr: String = "enr:${Base64URLSafe.encode(recipientEnr)}"
   private val keyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
   private val localPort: Int = 9000
-  private val bindAddress: InetSocketAddress = InetSocketAddress(localPort)
+  private val bindAddress: InetSocketAddress = InetSocketAddress("localhost", localPort)
   private val bootstrapENRList: List<String> = listOf(encodedEnr)
   private val enrSeq: Long = Instant.now().toEpochMilli()
   private val selfENR: Bytes = EthereumNodeRecord.toRLP(
@@ -66,7 +66,7 @@
   @Test
   fun startInitializesConnectorAndBootstraps() = runBlocking {
     val recipientSocket = CoroutineDatagramChannel.open()
-    recipientSocket.bind(InetSocketAddress(9001))
+    recipientSocket.bind(InetSocketAddress("localhost", 9001))
 
     nodeDiscoveryService.start()
 
diff --git a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt
index b7ff405..93ea1d6 100644
--- a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt
+++ b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt
@@ -55,7 +55,7 @@
 
   @BeforeEach
   fun setUp() {
-    val address = InetSocketAddress(9090 + counter)
+    val address = InetSocketAddress(InetAddress.getLoopbackAddress(), 9090 + counter)
     val keyPair = SECP256K1.KeyPair.random()
     val selfEnr = EthereumNodeRecord.toRLP(keyPair, ip = address.address)
     connector = DefaultUdpConnector(address, keyPair, selfEnr)
@@ -97,7 +97,7 @@
 
     val destNodeId = Bytes.random(32)
 
-    val receiverAddress = InetSocketAddress(InetAddress.getLocalHost(), 5000)
+    val receiverAddress = InetSocketAddress(InetAddress.getLoopbackAddress(), 5000)
     val socketChannel = CoroutineDatagramChannel.open()
     socketChannel.bind(receiverAddress)
 
@@ -119,6 +119,7 @@
   @ExperimentalCoroutinesApi
   @Test
   fun attachObserverRegistersListener() = runBlocking {
+    println("yup")
     val observer = object : MessageObserver {
       var result: Channel<RandomMessage> = Channel()
       override fun observe(message: UdpMessage) {
@@ -127,19 +128,27 @@
         }
       }
     }
+    println("yup1")
     connector!!.attachObserver(observer)
+    println("yup2")
     connector!!.start()
+    println("yup3")
     assertTrue(observer.result.isEmpty)
     val codec = DefaultPacketCodec(
       SECP256K1.KeyPair.random(),
       RoutingTable(Bytes.random(32))
     )
+    println("yup4")
     val socketChannel = CoroutineDatagramChannel.open()
+    println("yup5")
     val message = RandomMessage()
+    println("yup6")
     val encodedRandomMessage = codec.encode(message, Hash.sha2_256(connector!!.getEnrBytes()))
     val buffer = ByteBuffer.wrap(encodedRandomMessage.content.toArray())
-    socketChannel.send(buffer, InetSocketAddress(InetAddress.getLocalHost(), 9090))
+    socketChannel.send(buffer, InetSocketAddress(InetAddress.getLoopbackAddress(), 9090))
+    println("yup7")
     val expectedResult = observer.result.receive()
+    println("yup8")
     assertEquals(expectedResult.data, message.data)
   }
 
@@ -166,7 +175,7 @@
     val message = RandomMessage()
     val encodedRandomMessage = codec.encode(message, Hash.sha2_256(connector!!.getEnrBytes()))
     val buffer = ByteBuffer.wrap(encodedRandomMessage.content.toArray())
-    socketChannel.send(buffer, InetSocketAddress(InetAddress.getLocalHost(), 9090))
+    socketChannel.send(buffer, InetSocketAddress(InetAddress.getLoopbackAddress(), 9090 + counter))
     assertTrue(observer.result.isEmpty)
   }
 }
diff --git a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicIntegrationTest.kt b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicIntegrationTest.kt
index 55e50bc..3057166 100644
--- a/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicIntegrationTest.kt
+++ b/devp2p/src/integrationTest/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicIntegrationTest.kt
@@ -66,7 +66,10 @@
 
     val topic = Topic("0x41")
     node2.topicTable.put(topic, node2.enr)
-    node2.topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost()))
+    node2.topicTable.put(
+      topic,
+      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress())
+    )
     val requestId = UdpMessage.requestId()
     val message = RegTopicMessage(requestId, node1.enr, topic.toBytes(), Bytes.EMPTY)
     val ticketMessage = sendAndAwait<TicketMessage>(node1, node2, message)
diff --git a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/DiscoveryService.kt b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/DiscoveryService.kt
index 039f5bc..dd54a9f 100644
--- a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/DiscoveryService.kt
+++ b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/DiscoveryService.kt
@@ -16,7 +16,6 @@
  */
 package org.apache.tuweni.devp2p
 
-import org.slf4j.LoggerFactory
 import com.google.common.cache.Cache
 import com.google.common.cache.CacheBuilder
 import kotlinx.coroutines.CancellationException
@@ -54,6 +53,7 @@
 import org.apache.tuweni.net.coroutines.CommonCoroutineGroup
 import org.apache.tuweni.net.coroutines.CoroutineChannelGroup
 import org.apache.tuweni.net.coroutines.CoroutineDatagramChannel
+import org.slf4j.LoggerFactory
 import java.io.IOException
 import java.net.InetAddress
 import java.net.InetSocketAddress
@@ -348,8 +348,10 @@
       advertiseTcpPort
     )
 
-    enr = EthereumNodeRecord.toRLP(keyPair, seq, enrData, selfEndpoint.address, selfEndpoint.tcpPort,
-      selfEndpoint.udpPort)
+    enr = EthereumNodeRecord.toRLP(
+      keyPair, seq, enrData, selfEndpoint.address, selfEndpoint.tcpPort,
+      selfEndpoint.udpPort
+    )
 
     val bootstrapping = bootstrapURIs.map { uri ->
       activityLatch.countUp()
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt
index 4b6d188..217d2ab 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt
@@ -31,7 +31,7 @@
 class DefaultAuthenticationProviderTest {
 
   private val providerKeyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
-  private val providerEnr: Bytes = EthereumNodeRecord.toRLP(providerKeyPair, ip = InetAddress.getLocalHost())
+  private val providerEnr: Bytes = EthereumNodeRecord.toRLP(providerKeyPair, ip = InetAddress.getLoopbackAddress())
   private val routingTable: RoutingTable = RoutingTable(providerEnr)
   private val authenticationProvider = DefaultAuthenticationProvider(providerKeyPair, routingTable)
 
@@ -40,7 +40,7 @@
     val keyPair = SECP256K1.KeyPair.random()
     val nonce = Bytes.fromHexString("0x012715E4EFA2464F51BE49BBC40836E5816B3552249F8AC00AD1BBDB559E44E9")
     val authTag = Bytes.fromHexString("0x39BBC27C8CFA3735DF436AC6")
-    val destEnr = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost())
+    val destEnr = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLoopbackAddress())
     val params = HandshakeInitParameters(nonce, authTag, destEnr)
 
     val result = authenticationProvider.authenticate(params)
@@ -60,7 +60,7 @@
     val keyPair = SECP256K1.KeyPair.random()
     val nonce = Bytes.fromHexString("0x012715E4EFA2464F51BE49BBC40836E5816B3552249F8AC00AD1BBDB559E44E9")
     val authTag = Bytes.fromHexString("0x39BBC27C8CFA3735DF436AC6")
-    val destEnr = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost())
+    val destEnr = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLoopbackAddress())
     val clientRoutingTable = RoutingTable(destEnr)
     val params = HandshakeInitParameters(nonce, authTag, providerEnr)
     val destNodeId = Hash.sha2_256(destEnr)
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt
index e04771f..82e5cba 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt
@@ -38,7 +38,7 @@
 class DefaultPacketCodecTest {
 
   private val keyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
-  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost())
+  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLoopbackAddress())
   private val nodeId: Bytes = Hash.sha2_256(enr)
   private val routingTable: RoutingTable = RoutingTable(enr)
   private val authenticationProvider: AuthenticationProvider = DefaultAuthenticationProvider(keyPair, routingTable)
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/NodesMessageTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/NodesMessageTest.kt
index d65696d..f4f2a17 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/NodesMessageTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/NodesMessageTest.kt
@@ -32,9 +32,9 @@
     val requestId = Bytes.fromHexString("0xC6E32C5E89CAA754")
     val total = 10
     val nodeRecords = listOf(
-      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost(), udp = 9090),
-      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost(), udp = 9091),
-      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost(), udp = 9092)
+      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress(), udp = 9090),
+      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress(), udp = 9091),
+      EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress(), udp = 9092)
     )
     val message = NodesMessage(requestId, total, nodeRecords)
 
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/PongMessageTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/PongMessageTest.kt
index e0847c4..22204ac 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/PongMessageTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/packet/PongMessageTest.kt
@@ -25,7 +25,7 @@
   @Test
   fun encodeCreatesValidBytesSequence() {
     val requestId = Bytes.fromHexString("0xC6E32C5E89CAA754")
-    val message = PongMessage(requestId, 0, InetAddress.getLocalHost(), 9090)
+    val message = PongMessage(requestId, 0, InetAddress.getLoopbackAddress(), 9090)
 
     val encodingResult = message.encode()
 
@@ -39,7 +39,7 @@
 
   @Test
   fun getMessageTypeHasValidIndex() {
-    val message = PongMessage(recipientIp = InetAddress.getLocalHost(), recipientPort = 9090)
+    val message = PongMessage(recipientIp = InetAddress.getLoopbackAddress(), recipientPort = 9090)
 
     assert(2 == message.getMessageType().toInt())
   }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/EnrStorageTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/EnrStorageTest.kt
index ede0d30..0db495d 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/EnrStorageTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/EnrStorageTest.kt
@@ -32,7 +32,7 @@
 
   @Test
   fun setPersistsAndFindRetrievesNodeRecord() {
-    val enr = EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost())
+    val enr = EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress())
 
     storage.set(enr)
 
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/RoutingTableTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/RoutingTableTest.kt
index fbf2ede..9792645 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/RoutingTableTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/storage/RoutingTableTest.kt
@@ -29,11 +29,11 @@
 class RoutingTableTest {
 
   private val keyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
-  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost())
+  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLoopbackAddress())
   private val routingTable: RoutingTable = RoutingTable(enr)
 
   private val newKeyPair = SECP256K1.KeyPair.random()
-  private val newEnr = EthereumNodeRecord.toRLP(newKeyPair, ip = InetAddress.getLocalHost())
+  private val newEnr = EthereumNodeRecord.toRLP(newKeyPair, ip = InetAddress.getLoopbackAddress())
 
   @Test
   fun addCreatesRecordInBucket() {
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicTableTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicTableTest.kt
index de31cad..4b570b8 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicTableTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/topic/TopicTableTest.kt
@@ -28,7 +28,7 @@
 @ExtendWith(BouncyCastleExtension::class)
 class TopicTableTest {
   private val keyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
-  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost())
+  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLoopbackAddress())
 
   private val topicTable = TopicTable(TABLE_CAPACITY, QUEUE_CAPACITY)
 
@@ -43,8 +43,8 @@
   @Test
   fun putAddNodeToNotEmptyQueueShouldReturnWaitingTime() {
     val topic = Topic("A")
-    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost()))
-    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost()))
+    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress()))
+    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress()))
 
     val waitTime = topicTable.put(topic, enr)
 
@@ -64,8 +64,8 @@
   @Test
   fun getNodesReturnNodesThatProvidesTopic() {
     val topic = Topic("A")
-    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost()))
-    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLocalHost()))
+    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress()))
+    topicTable.put(topic, EthereumNodeRecord.toRLP(SECP256K1.KeyPair.random(), ip = InetAddress.getLoopbackAddress()))
 
     val nodes = topicTable.getNodes(topic)
 
diff --git a/gossip/src/test/java/org/apache/tuweni/gossip/GossipCommandLineOptionsTest.java b/gossip/src/test/java/org/apache/tuweni/gossip/GossipCommandLineOptionsTest.java
index e8ad88b..d70328d 100644
--- a/gossip/src/test/java/org/apache/tuweni/gossip/GossipCommandLineOptionsTest.java
+++ b/gossip/src/test/java/org/apache/tuweni/gossip/GossipCommandLineOptionsTest.java
@@ -18,7 +18,6 @@
 import org.apache.tuweni.config.Configuration;
 
 import java.net.URI;
-import java.net.UnknownHostException;
 
 import org.junit.jupiter.api.Test;
 
@@ -55,7 +54,7 @@
   }
 
   @Test
-  void operateFromConfig() throws UnknownHostException {
+  void operateFromConfig() {
     Configuration config = Configuration
         .fromToml(
             ""
@@ -74,7 +73,7 @@
   }
 
   @Test
-  void invalidConfigFilePort() throws UnknownHostException {
+  void invalidConfigFilePort() {
     Configuration config = Configuration
         .fromToml(
             ""
@@ -99,7 +98,7 @@
   }
 
   @Test
-  void cliConfigOverConfigFile() throws UnknownHostException {
+  void cliConfigOverConfigFile() {
     Configuration config = Configuration
         .fromToml(
             ""
diff --git a/gradle/build.Dockerfile b/gradle/build.Dockerfile
index 358d45c..afee8d4 100644
--- a/gradle/build.Dockerfile
+++ b/gradle/build.Dockerfile
@@ -12,4 +12,4 @@
 # Build image used for github actions
 FROM gradle:6.3-jdk11
 
-RUN apt-get update && apt-get install -y libsodium-dev && apt-get clean
\ No newline at end of file
+RUN apt-get update && apt-get install -y libsodium-dev gpg && apt-get clean
\ No newline at end of file
diff --git a/gradle/tuweni-test.asc b/gradle/tuweni-test.asc
new file mode 100644
index 0000000..9505d0d
--- /dev/null
+++ b/gradle/tuweni-test.asc
@@ -0,0 +1,157 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=6qjl
+-----END PGP PRIVATE KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+g3bCbgyoJoU=
+=9Wkn
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/hobbits-relayer/src/integrationTest/kotlin/org/apache/tuweni/relayer/RelayerAppTest.kt b/hobbits-relayer/src/integrationTest/kotlin/org/apache/tuweni/relayer/RelayerAppTest.kt
index 1829197..a8794df 100644
--- a/hobbits-relayer/src/integrationTest/kotlin/org/apache/tuweni/relayer/RelayerAppTest.kt
+++ b/hobbits-relayer/src/integrationTest/kotlin/org/apache/tuweni/relayer/RelayerAppTest.kt
@@ -38,9 +38,9 @@
     val ref = AtomicReference<Message>()
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
-    RelayerApp.main(arrayOf("-b", "tcp://localhost:21000", "-t", "tcp://0.0.0.0:22000"))
+    RelayerApp.main(arrayOf("-b", "tcp://localhost:21000", "-t", "tcp://localhost:22000"))
     runBlocking {
-      client1.createTCPEndpoint("foo", port = 22000, handler = ref::set)
+      client1.createTCPEndpoint("foo", networkInterface = "127.0.0.1", port = 22000, handler = ref::set)
       client1.start()
       client2.start()
       client2.sendMessage(
diff --git a/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/InteractionTest.kt b/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/InteractionTest.kt
index 0c46e2b..c3e1d47 100644
--- a/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/InteractionTest.kt
+++ b/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/InteractionTest.kt
@@ -38,13 +38,19 @@
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
     runBlocking {
-      client1.createTCPEndpoint("foo", port = 0, handler = ref::set, portUpdateListener = newPort::set)
+      client1.createTCPEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        portUpdateListener = newPort::set
+      )
       client1.start()
       client2.start()
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.TCP,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort.get()
       )
     }
@@ -62,13 +68,20 @@
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
     runBlocking {
-      client1.createTCPEndpoint("foo", port = 0, handler = ref::set, tls = true, portUpdateListener = newPort::set)
+      client1.createTCPEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        tls = true,
+        portUpdateListener = newPort::set
+      )
       client1.start()
       client2.start()
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.TCP,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort.get()
       )
     }
@@ -87,20 +100,32 @@
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
     runBlocking {
-      client1.createTCPEndpoint("foo", port = 0, handler = ref::set, portUpdateListener = newPort::set)
-      client1.createTCPEndpoint("bar", port = 0, handler = ref2::set, portUpdateListener = newPort2::set)
+      client1.createTCPEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        portUpdateListener = newPort::set
+      )
+      client1.createTCPEndpoint(
+        "bar",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref2::set,
+        portUpdateListener = newPort2::set
+      )
       client1.start()
       client2.start()
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.TCP,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort.get()
       )
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.TCP,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort2.get()
       )
     }
@@ -122,11 +147,21 @@
     val newPort = AtomicInteger()
 
     runBlocking {
-      client1.createHTTPEndpoint("foo", port = 0, handler = ref::set, portUpdateListener = newPort::set)
+      client1.createHTTPEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        portUpdateListener = newPort::set
+      )
       client1.start()
       client2.start()
-      client2.sendMessage(Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"),
-        headers = Bytes.random(16)), Transport.HTTP, "0.0.0.0", newPort.get())
+      client2.sendMessage(
+        Message(
+          protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"),
+          headers = Bytes.random(16)
+        ), Transport.HTTP, "127.0.0.1", newPort.get()
+      )
     }
     Thread.sleep(200)
     assertEquals(Bytes.fromHexString("deadbeef"), ref.get().body)
@@ -143,20 +178,32 @@
     val newPort = AtomicInteger()
     val newPort2 = AtomicInteger()
     runBlocking {
-      client1.createHTTPEndpoint("foo", port = 0, handler = ref::set, portUpdateListener = newPort::set)
-      client1.createHTTPEndpoint("bar", port = 0, handler = ref2::set, portUpdateListener = newPort2::set)
+      client1.createHTTPEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        portUpdateListener = newPort::set
+      )
+      client1.createHTTPEndpoint(
+        "bar",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref2::set,
+        portUpdateListener = newPort2::set
+      )
       client1.start()
       client2.start()
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.HTTP,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort.get()
       )
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.HTTP,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort2.get()
       )
     }
@@ -180,8 +227,12 @@
       client1.createUDPEndpoint("foo", "localhost", 15000, ref::set)
       client1.start()
       client2.start()
-      client2.sendMessage(Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"),
-        headers = Bytes.random(16)), Transport.UDP, "localhost", 15000)
+      client2.sendMessage(
+        Message(
+          protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"),
+          headers = Bytes.random(16)
+        ), Transport.UDP, "localhost", 15000
+      )
     }
     Thread.sleep(200)
     assertEquals(Bytes.fromHexString("deadbeef"), ref.get().body)
@@ -232,11 +283,21 @@
     val newPort = AtomicInteger()
 
     runBlocking {
-      client1.createWSEndpoint("foo", port = 0, handler = ref::set, portUpdateListener = newPort::set)
+      client1.createWSEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        portUpdateListener = newPort::set
+      )
       client1.start()
       client2.start()
-      client2.sendMessage(Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"),
-        headers = Bytes.random(16)), Transport.WS, "0.0.0.0", newPort.get())
+      client2.sendMessage(
+        Message(
+          protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"),
+          headers = Bytes.random(16)
+        ), Transport.WS, "127.0.0.1", newPort.get()
+      )
     }
     Thread.sleep(200)
     assertEquals(Bytes.fromHexString("deadbeef"), ref.get().body)
@@ -255,20 +316,32 @@
     runBlocking {
       client1.exceptionHandler { it.printStackTrace() }
       client2.exceptionHandler { it.printStackTrace() }
-      client1.createWSEndpoint("foo", port = 0, handler = ref::set, portUpdateListener = newPort::set)
-      client1.createWSEndpoint("bar", port = 0, handler = ref2::set, portUpdateListener = newPort2::set)
+      client1.createWSEndpoint(
+        "foo",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref::set,
+        portUpdateListener = newPort::set
+      )
+      client1.createWSEndpoint(
+        "bar",
+        networkInterface = "127.0.0.1",
+        port = 0,
+        handler = ref2::set,
+        portUpdateListener = newPort2::set
+      )
       client1.start()
       client2.start()
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.WS,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort.get()
       )
       client2.sendMessage(
         Message(protocol = Protocol.PING, body = Bytes.fromHexString("deadbeef"), headers = Bytes.random(16)),
         Transport.WS,
-        "0.0.0.0",
+        "127.0.0.1",
         newPort2.get()
       )
     }
diff --git a/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/RelayerTest.kt b/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/RelayerTest.kt
index 74bba8d..48a4130 100644
--- a/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/RelayerTest.kt
+++ b/hobbits/src/integrationTest/kotlin/org/apache/tuweni/hobbits/RelayerTest.kt
@@ -34,9 +34,9 @@
     val ref = AtomicReference<Message>()
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
-    val relayer = Relayer(vertx, "tcp://localhost:22000", "tcp://0.0.0.0:20000", { })
+    val relayer = Relayer(vertx, "tcp://localhost:22000", "tcp://localhost:20000", { })
     runBlocking {
-      client1.createTCPEndpoint("foo", port = 20000, handler = ref::set)
+      client1.createTCPEndpoint("foo", networkInterface = "127.0.0.1", port = 20000, handler = ref::set)
       client1.start()
       client2.start()
       relayer.start()
@@ -59,9 +59,9 @@
     val ref = AtomicReference<Message>()
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
-    val relayer = Relayer(vertx, "http://localhost:13000", "http://0.0.0.0:11000", { })
+    val relayer = Relayer(vertx, "http://localhost:13000", "http://localhost:11000", { })
     runBlocking {
-      client1.createHTTPEndpoint("foo", port = 11000, handler = ref::set)
+      client1.createHTTPEndpoint("foo", networkInterface = "127.0.0.1", port = 11000, handler = ref::set)
       client1.start()
       client2.start()
       relayer.start()
@@ -109,7 +109,7 @@
     val ref = AtomicReference<Message>()
     val client1 = HobbitsTransport(vertx)
     val client2 = HobbitsTransport(vertx)
-    val relayer = Relayer(vertx, "ws://localhost:32000", "ws://0.0.0.0:30000", { })
+    val relayer = Relayer(vertx, "ws://localhost:32000", "ws://localhost:30000", { })
     runBlocking {
       client1.createWSEndpoint("foo", port = 30000, handler = ref::set)
       client1.start()
diff --git a/hobbits/src/main/kotlin/org/apache/tuweni/hobbits/HobbitsTransport.kt b/hobbits/src/main/kotlin/org/apache/tuweni/hobbits/HobbitsTransport.kt
index 7eee79f..593e77e 100644
--- a/hobbits/src/main/kotlin/org/apache/tuweni/hobbits/HobbitsTransport.kt
+++ b/hobbits/src/main/kotlin/org/apache/tuweni/hobbits/HobbitsTransport.kt
@@ -282,10 +282,10 @@
           if (it.failed()) {
             completion.completeExceptionally(it.cause())
           } else {
-            completion.complete()
             if (endpoint.port == 0) {
               endpoint.updatePort(it.result().actualPort())
             }
+            completion.complete()
           }
         }
         completions.add(completion)
@@ -307,10 +307,10 @@
           if (it.failed()) {
             completion.completeExceptionally(it.cause())
           } else {
-            completion.complete()
             if (endpoint.port == 0) {
               endpoint.updatePort(it.result().actualPort())
             }
+            completion.complete()
           }
         }
         completions.add(completion)
@@ -367,10 +367,10 @@
           if (it.failed()) {
             completion.completeExceptionally(it.cause())
           } else {
-            completion.complete()
             if (endpoint.port == 0) {
               endpoint.updatePort(it.result().actualPort())
             }
+            completion.complete()
           }
         }
         completions.add(completion)
diff --git a/hobbits/src/test/kotlin/org/apache/tuweni/hobbits/HobbitsTransportTest.kt b/hobbits/src/test/kotlin/org/apache/tuweni/hobbits/HobbitsTransportTest.kt
index 6088852..d04b0f0 100644
--- a/hobbits/src/test/kotlin/org/apache/tuweni/hobbits/HobbitsTransportTest.kt
+++ b/hobbits/src/test/kotlin/org/apache/tuweni/hobbits/HobbitsTransportTest.kt
@@ -57,7 +57,7 @@
     val server = HobbitsTransport(vertx)
     server.start()
     val exception: IllegalStateException = assertThrows {
-      server.createHTTPEndpoint(handler = {})
+      server.createHTTPEndpoint(networkInterface = "127.0.0.1", handler = {})
     }
     assertEquals("Server already started", exception.message)
   }
diff --git a/junit/src/main/java/org/apache/tuweni/junit/RedisServerExtension.java b/junit/src/main/java/org/apache/tuweni/junit/RedisServerExtension.java
index fab43e7..ec81477 100644
--- a/junit/src/main/java/org/apache/tuweni/junit/RedisServerExtension.java
+++ b/junit/src/main/java/org/apache/tuweni/junit/RedisServerExtension.java
@@ -53,7 +53,7 @@
     int port = range;
     while (port < range + 100) {
       try {
-        ServerSocket socket = new ServerSocket(port, 0, InetAddress.getLocalHost());
+        ServerSocket socket = new ServerSocket(port, 0, InetAddress.getLoopbackAddress());
         socket.setReuseAddress(false);
         socket.close();
         return port;
diff --git a/net-coroutines/src/integrationTest/kotlin/org/apache/tuweni/net/coroutines/CoroutineSocketChannelTest.kt b/net-coroutines/src/integrationTest/kotlin/org/apache/tuweni/net/coroutines/CoroutineSocketChannelTest.kt
index 559059a..db62e8f 100644
--- a/net-coroutines/src/integrationTest/kotlin/org/apache/tuweni/net/coroutines/CoroutineSocketChannelTest.kt
+++ b/net-coroutines/src/integrationTest/kotlin/org/apache/tuweni/net/coroutines/CoroutineSocketChannelTest.kt
@@ -39,7 +39,7 @@
     listenChannel.bind(null)
     assertNotNull(listenChannel.localAddress)
     assertTrue(listenChannel.localPort > 0)
-    val addr = InetSocketAddress(InetAddress.getLocalHost(), listenChannel.localPort)
+    val addr = InetSocketAddress(InetAddress.getLoopbackAddress(), listenChannel.localPort)
 
     var didBlock = false
     val job = async {
@@ -60,7 +60,8 @@
   fun shouldBlockSocketChannelWhileReading() = runBlocking {
     val listenChannel = CoroutineServerSocketChannel.open()
     listenChannel.bind(null)
-    val addr = InetSocketAddress(InetAddress.getLocalHost(), (listenChannel.localAddress as InetSocketAddress).port)
+    val addr =
+      InetSocketAddress(InetAddress.getLoopbackAddress(), (listenChannel.localAddress as InetSocketAddress).port)
 
     val serverJob = async {
       val serverChannel = listenChannel.accept()
@@ -105,7 +106,8 @@
   fun shouldCloseSocketChannelWhenRemoteClosed() = runBlocking {
     val listenChannel = CoroutineServerSocketChannel.open()
     listenChannel.bind(null)
-    val addr = InetSocketAddress(InetAddress.getLocalHost(), (listenChannel.localAddress as InetSocketAddress).port)
+    val addr =
+      InetSocketAddress(InetAddress.getLoopbackAddress(), (listenChannel.localAddress as InetSocketAddress).port)
 
     val serverJob = async {
       val serverChannel = listenChannel.accept()
diff --git a/net-coroutines/src/test/kotlin/org/apache/tuweni/net/coroutines/CoroutineDatagramChannelTest.kt b/net-coroutines/src/test/kotlin/org/apache/tuweni/net/coroutines/CoroutineDatagramChannelTest.kt
index b8acdaa..3df1a54 100644
--- a/net-coroutines/src/test/kotlin/org/apache/tuweni/net/coroutines/CoroutineDatagramChannelTest.kt
+++ b/net-coroutines/src/test/kotlin/org/apache/tuweni/net/coroutines/CoroutineDatagramChannelTest.kt
@@ -52,7 +52,7 @@
     didBlock = true
 
     val socket = DatagramSocket()
-    socket.connect(InetAddress.getLocalHost(), (channel.localAddress as InetSocketAddress).port)
+    socket.connect(InetAddress.getLoopbackAddress(), (channel.localAddress as InetSocketAddress).port)
 
     val testData = byteArrayOf(1, 2, 3, 4, 5)
     socket.send(DatagramPacket(testData, 5))
@@ -72,7 +72,7 @@
     val socket = DatagramSocket()
 
     val channel = CoroutineDatagramChannel.open()
-    val address = InetSocketAddress(InetAddress.getLocalHost(), socket.localPort)
+    val address = InetSocketAddress(InetAddress.getLoopbackAddress(), socket.localPort)
 
     val testData = byteArrayOf(1, 2, 3, 4, 5)
     val src = ByteBuffer.wrap(testData)
diff --git a/scuttlebutt-discovery/src/integrationTest/java/org/apache/tuweni/scuttlebutt/discovery/ScuttlebuttLocalDiscoveryServiceTest.java b/scuttlebutt-discovery/src/integrationTest/java/org/apache/tuweni/scuttlebutt/discovery/ScuttlebuttLocalDiscoveryServiceTest.java
index 6b8c1ed..5942cf6 100644
--- a/scuttlebutt-discovery/src/integrationTest/java/org/apache/tuweni/scuttlebutt/discovery/ScuttlebuttLocalDiscoveryServiceTest.java
+++ b/scuttlebutt-discovery/src/integrationTest/java/org/apache/tuweni/scuttlebutt/discovery/ScuttlebuttLocalDiscoveryServiceTest.java
@@ -40,14 +40,16 @@
 
   @Test
   void startStop(@VertxInstance Vertx vertx) throws Exception {
-    ScuttlebuttLocalDiscoveryService service = new ScuttlebuttLocalDiscoveryService(vertx, 0, "0.0.0.0", "233.0.10.0");
+    ScuttlebuttLocalDiscoveryService service =
+        new ScuttlebuttLocalDiscoveryService(vertx, 0, "127.0.0.1", "233.0.10.0");
     service.start().join();
     service.stop().join();
   }
 
   @Test
   void startStart(@VertxInstance Vertx vertx) throws Exception {
-    ScuttlebuttLocalDiscoveryService service = new ScuttlebuttLocalDiscoveryService(vertx, 0, "0.0.0.0", "233.0.10.0");
+    ScuttlebuttLocalDiscoveryService service =
+        new ScuttlebuttLocalDiscoveryService(vertx, 0, "127.0.0.1", "233.0.10.0");
     service.start().join();
     service.start().join();
     service.stop().join();
@@ -57,12 +59,13 @@
   void invalidMulticastAddress(@VertxInstance Vertx vertx) throws Exception {
     assertThrows(
         IllegalArgumentException.class,
-        () -> new ScuttlebuttLocalDiscoveryService(vertx, 8008, "0.0.0.0", "10.0.0.0"));
+        () -> new ScuttlebuttLocalDiscoveryService(vertx, 8008, "127.0.0.1", "10.0.0.0"));
   }
 
   @Test
   void stopFirst(@VertxInstance Vertx vertx) throws Exception {
-    ScuttlebuttLocalDiscoveryService service = new ScuttlebuttLocalDiscoveryService(vertx, 0, "0.0.0.0", "233.0.10.0");
+    ScuttlebuttLocalDiscoveryService service =
+        new ScuttlebuttLocalDiscoveryService(vertx, 0, "127.0.0.1", "233.0.10.0");
     service.stop().join();
     service.start().join();
     service.stop().join();
diff --git a/scuttlebutt-handshake/src/integrationTest/java/org/apache/tuweni/scuttlebutt/handshake/vertx/VertxIntegrationTest.java b/scuttlebutt-handshake/src/integrationTest/java/org/apache/tuweni/scuttlebutt/handshake/vertx/VertxIntegrationTest.java
index 6e39a67..1822508 100644
--- a/scuttlebutt-handshake/src/integrationTest/java/org/apache/tuweni/scuttlebutt/handshake/vertx/VertxIntegrationTest.java
+++ b/scuttlebutt-handshake/src/integrationTest/java/org/apache/tuweni/scuttlebutt/handshake/vertx/VertxIntegrationTest.java
@@ -97,7 +97,7 @@
     AtomicReference<MyServerHandler> serverHandlerRef = new AtomicReference<>();
     SecureScuttlebuttVertxServer server = new SecureScuttlebuttVertxServer(
         vertx,
-        new InetSocketAddress("0.0.0.0", 20000),
+        new InetSocketAddress("localhost", 20000),
         serverKeyPair,
         networkIdentifier,
         (streamServer, fn) -> {
@@ -109,7 +109,8 @@
 
     SecureScuttlebuttVertxClient client =
         new SecureScuttlebuttVertxClient(vertx, Signature.KeyPair.random(), networkIdentifier);
-    MyClientHandler handler = client.connectTo(20000, "0.0.0.0", serverKeyPair.publicKey(), MyClientHandler::new).get();
+    MyClientHandler handler =
+        client.connectTo(20000, "localhost", serverKeyPair.publicKey(), MyClientHandler::new).get();
 
     Thread.sleep(1000);
     assertNotNull(handler);