CORDA-1811 - If a second identity is mistakenly created the node will not start

This commit is contained in:
Stefano Franz 2018-09-18 17:55:05 +01:00 committed by Katelyn Baker
parent 40e5e65365
commit a11df32dd6
3 changed files with 89 additions and 6 deletions

View File

@ -24,7 +24,6 @@ import net.corda.core.node.services.*
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.*
import net.corda.node.CordaClock
@ -155,7 +154,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
/** Set to non-null once [start] has been successfully called. */
open val started get() = _started
@Volatile private var _started: StartedNode<AbstractNode>? = null
@Volatile
private var _started: StartedNode<AbstractNode>? = null
/** The implementation of the [CordaRPCOps] interface used by this node. */
open fun makeRPCOps(flowStarter: FlowStarter, database: CordaPersistence, smm: StateMachineManager): CordaRPCOps {
@ -319,7 +319,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
serial = 0
)
val nodeInfoFromDb = networkMapCache.getNodeByLegalName(identity.name)
val nodeInfoFromDb = getPreviousNodeInfoIfPresent(networkMapCache, identity)
val nodeInfo = if (potentialNodeInfo == nodeInfoFromDb?.copy(serial = 0)) {
// The node info hasn't changed. We use the one from the database to preserve the serial.
@ -349,6 +350,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return Pair(keyPairs, nodeInfo)
}
private fun getPreviousNodeInfoIfPresent(networkMapCache: NetworkMapCacheBaseInternal, identity: PartyAndCertificate): NodeInfo? {
val nodeInfosFromDb = networkMapCache.getNodesByLegalName(identity.name)
return when (nodeInfosFromDb.size) {
0 -> null
1 -> nodeInfosFromDb[0]
else -> {
log.warn("Found more than one node registration with our legal name, this is only expected if our keypair has been regenerated")
nodeInfosFromDb[0]
}
}
}
// Publish node info on startup and start task that sends every day a heartbeat - republishes node info.
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
// By default heartbeat interval should be set to 1 day, but for testing we may change it.
@ -712,7 +726,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
val notaryKey = myNotaryIdentity?.owningKey
?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
return notaryConfig.run {
if (raft != null) {
val uniquenessProvider = RaftUniquenessProvider(configuration, database, services.monitoringService.metrics, raft)
@ -846,7 +861,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist")
return cordappServices.getInstance(type)
?: throw IllegalArgumentException("Corda service ${type.name} does not exist")
}
override fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {

View File

@ -162,7 +162,7 @@ open class PersistentNetworkMapCache(
}
}
override fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo> = database.transaction { queryByLegalName(session, name) }
override fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo> = database.transaction { queryByLegalName(session, name) }.sortedByDescending { it.serial }
override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo> = nodesByKeyCache[identityKey]

View File

@ -6,13 +6,17 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.readObject
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.VersionInfo
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.createNodeInfoAndSigned
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.junit.Rule
@ -67,4 +71,67 @@ class NodeTest {
assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial)
}
}
@Test
fun `Node can start with multiple keypairs for it's identity`() {
val configuration = createConfig(ALICE_NAME)
val (nodeInfo1, _) = createNodeInfoAndSigned(ALICE_NAME)
val (nodeInfo2, _) = createNodeInfoAndSigned(ALICE_NAME)
val persistentNodeInfo2 = NodeInfoSchemaV1.PersistentNodeInfo(
id = 0,
hash = nodeInfo2.serialize().hash.toString(),
addresses = nodeInfo2.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
legalIdentitiesAndCerts = nodeInfo2.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
},
platformVersion = nodeInfo2.platformVersion,
serial = nodeInfo2.serial
)
val persistentNodeInfo1 = NodeInfoSchemaV1.PersistentNodeInfo(
id = 0,
hash = nodeInfo1.serialize().hash.toString(),
addresses = nodeInfo1.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
legalIdentitiesAndCerts = nodeInfo1.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
},
platformVersion = nodeInfo1.platformVersion,
serial = nodeInfo1.serial
)
configureDatabase(configuration.dataSourceProperties, configuration.database, rigorousMock()).use {
it.transaction {
session.save(persistentNodeInfo1)
}
it.transaction {
session.save(persistentNodeInfo2)
}
val node = Node(configuration, rigorousMock<VersionInfo>().also {
doReturn(10).whenever(it).platformVersion
}, initialiseSerialization = false)
//this throws an exception with old behaviour
node.generateNodeInfo()
}
}
private fun createConfig(nodeName: CordaX500Name): NodeConfiguration {
val dataSourceProperties = makeTestDataSourceProperties()
val databaseConfig = DatabaseConfig()
val nodeAddress = NetworkHostAndPort("0.1.2.3", 456)
return rigorousMock<AbstractNodeConfiguration>().also {
doReturn(nodeAddress).whenever(it).p2pAddress
doReturn(nodeName).whenever(it).myLegalName
doReturn(null).whenever(it).notary // Don't add notary identity.
doReturn(dataSourceProperties).whenever(it).dataSourceProperties
doReturn(databaseConfig).whenever(it).database
doReturn(temporaryFolder.root.toPath()).whenever(it).baseDirectory
doReturn(true).whenever(it).devMode // Needed for identity cert.
doReturn("tsp").whenever(it).trustStorePassword
doReturn("ksp").whenever(it).keyStorePassword
}
}
}