diff --git a/node/src/main/kotlin/com/r3corda/node/services/network/InMemoryNetworkMapCache.kt b/node/src/main/kotlin/com/r3corda/node/services/network/InMemoryNetworkMapCache.kt index e7438ccff9..4fe8d4f911 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/network/InMemoryNetworkMapCache.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/network/InMemoryNetworkMapCache.kt @@ -6,6 +6,7 @@ import com.google.common.util.concurrent.SettableFuture import com.r3corda.core.bufferUntilSubscribed import com.r3corda.core.contracts.Contract import com.r3corda.core.crypto.Party +import com.r3corda.core.crypto.toStringShort import com.r3corda.core.map import com.r3corda.core.messaging.MessagingService import com.r3corda.core.messaging.SingleMessageRecipient @@ -72,9 +73,16 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach override fun get(serviceType: ServiceType) = registeredNodes.filterValues { it.advertisedServices.any { it.info.type.isSubTypeOf(serviceType) } }.map { it.value } override fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = get(type).firstOrNull() override fun getNodeByLegalName(name: String) = get().singleOrNull { it.legalIdentity.name == name } - override fun getNodeByPublicKey(publicKey: PublicKey) = get().singleOrNull { - (it.legalIdentity.owningKey == publicKey) - || it.advertisedServices.any { it.identity.owningKey == publicKey } + override fun getNodeByPublicKey(publicKey: PublicKey): NodeInfo? { + // Although we should never have more than one match, it is theoretically possible. Report an error if it happens. + val candidates = get().filter { + (it.legalIdentity.owningKey == publicKey) + || it.advertisedServices.any { it.identity.owningKey == publicKey } + } + if (candidates.size > 1) { + throw IllegalStateException("Found more than one match for key ${publicKey.toStringShort()}") + } + return candidates.singleOrNull() } override fun addMapService(net: MessagingService, networkMapAddress: SingleMessageRecipient, subscribe: Boolean, diff --git a/node/src/test/kotlin/com/r3corda/node/services/InMemoryNetworkMapCacheTest.kt b/node/src/test/kotlin/com/r3corda/node/services/InMemoryNetworkMapCacheTest.kt index 02087ccd1c..3fa1062f05 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/InMemoryNetworkMapCacheTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/InMemoryNetworkMapCacheTest.kt @@ -1,8 +1,14 @@ package com.r3corda.node.services +import com.r3corda.core.crypto.generateKeyPair +import com.r3corda.core.node.services.ServiceInfo +import com.r3corda.node.services.network.NetworkMapService +import com.r3corda.node.services.transactions.SimpleNotaryService +import com.r3corda.testing.expect import com.r3corda.testing.node.MockNetwork import org.junit.Before import org.junit.Test +import kotlin.test.assertEquals class InMemoryNetworkMapCacheTest { lateinit var network: MockNetwork @@ -20,4 +26,20 @@ class InMemoryNetworkMapCacheTest { network.runNetwork() future.get() } + + @Test + fun `key collision`() { + val keyPair = generateKeyPair() + val nodeA = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node A", keyPair, ServiceInfo(NetworkMapService.type)) + val nodeB = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node B", keyPair, ServiceInfo(NetworkMapService.type)) + + // Node A currently knows only about itself, so this returns node A + assertEquals(nodeA.netMapCache.getNodeByPublicKey(keyPair.public), nodeA.info) + + nodeA.netMapCache.addNode(nodeB.info) + // Now both nodes match, so it throws an error + expect { + nodeA.netMapCache.getNodeByPublicKey(keyPair.public) + } + } }