diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt index 8bbebe2a9d..fcf228d398 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt @@ -10,22 +10,19 @@ package com.r3.corda.networkmanage.common.persistence -import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity -import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity -import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity -import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity +import com.r3.corda.networkmanage.common.persistence.entity.* import net.corda.core.crypto.SecureHash import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NetworkParameters import net.corda.nodeapi.internal.network.NetworkMapAndSigned -import net.corda.nodeapi.internal.network.ParametersUpdate import net.corda.nodeapi.internal.network.SignedNetworkParameters import java.time.Instant /** * Data access object interface for NetworkMap persistence layer */ -// TODO This storage abstraction should be removed. It results in less readable code when constructing network map in NetworkMapSigner +// TODO This storage abstraction needs some thought. Some of the methods clearly don't make sense e.g. setParametersUpdateStatus. +// The NetworkMapSignerTest uses a mock of this which means we need to provide methods for every trivial db operation. interface NetworkMapStorage { /** * Returns the active network map, or null @@ -52,22 +49,16 @@ interface NetworkMapStorage { /** * Persists given network parameters with signature if provided. - * @return hash corresponding to newly created network parameters entry + * @return The newly inserted [NetworkParametersEntity] */ - fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): SecureHash + fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): NetworkParametersEntity /** - * Save new parameters update information with corresponding network parameters. Only one parameters update entity can be present at any time. Any existing - * parameters update is cleared and overwritten by this one. + * Save new parameters update information with corresponding network parameters. Only one parameters update entity + * can be NEW or FLAG_DAY at any time - if one exists it will be cancelled. */ fun saveNewParametersUpdate(networkParameters: NetworkParameters, description: String, updateDeadline: Instant) - /** - * Indicate that it is time to switch network parameters in network map from active ones to the ones from update. - * @param parametersHash hash of the parameters from update - */ - fun setFlagDay(parametersHash: SecureHash) - /** * Retrieves the latest (i.e. most recently inserted) network parameters entity * Note that they may not have been signed up yet. @@ -75,13 +66,8 @@ interface NetworkMapStorage { */ fun getLatestNetworkParameters(): NetworkParametersEntity? - /** - * Retrieve any parameters update that may be active, null if none are present. - */ - fun getParametersUpdate(): ParametersUpdateEntity? + /** Returns the single new or flag day parameters update, or null if there isn't one. */ + fun getCurrentParametersUpdate(): ParametersUpdateEntity? - /** - * Removes any scheduled parameters updates. - */ - fun clearParametersUpdates() + fun setParametersUpdateStatus(update: ParametersUpdateEntity, newStatus: UpdateStatus) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt index 7fff43f8b8..1368be7b67 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt @@ -10,7 +10,7 @@ package com.r3.corda.networkmanage.common.persistence -import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity +import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity import net.corda.core.crypto.SecureHash import net.corda.core.node.NodeInfo import net.corda.nodeapi.internal.NodeInfoAndSigned @@ -34,10 +34,10 @@ interface NodeInfoStorage { fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo? /** - * Returns the network parameters that the node has accepted or null if couldn't find node info with given hash or - * there is no information on accepted parameters hash stored for this entity + * Returns the parameters update the node has accepted or null if couldn't find node info with given hash or + * there is no information on accepted parameters update stored for this entity. */ - fun getAcceptedNetworkParameters(nodeInfoHash: SecureHash): NetworkParametersEntity? + fun getAcceptedParametersUpdate(nodeInfoHash: SecureHash): ParametersUpdateEntity? /** * The [nodeInfoAndSigned] is keyed by the public key, old node info with the same public key will be replaced by the new node info. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt index fd1c546059..a74e868ffa 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt @@ -18,6 +18,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.nodeapi.internal.persistence.SchemaMigration +import org.hibernate.Session import org.hibernate.query.Query import java.util.* import javax.persistence.LockModeType @@ -25,6 +26,10 @@ import javax.persistence.criteria.CriteriaBuilder import javax.persistence.criteria.Path import javax.persistence.criteria.Predicate +inline fun Session.fromQuery(query: String): Query { + return createQuery("from ${T::class.java.name} $query", T::class.java) +} + inline fun DatabaseTransaction.uniqueEntityWhere(predicate: (CriteriaBuilder, Path) -> Predicate): T? { return entitiesWhere(predicate).setMaxResults(1).uniqueResult() } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt index 2f9d0c42ef..c1f69692f4 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt @@ -11,6 +11,7 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.common.persistence.entity.* +import com.r3.corda.networkmanage.common.utils.logger import net.corda.core.crypto.SecureHash import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NetworkParameters @@ -83,11 +84,11 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw } } - override fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): SecureHash { + override fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): NetworkParametersEntity { val serialized = networkParameters.serialize() signature?.verify(serialized) val hash = serialized.hash - database.transaction { + return database.transaction { val entity = getNetworkParametersEntity(hash) val newNetworkParamsEntity = if (entity != null) { entity.copy( @@ -102,9 +103,8 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw certificate = signature?.by ) } - session.merge(newNetworkParamsEntity) + session.merge(newNetworkParamsEntity) as NetworkParametersEntity } - return hash } override fun getLatestNetworkParameters(): NetworkParametersEntity? { @@ -123,47 +123,37 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw override fun saveNewParametersUpdate(networkParameters: NetworkParameters, description: String, updateDeadline: Instant) { database.transaction { - val hash = saveNetworkParameters(networkParameters, null) - val netParamsEntity = getNetworkParametersEntity(hash)!! - clearParametersUpdates() - session.save(ParametersUpdateEntity(0, netParamsEntity, description, updateDeadline)) + val existingUpdate = getCurrentParametersUpdate() + if (existingUpdate != null) { + logger.info("Cancelling existing update: $existingUpdate") + session.merge(existingUpdate.copy(status = UpdateStatus.CANCELLED)) + } + val netParamsEntity = saveNetworkParameters(networkParameters, null) + session.save(ParametersUpdateEntity( + networkParameters = netParamsEntity, + description = description, + updateDeadline = updateDeadline + )) } } - override fun clearParametersUpdates() { - database.transaction { - val delete = "delete from ${ParametersUpdateEntity::class.java.name}" - session.createQuery(delete).executeUpdate() - } - } - - override fun getParametersUpdate(): ParametersUpdateEntity? { + override fun getCurrentParametersUpdate(): ParametersUpdateEntity? { return database.transaction { - val currentParametersHash = getActiveNetworkMap()?.networkParameters?.hash - val latestParameters = getLatestNetworkParameters() - val criteria = session.criteriaBuilder.createQuery(ParametersUpdateEntity::class.java) - val root = criteria.from(ParametersUpdateEntity::class.java) - val query = criteria.select(root) - // We just want the last entry - val parametersUpdate = session.createQuery(query).setMaxResults(1).uniqueResult() - check(parametersUpdate == null || latestParameters == parametersUpdate.networkParameters) { - "ParametersUpdate doesn't correspond to latest network parameters" + val newParamsUpdates = session.fromQuery("u where u.status in :statuses") + .setParameterList("statuses", listOf(UpdateStatus.NEW, UpdateStatus.FLAG_DAY)) + .resultList + when (newParamsUpdates.size) { + 0 -> null + 1 -> newParamsUpdates[0] + else -> throw IllegalStateException("More than one update found: $newParamsUpdates") } - // Highly unlikely, but... - check(parametersUpdate == null || latestParameters?.hash != currentParametersHash) { - "Having update for parameters that are already in network map" - } - parametersUpdate } } - override fun setFlagDay(parametersHash: SecureHash) { + override fun setParametersUpdateStatus(update: ParametersUpdateEntity, newStatus: UpdateStatus) { + require(newStatus != UpdateStatus.NEW) database.transaction { - val parametersUpdateEntity = getParametersUpdate() ?: throw IllegalArgumentException("Setting flag day but no parameters update to switch to") - if (parametersHash.toString() != parametersUpdateEntity.networkParameters.hash) { - throw IllegalArgumentException("Setting flag day for parameters: $parametersHash, but in database we have update for: ${parametersUpdateEntity.networkParameters.hash}") - } - session.merge(parametersUpdateEntity.copy(flagDay = true)) + session.merge(update.copy(status = newStatus)) } } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt index 3c101ee494..29ad2ab8a9 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt @@ -11,9 +11,11 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity -import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity +import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus import com.r3.corda.networkmanage.common.utils.hashString +import com.r3.corda.networkmanage.common.utils.logger import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.internal.CertRole @@ -33,7 +35,19 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.x509Certificates.find { CertRole.extract(it) == NODE_CA } nodeCaCert ?: throw IllegalArgumentException("Missing Node CA") + val nodeInfoHash = signedNodeInfo.raw.hash + database.transaction { + val count = session.createQuery( + "select count(*) from ${NodeInfoEntity::class.java.name} where nodeInfoHash = :nodeInfoHash", java.lang.Long::class.java) + .setParameter("nodeInfoHash", nodeInfoHash.toString()) + .singleResult + .toLong() + if (count != 0L) { + logger.debug("Ignoring duplicate publish: $nodeInfo") + return@transaction nodeInfoHash + } + // TODO Move these checks out of data access layer val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256())) { "Node-info not registered with us" @@ -42,9 +56,8 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" } } - val existingNodeInfos = session.createQuery( - "from ${NodeInfoEntity::class.java.name} n where n.certificateSigningRequest = :csr and n.isCurrent = true order by n.publishedAt desc", - NodeInfoEntity::class.java) + val existingNodeInfos = session.fromQuery( + "n where n.certificateSigningRequest = :csr and n.isCurrent = true order by n.publishedAt desc") .setParameter("csr", request) .resultList @@ -52,15 +65,16 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn existingNodeInfos.forEach { session.merge(it.copy(isCurrent = false)) } session.save(NodeInfoEntity( - nodeInfoHash = signedNodeInfo.raw.hash.toString(), + nodeInfoHash = nodeInfoHash.toString(), publicKeyHash = nodeInfo.legalIdentities[0].owningKey.hashString(), certificateSigningRequest = request, signedNodeInfo = signedNodeInfo, isCurrent = true, - acceptedNetworkParameters = existingNodeInfos.firstOrNull()?.acceptedNetworkParameters + acceptedParametersUpdate = existingNodeInfos.firstOrNull()?.acceptedParametersUpdate )) } - return signedNodeInfo.raw.hash + + return nodeInfoHash } override fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo? { @@ -69,9 +83,9 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn } } - override fun getAcceptedNetworkParameters(nodeInfoHash: SecureHash): NetworkParametersEntity? { + override fun getAcceptedParametersUpdate(nodeInfoHash: SecureHash): ParametersUpdateEntity? { return database.transaction { - session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.acceptedNetworkParameters + session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.acceptedParametersUpdate } } @@ -84,21 +98,18 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn override fun ackNodeInfoParametersUpdate(publicKeyHash: SecureHash, acceptedParametersHash: SecureHash) { return database.transaction { - val builder = session.criteriaBuilder - val query = builder.createQuery(NodeInfoEntity::class.java).run { - from(NodeInfoEntity::class.java).run { - where(builder.equal(get(NodeInfoEntity::publicKeyHash.name), publicKeyHash.toString())) - } + val nodeInfoEntity = session.fromQuery( + "n where n.publicKeyHash = :publicKeyHash and isCurrent = true") + .setParameter("publicKeyHash", publicKeyHash.toString()) + .singleResult + val parametersUpdateEntity = session.fromQuery( + "u where u.networkParameters.hash = :acceptedParametersHash"). + setParameter("acceptedParametersHash", acceptedParametersHash.toString()) + .singleResult + require(parametersUpdateEntity.status in listOf(UpdateStatus.NEW, UpdateStatus.FLAG_DAY)) { + "$parametersUpdateEntity can no longer be accepted as it's ${parametersUpdateEntity.status}" } - val nodeInfo = requireNotNull(session.createQuery(query).setMaxResults(1).uniqueResult()) { - "NodeInfo with public key hash $publicKeyHash doesn't exist" - } - val networkParameters = requireNotNull(getNetworkParametersEntity(acceptedParametersHash)) { - "Network parameters $acceptedParametersHash doesn't exist" - } - require(networkParameters.isSigned) { "Network parameters $acceptedParametersHash is not signed" } - val newInfo = nodeInfo.copy(acceptedNetworkParameters = networkParameters) - session.merge(newInfo) + session.merge(nodeInfoEntity.copy(acceptedParametersUpdate = parametersUpdateEntity)) } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt index f08e9d2387..ebea041651 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NodeInfoEntity.kt @@ -41,6 +41,6 @@ data class NodeInfoEntity( val publishedAt: Instant = Instant.now(), @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "accepted_network_parameters") - val acceptedNetworkParameters: NetworkParametersEntity? + @JoinColumn(name = "accepted_params_update") + val acceptedParametersUpdate: ParametersUpdateEntity? ) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt index cc099e03dd..97e7c9385d 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/ParametersUpdateEntity.kt @@ -23,11 +23,26 @@ data class ParametersUpdateEntity( @Column(name = "update_deadline", nullable = false) val updateDeadline: Instant, - // This boolean flag is used when we want to explicitly point that it's time to switch parameters in network map. - @Column(name = "flag_day", nullable = false) - val flagDay: Boolean = false + @Column(name = "status", length = 16, nullable = false, columnDefinition = "NVARCHAR(16)") + @Enumerated(EnumType.STRING) + val status: UpdateStatus = UpdateStatus.NEW ) { fun toParametersUpdate(): ParametersUpdate { return ParametersUpdate(SecureHash.parse(networkParameters.hash), description, updateDeadline) } -} \ No newline at end of file +} + +enum class UpdateStatus { + /** A newly created update. */ + NEW, + /** + * An update that has passed its deadline and flagged to be made active on the next signing event. At most only one + * update with status either NEW or FLAG_DAY can exist. + */ + FLAG_DAY, + /** Any previously flagged update that has been activated into the network map. */ + APPLIED, + /** A new or flag day update that has been cancelled. */ + CANCELLED +} + diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt index e7beb52f35..1840aa8799 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt @@ -11,6 +11,7 @@ package com.r3.corda.networkmanage.common.signer import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus.* import net.corda.core.crypto.SecureHash import net.corda.core.node.NetworkParameters import net.corda.core.utilities.contextLogger @@ -34,7 +35,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private } logger.debug { "Retrieved latest network parameters: ${latestNetworkParameters.networkParameters}" } - val parametersUpdate = networkMapStorage.getParametersUpdate() + val parametersUpdate = networkMapStorage.getCurrentParametersUpdate() logger.debug { "Retrieved parameters update: $parametersUpdate" } check(parametersUpdate == null || parametersUpdate.networkParameters.hash == latestNetworkParameters.hash) { "The latest network parameters are not the scheduled updated ones" @@ -61,8 +62,8 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private logger.debug { "No need to sign any network parameters as they're up-to-date" } } - val parametersToNetworkMap = if (parametersUpdate?.flagDay == true || activeNetworkParameters == null) { - networkMapStorage.clearParametersUpdates() + val parametersToNetworkMap = if (parametersUpdate?.status == FLAG_DAY || activeNetworkParameters == null) { + parametersUpdate?.let { networkMapStorage.setParametersUpdateStatus(it, APPLIED) } latestNetworkParameters } else { activeNetworkParameters @@ -71,7 +72,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val newNetworkMap = NetworkMap( nodeInfoHashes, SecureHash.parse(parametersToNetworkMap.hash), - parametersUpdate?.let { if (!it.flagDay) it.toParametersUpdate() else null }) + parametersUpdate?.let { if (it.status == NEW) it.toParametersUpdate() else null }) logger.debug { "Potential new network map: $newNetworkMap" } if (activeNetworkMap?.networkMap != newNetworkMap) { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt index ed079f3ce5..599504acbb 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt @@ -12,11 +12,11 @@ package com.r3.corda.networkmanage.doorman import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory import com.r3.corda.networkmanage.common.persistence.* +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus import com.r3.corda.networkmanage.common.signer.NetworkMapSigner import com.r3.corda.networkmanage.common.utils.CertPathAndKey import com.r3.corda.networkmanage.doorman.signer.* import com.r3.corda.networkmanage.doorman.webservice.* -import net.corda.core.crypto.SecureHash import net.corda.core.node.NetworkParameters import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger @@ -238,36 +238,33 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: } private fun handleFlagDay() { - val parametersUpdate = checkNotNull(networkMapStorage.getParametersUpdate()) { + val parametersUpdate = checkNotNull(networkMapStorage.getCurrentParametersUpdate()) { "No network parameters updates are scheduled" } check(Instant.now() >= parametersUpdate.updateDeadline) { "Update deadline of ${parametersUpdate.updateDeadline} hasn't passed yet" } + val latestNetParamsEntity = networkMapStorage.getLatestNetworkParameters() + check(parametersUpdate.networkParameters.hash == networkMapStorage.getLatestNetworkParameters()?.hash) { + "The latest network parameters is not the scheduled one:\n${latestNetParamsEntity?.networkParameters}\n${parametersUpdate.toParametersUpdate()}" + } val activeNetParams = networkMapStorage.getActiveNetworkMap()?.networkParameters - val latestNetParamsEntity = networkMapStorage.getLatestNetworkParameters()!! - check(latestNetParamsEntity.isSigned) { + check(parametersUpdate.networkParameters.isSigned) { "Parameters we are trying to switch to haven't been signed yet" } - // TODO This check is stil not good enough as when it comes to signing, the NetworkMapSigner will just accept - check(latestNetParamsEntity.hash == parametersUpdate.networkParameters.hash) { - "The latest network parameters is not the scheduled one:\n${latestNetParamsEntity.networkParameters}\n${parametersUpdate.toParametersUpdate()}" - } - logger.info("Flag day has occurred, however the new network parameters won't be active until the new network map is signed.\n" + - "Switching from: $activeNetParams\nTo: ${latestNetParamsEntity.networkParameters}") - networkMapStorage.setFlagDay(SecureHash.parse(parametersUpdate.networkParameters.hash)) - println("Set the flag day") + logger.info("""Flag day has occurred, however the new network parameters won't be active until the new network map is signed. +From: $activeNetParams +To: ${parametersUpdate.networkParameters}""") + networkMapStorage.setParametersUpdateStatus(parametersUpdate, UpdateStatus.FLAG_DAY) } private fun handleCancelUpdate() { - val parametersUpdate = networkMapStorage.getParametersUpdate() - if (parametersUpdate == null) { - logger.info("Trying to cancel parameters update but no update is scheduled") - } else { - logger.info("Cancelling parameters update: ${parametersUpdate.toParametersUpdate()}") - // We leave parameters from that update in the database, for auditing reasons - networkMapStorage.clearParametersUpdates() + val parametersUpdate = checkNotNull(networkMapStorage.getCurrentParametersUpdate()) { + "No network parameters updates are scheduled" } + logger.info("""Cancelling parameters update: ${parametersUpdate.toParametersUpdate()}. +However, the network map will continue to advertise this update until the new one is signed.""") + networkMapStorage.setParametersUpdateStatus(parametersUpdate, UpdateStatus.CANCELLED) println("Done with cancel update") } } diff --git a/network-management/src/main/resources/migration/network-manager.changelog-init.xml b/network-management/src/main/resources/migration/network-manager.changelog-init.xml index 506db970f4..6e435bb819 100644 --- a/network-management/src/main/resources/migration/network-manager.changelog-init.xml +++ b/network-management/src/main/resources/migration/network-manager.changelog-init.xml @@ -137,7 +137,7 @@ - + @@ -318,7 +318,7 @@ @@ -335,18 +335,18 @@ - + - + diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt index 45be8c2160..8425aae758 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt @@ -12,14 +12,14 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity +import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.random63BitValue -import net.corda.core.serialization.serialize +import net.corda.core.utilities.days import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMapAndSigned -import net.corda.nodeapi.internal.network.ParametersUpdate import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -68,8 +68,8 @@ class PersistentNetworkMapStorageTest : TestBase() { // Create network parameters val networkParameters = testNetworkParameters(maxTransactionSize = 1234567) val networkParametersSig = networkMapCertAndKeyPair.sign(networkParameters).sig - val networkParametersHash = networkMapStorage.saveNetworkParameters(networkParameters, networkParametersSig) - val networkMap = NetworkMap(listOf(nodeInfoHash), networkParametersHash, null) + val networkParametersHash = networkMapStorage.saveNetworkParameters(networkParameters, networkParametersSig).hash + val networkMap = NetworkMap(listOf(nodeInfoHash), SecureHash.parse(networkParametersHash), null) val networkMapAndSigned = NetworkMapAndSigned(networkMap) { networkMapCertAndKeyPair.sign(networkMap).sig } // when @@ -87,8 +87,8 @@ class PersistentNetworkMapStorageTest : TestBase() { assertThat(activeSignedNetworkMap.sig).isEqualTo(networkMapAndSigned.signed.sig) assertThat(activeNetworkParameters).isEqualTo(networkParameters) assertThat(activeSignedNetworkParameters.sig).isEqualTo(networkParametersSig) - assertThat(SecureHash.parse(activeNetworkParametersEntity.hash)) - .isEqualTo(activeNetworkMap.networkParameterHash) + assertThat(activeNetworkParametersEntity.hash) + .isEqualTo(activeNetworkMap.networkParameterHash.toString()) .isEqualTo(networkParametersHash) } @@ -127,27 +127,26 @@ class PersistentNetworkMapStorageTest : TestBase() { } @Test - fun `saveNewParametersUpdate clears the previous updates from database`() { - val testParameters1 = testNetworkParameters(epoch = 1) - val testParameters2 = testNetworkParameters(epoch = 2) - val hash1 = testParameters1.serialize().hash - val hash2 = testParameters2.serialize().hash - val updateDeadline1 = Instant.ofEpochMilli(random63BitValue()) - val updateDeadline2 = Instant.ofEpochMilli(random63BitValue()) - networkMapStorage.saveNewParametersUpdate(testParameters1, "Update 1", updateDeadline1) - networkMapStorage.saveNewParametersUpdate(testParameters1, "Update of update", updateDeadline1) - assertThat(networkMapStorage.getParametersUpdate()?.toParametersUpdate()).isEqualTo(ParametersUpdate(hash1, "Update of update", updateDeadline1)) - networkMapStorage.saveNewParametersUpdate(testParameters2, "Update 3", updateDeadline2) - assertThat(networkMapStorage.getParametersUpdate()?.toParametersUpdate()).isEqualTo(ParametersUpdate(hash2, "Update 3", updateDeadline2)) + fun `saveNewParametersUpdate marks update as NEW and persists network parameters as the latest`() { + val networkParameters = testNetworkParameters() + val updateDeadline = Instant.now() + 10.days + networkMapStorage.saveNewParametersUpdate(networkParameters, "Update 1", updateDeadline) + val parameterUpdate = networkMapStorage.getCurrentParametersUpdate()!! + assertThat(parameterUpdate.description).isEqualTo("Update 1") + assertThat(parameterUpdate.updateDeadline).isEqualTo(updateDeadline) + assertThat(parameterUpdate.status).isEqualTo(UpdateStatus.NEW) + assertThat(parameterUpdate.networkParameters.networkParameters).isEqualTo(networkParameters) + assertThat(networkMapStorage.getLatestNetworkParameters()?.networkParameters).isEqualTo(networkParameters) } @Test - fun `clear parameters update removes all parameters updates`() { - val params1 = testNetworkParameters(minimumPlatformVersion = 1) - val params2 = testNetworkParameters(minimumPlatformVersion = 2) - networkMapStorage.saveNewParametersUpdate(params1, "Update 1", Instant.ofEpochMilli(random63BitValue())) - networkMapStorage.saveNewParametersUpdate(params2, "Update 2", Instant.ofEpochMilli(random63BitValue())) - networkMapStorage.clearParametersUpdates() - assertThat(networkMapStorage.getParametersUpdate()).isNull() + fun `saveNewParametersUpdate marks previous update as cancelled`() { + networkMapStorage.saveNewParametersUpdate(testNetworkParameters(epoch = 1), "Update 1", Instant.now() + 1.days) + networkMapStorage.saveNewParametersUpdate(testNetworkParameters(epoch = 2), "Update of update", Instant.now() + 2.days) + val firstUpdate = persistence.transaction { + session.fromQuery("u where u.description = 'Update 1'").singleResult + } + assertThat(firstUpdate.status).isEqualTo(UpdateStatus.CANCELLED) + assertThat(networkMapStorage.getCurrentParametersUpdate()?.description).isEqualTo("Update of update") } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt index cbd8176d8b..cae4623552 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt @@ -11,6 +11,7 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase +import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity import com.r3.corda.networkmanage.common.utils.hashString import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash @@ -18,8 +19,8 @@ import net.corda.core.crypto.sha256 import net.corda.core.identity.CordaX500Name import net.corda.core.internal.CertRole import net.corda.core.serialization.serialize +import net.corda.core.utilities.days import net.corda.nodeapi.internal.NodeInfoAndSigned -import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities @@ -36,6 +37,7 @@ import org.junit.Before import org.junit.Test import java.security.PrivateKey import java.security.cert.X509Certificate +import java.time.Instant import javax.security.auth.x500.X500Principal import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -49,14 +51,12 @@ class PersistentNodeInfoStorageTest : TestBase() { private lateinit var persistence: CordaPersistence private lateinit var rootCaCert: X509Certificate private lateinit var doormanCertAndKeyPair: CertificateAndKeyPair - private lateinit var networkMapCertAndKeyPair: CertificateAndKeyPair @Before fun startDb() { val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() rootCaCert = rootCa.certificate this.doormanCertAndKeyPair = intermediateCa - networkMapCertAndKeyPair = createDevNetworkMapCa(rootCa) persistence = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)) nodeInfoStorage = PersistentNodeInfoStorage(persistence) requestStorage = PersistentCertificateSigningRequestStorage(persistence) @@ -142,14 +142,26 @@ class PersistentNodeInfoStorageTest : TestBase() { @Test fun `putNodeInfo persists SignedNodeInfo with its signature`() { // given - val (nodeInfoWithSigned) = createValidSignedNodeInfo("Test", requestStorage) + val (nodeInfoAndSigned) = createValidSignedNodeInfo("Test", requestStorage) // when - val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoWithSigned) + val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoAndSigned) // then val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) - assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signed.signatures) + assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoAndSigned.signed.signatures) + } + + @Test + fun `publish same node info twice`() { + fun singleNodeInfo() = persistence.transaction { session.fromQuery("").singleResult } + + val (nodeInfoAndSigned) = createValidSignedNodeInfo("Test", requestStorage) + nodeInfoStorage.putNodeInfo(nodeInfoAndSigned) + val nodeInfo = singleNodeInfo() + nodeInfoStorage.putNodeInfo(nodeInfoAndSigned) + assertThat(nodeInfo.publishedAt).isEqualTo(singleNodeInfo().publishedAt) // Check publishAt hasn't changed + assertThat(singleNodeInfo().isCurrent).isTrue() } @Test @@ -159,21 +171,21 @@ class PersistentNodeInfoStorageTest : TestBase() { // when val networkParameters = testNetworkParameters() - val sigWithCert = networkMapCertAndKeyPair.sign(networkParameters).sig - val netParamsHash = networkMapStorage.saveNetworkParameters(networkParameters, sigWithCert) + val netParamsHash = networkParameters.serialize().hash + networkMapStorage.saveNewParametersUpdate(networkParameters, "Update", Instant.now() + 1.days) val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoAndSigned) nodeInfoStorage.ackNodeInfoParametersUpdate(nodeInfoAndSigned.nodeInfo.legalIdentities[0].owningKey.encoded.sha256(), netParamsHash) // then - val acceptedNetworkParameters = nodeInfoStorage.getAcceptedNetworkParameters(nodeInfoHash) - assertThat(acceptedNetworkParameters?.hash).isEqualTo(netParamsHash.toString()) + val acceptedUpdate = nodeInfoStorage.getAcceptedParametersUpdate(nodeInfoHash) + assertThat(acceptedUpdate?.networkParameters?.hash).isEqualTo(netParamsHash.toString()) } @Test fun `updating node info after it's accepted network parameters`() { val networkParameters = testNetworkParameters() - val sigWithCert = networkMapCertAndKeyPair.sign(networkParameters).sig - val netParamsHash = networkMapStorage.saveNetworkParameters(networkParameters, sigWithCert) + val netParamsHash = networkParameters.serialize().hash + networkMapStorage.saveNewParametersUpdate(networkParameters, "Update", Instant.now() + 1.days) val (nodeInfoAndSigned, privateKey) = createValidSignedNodeInfo("Test", requestStorage) nodeInfoStorage.putNodeInfo(nodeInfoAndSigned) @@ -184,8 +196,8 @@ class PersistentNodeInfoStorageTest : TestBase() { val nodeInfoAndSigned2 = NodeInfoAndSigned(nodeInfo2.signWith(listOf(privateKey))) val nodeInfoHash2 = nodeInfoStorage.putNodeInfo(nodeInfoAndSigned2) - val acceptedNetworkParameters = nodeInfoStorage.getAcceptedNetworkParameters(nodeInfoHash2) - assertThat(acceptedNetworkParameters?.hash).isEqualTo(netParamsHash.toString()) + val acceptedUpdate = nodeInfoStorage.getAcceptedParametersUpdate(nodeInfoHash2) + assertThat(acceptedUpdate?.networkParameters?.hash).isEqualTo(netParamsHash.toString()) } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt index 63319faa03..0e304af1ea 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt @@ -14,6 +14,7 @@ import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.TestBase import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.entity.ParametersUpdateEntity +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus import com.r3.corda.networkmanage.createNetworkMapEntity import com.r3.corda.networkmanage.createNetworkParametersEntity import com.r3.corda.networkmanage.createNetworkParametersEntityUnsigned @@ -72,7 +73,7 @@ class NetworkMapSignerTest : TestBase() { whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetParamsEntity) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(nodeInfoHashes) whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) - whenever(networkMapStorage.getParametersUpdate()).thenReturn(null) + whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(null) // when networkMapSigner.signNetworkMap() @@ -81,7 +82,7 @@ class NetworkMapSignerTest : TestBase() { // Verify networkMapStorage calls verify(networkMapStorage).getActiveNodeInfoHashes() verify(networkMapStorage).getActiveNetworkMap() - verify(networkMapStorage).getParametersUpdate() + verify(networkMapStorage).getCurrentParametersUpdate() verify(networkMapStorage).getLatestNetworkParameters() argumentCaptor().apply { verify(networkMapStorage).saveNewActiveNetworkMap(capture()) @@ -122,7 +123,7 @@ class NetworkMapSignerTest : TestBase() { whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntityUnsigned(netParams)) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) - whenever(networkMapStorage.getParametersUpdate()).thenReturn(null) + whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(null) // when networkMapSigner.signNetworkMap() @@ -132,7 +133,7 @@ class NetworkMapSignerTest : TestBase() { verify(networkMapStorage).getActiveNodeInfoHashes() verify(networkMapStorage).getActiveNetworkMap() verify(networkMapStorage).getLatestNetworkParameters() - verify(networkMapStorage).getParametersUpdate() + verify(networkMapStorage).getCurrentParametersUpdate() argumentCaptor().apply { verify(networkMapStorage).saveNewActiveNetworkMap(capture()) assertEquals(netParams.serialize().hash, firstValue.networkMap.networkParameterHash) @@ -152,7 +153,7 @@ class NetworkMapSignerTest : TestBase() { val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue())) val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, currentNetworkParameters, emptyList(), null) whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity) - whenever(networkMapStorage.getParametersUpdate()).thenReturn(parametersUpdate) + whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(parametersUpdate) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(updateNetworkParameters) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) @@ -164,7 +165,7 @@ class NetworkMapSignerTest : TestBase() { verify(networkMapStorage).getActiveNetworkMap() verify(networkMapStorage).getActiveNodeInfoHashes() verify(networkMapStorage).getLatestNetworkParameters() - verify(networkMapStorage).getParametersUpdate() + verify(networkMapStorage).getCurrentParametersUpdate() val paramsCaptor = argumentCaptor() val signatureCaptor = argumentCaptor() @@ -178,7 +179,7 @@ class NetworkMapSignerTest : TestBase() { val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2)) val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue())) whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) - whenever(networkMapStorage.getParametersUpdate()).thenReturn(parametersUpdate) + whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(parametersUpdate) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity()) @@ -195,7 +196,7 @@ class NetworkMapSignerTest : TestBase() { whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(activeNetworkMap) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) - whenever(networkMapStorage.getParametersUpdate()).thenReturn(parametersUpdate.copy(flagDay = true)) + whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(parametersUpdate.copy(status = UpdateStatus.FLAG_DAY)) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(updateNetworkParameters) // when @@ -220,7 +221,7 @@ class NetworkMapSignerTest : TestBase() { whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(activeNetworkMap) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) - whenever(networkMapStorage.getParametersUpdate()).thenReturn(null) + whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(null) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity()) // when