CORDA-3974: NetworkMapCache should link entries with different public keys by X.500 name (#6711)

This commit is contained in:
Denis Rekalov 2020-09-17 09:25:42 +01:00 committed by GitHub
parent d1735b8c42
commit acb82f77b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 21 deletions

View File

@ -263,7 +263,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
}
private fun updateBridgesOnNetworkChange(change: NetworkMapCache.MapChange) {
log.info("Updating bridges on network map change: ${change.node}")
log.info("Updating bridges on network map change: ${change::class.simpleName} ${change.node}")
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
return state.locked {
node.legalIdentitiesAndCerts.map {

View File

@ -161,12 +161,15 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
}
}
private fun NodeInfo.printWithKey() = "$this, owningKey=${legalIdentities.first().owningKey.toStringShort()}"
override fun addOrUpdateNodes(nodes: List<NodeInfo>) {
synchronized(_changed) {
val newNodes = mutableListOf<NodeInfo>()
val updatedNodes = mutableListOf<Pair<NodeInfo, NodeInfo>>()
nodes.map { it to getNodesByLegalIdentityKey(it.legalIdentities.first().owningKey).firstOrNull() }
nodes.map { it to getNodeInfo(it.legalIdentities.first()) }
.forEach { (node, previousNode) ->
logger.info("Adding node with info: ${node.printWithKey()}")
when {
previousNode == null -> {
logger.info("No previous node found for ${node.legalIdentities.first().name}")
@ -178,7 +181,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
logger.info("Discarding older nodeInfo for ${node.legalIdentities.first().name}")
}
previousNode != node -> {
logger.info("Previous node was found for ${node.legalIdentities.first().name} as: $previousNode")
logger.info("Previous node was found for ${node.legalIdentities.first().name} as: ${previousNode.printWithKey()}")
// TODO We should be adding any new identities as well
if (verifyIdentities(node)) {
updatedNodes.add(node to previousNode)
@ -196,6 +199,23 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
recursivelyUpdateNodes(newNodes.map { nodeInfo -> Pair(nodeInfo, MapChange.Added(nodeInfo)) } +
updatedNodes.map { (nodeInfo, previousNodeInfo) -> Pair(nodeInfo, MapChange.Modified(nodeInfo, previousNodeInfo)) })
}
logger.debug { "Done adding nodes with info: $nodes" }
}
// Obtain node info by its legal identity key or, if the key was rotated, by name.
private fun getNodeInfo(party: Party): NodeInfo? {
val infoByKey = getNodesByLegalIdentityKey(party.owningKey).firstOrNull()
return infoByKey ?: getPeerCertificateByLegalName(party.name)?.let {
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
}
}
// Obtain node info by its legal identity key or, if the key was rotated, by name.
private fun getPersistentNodeInfo(session: Session, party: Party): NodeInfoSchemaV1.PersistentNodeInfo? {
val infoByKey = findByIdentityKey(session, party.owningKey).firstOrNull()
return infoByKey ?: queryIdentityByLegalName(session, party.name)?.let {
findByIdentityKey(session, it.owningKey).firstOrNull()
}
}
private fun recursivelyUpdateNodes(nodeUpdates: List<Pair<NodeInfo, MapChange>>) {
@ -222,16 +242,14 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
private fun persistNodeUpdates(nodeUpdates: List<Pair<NodeInfo, MapChange>>) {
database.transaction {
nodeUpdates.forEach { (nodeInfo, change) ->
updateInfoDB(nodeInfo, session)
updateInfoDB(nodeInfo, session, change)
changePublisher.onNext(change)
}
}
}
override fun addOrUpdateNode(node: NodeInfo) {
logger.info("Adding node with info: $node")
addOrUpdateNodes(listOf(node))
logger.debug { "Done adding node with info: $node" }
}
private fun verifyIdentities(node: NodeInfo): Boolean {
@ -279,17 +297,20 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
return session.createQuery(criteria).resultList
}
private fun updateInfoDB(nodeInfo: NodeInfo, session: Session) {
// TODO For now the main legal identity is left in NodeInfo, this should be set comparision/come up with index for NodeInfo?
val info = findByIdentityKey(session, nodeInfo.legalIdentitiesAndCerts.first().owningKey)
private fun updateInfoDB(nodeInfo: NodeInfo, session: Session, change: MapChange) {
val info = getPersistentNodeInfo(session, nodeInfo.legalIdentitiesAndCerts.first().party)
val nodeInfoEntry = generateMappedObject(nodeInfo)
if (info.isNotEmpty()) {
nodeInfoEntry.id = info.first().id
if (info != null) {
nodeInfoEntry.id = info.id
}
session.merge(nodeInfoEntry)
// invalidate cache last - this way, we might serve up the wrong info for a short time, but it will get refreshed
// on the next load
invalidateCaches(nodeInfo)
// invalidate cache for previous key on rotated certificate
if (change is MapChange.Modified) {
invalidateCaches(change.previousNode)
}
}
private fun removeInfoDB(session: Session, nodeInfo: NodeInfo) {

View File

@ -1,11 +1,11 @@
package net.corda.node.services.network
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.toStringShort
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.serialize
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.getTestPartyAndCertificate
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
@ -108,13 +108,81 @@ class NetworkMapCacheTest {
}
}
@Test(timeout=300_000)
fun `add two nodes the same name different keys`() {
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
val aliceCache = aliceNode.services.networkMapCache
val alicePartyAndCert2 = getTestPartyAndCertificate(ALICE_NAME, generateKeyPair().public)
aliceCache.addOrUpdateNode(aliceNode.info.copy(legalIdentitiesAndCerts = listOf(alicePartyAndCert2)))
// This is correct behaviour as we may have distributed service nodes.
assertEquals(2, aliceCache.getNodesByLegalName(ALICE_NAME).size)
@Test(timeout = 300_000)
fun `replace node with the same key but different certificate`() {
val bobCache = mockNet.createPartyNode(BOB_NAME).services.networkMapCache
// Add node info with original key and certificate
val aliceInfo = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, entropyRoot = BigInteger.valueOf(70))).info
val aliceKey = aliceInfo.legalIdentities.single().owningKey
val aliceCertificate = aliceInfo.legalIdentitiesAndCerts.single()
bobCache.addOrUpdateNode(aliceInfo)
// Check database
assertThat(bobCache.getNodeByLegalName(ALICE_NAME)).isEqualTo(aliceInfo)
assertThat(bobCache.getNodesByOwningKeyIndex(aliceKey.toStringShort())).containsOnly(aliceInfo)
// Check cache
assertThat(bobCache.getNodeByHash(aliceInfo.serialize().hash)).isEqualTo(aliceInfo)
assertThat(bobCache.getPeerCertificateByLegalName(ALICE_NAME)).isEqualTo(aliceCertificate)
assertThat(bobCache.getNodesByLegalIdentityKey(aliceKey)).containsOnly(aliceInfo)
// Update node info with new key and certificate
val aliceInfo2 = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, entropyRoot = BigInteger.valueOf(70))).info
val aliceKey2 = aliceInfo2.legalIdentities.single().owningKey
val aliceCertificate2 = aliceInfo2.legalIdentitiesAndCerts.single()
bobCache.addOrUpdateNode(aliceInfo2)
// Check that keys are the same and certificates are different
assertThat(aliceKey).isEqualTo(aliceKey2)
assertThat(aliceCertificate.certificate).isNotEqualTo(aliceCertificate2.certificate)
// Check new entry
assertThat(bobCache.getNodeByLegalName(ALICE_NAME)).isEqualTo(aliceInfo2)
assertThat(bobCache.getNodesByOwningKeyIndex(aliceKey2.toStringShort())).containsOnly(aliceInfo2)
assertThat(bobCache.getNodeByHash(aliceInfo2.serialize().hash)).isEqualTo(aliceInfo2)
assertThat(bobCache.getPeerCertificateByLegalName(ALICE_NAME)).isEqualTo(aliceCertificate2)
assertThat(bobCache.getNodesByLegalIdentityKey(aliceKey2)).containsOnly(aliceInfo2)
// Check old entry
assertThat(bobCache.getNodeByHash(aliceInfo.serialize().hash)).isNull()
}
@Test(timeout = 300_000)
fun `replace node with the same name but different key and certificate`() {
val bobCache = mockNet.createPartyNode(BOB_NAME).services.networkMapCache
// Add node info with original key and certificate
val aliceInfo = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, entropyRoot = BigInteger.valueOf(70))).info
val aliceKey = aliceInfo.legalIdentities.single().owningKey
val aliceCertificate = aliceInfo.legalIdentitiesAndCerts.single()
bobCache.addOrUpdateNode(aliceInfo)
// Check database
assertThat(bobCache.getNodeByLegalName(ALICE_NAME)).isEqualTo(aliceInfo)
assertThat(bobCache.getNodesByOwningKeyIndex(aliceKey.toStringShort())).containsOnly(aliceInfo)
// Check cache
assertThat(bobCache.getNodeByHash(aliceInfo.serialize().hash)).isEqualTo(aliceInfo)
assertThat(bobCache.getPeerCertificateByLegalName(ALICE_NAME)).isEqualTo(aliceCertificate)
assertThat(bobCache.getNodesByLegalIdentityKey(aliceKey)).containsOnly(aliceInfo)
// Update node info with new key and certificate
val aliceInfo2 = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, entropyRoot = BigInteger.valueOf(71))).info
val aliceKey2 = aliceInfo2.legalIdentities.single().owningKey
val aliceCertificate2 = aliceInfo2.legalIdentitiesAndCerts.single()
bobCache.addOrUpdateNode(aliceInfo2)
// Check that keys and certificates are different
assertThat(aliceKey).isNotEqualTo(aliceKey2)
assertThat(aliceCertificate.certificate).isNotEqualTo(aliceCertificate2.certificate)
// Check new entry
assertThat(bobCache.getNodeByLegalName(ALICE_NAME)).isEqualTo(aliceInfo2)
assertThat(bobCache.getNodesByOwningKeyIndex(aliceKey2.toStringShort())).containsOnly(aliceInfo2)
assertThat(bobCache.getNodeByHash(aliceInfo2.serialize().hash)).isEqualTo(aliceInfo2)
assertThat(bobCache.getPeerCertificateByLegalName(ALICE_NAME)).isEqualTo(aliceCertificate2)
assertThat(bobCache.getNodesByLegalIdentityKey(aliceKey2)).containsOnly(aliceInfo2)
// Check old entry
assertThat(bobCache.getNodesByOwningKeyIndex(aliceKey.toStringShort())).isEmpty()
assertThat(bobCache.getNodeByHash(aliceInfo.serialize().hash)).isNull()
assertThat(bobCache.getNodesByLegalIdentityKey(aliceKey)).isEmpty()
}
}