From 7f8c36faa0b90fef1bb7dc78419a0d0e4054c488 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 13 Apr 2018 15:54:53 +0100 Subject: [PATCH] ENT-1586 Update entire service stack to support private network maps (#691) * private network map --- .../hsm/HsmSigningServiceTest.kt | 4 +- .../common/persistence/NetworkMapStorage.kt | 21 +++-- .../PersistentNetworkMapStorage.kt | 60 +++++++------ .../persistence/entity/NetworkMapEntity.kt | 14 ++- .../common/signer/NetworkMapSigner.kt | 83 +++++++++-------- .../corda/networkmanage/common/utils/Utils.kt | 2 + .../doorman/NetworkManagementServer.kt | 6 +- .../webservice/NetworkMapWebService.kt | 41 +++++---- .../hsm/processor/NetworkMapProcessor.kt | 2 +- .../network-manager.changelog-master.xml | 1 + ...ger.changelog-network-map-table-change.xml | 69 ++++++++++++++ .../com/r3/corda/networkmanage/TestUtils.kt | 40 ++++++--- .../PersistentNetworkMapStorageTest.kt | 54 ++++++----- .../PersistentNodeInfoStorageTest.kt | 7 +- .../common/signer/NetworkMapSignerTest.kt | 88 +++++++++--------- .../webservice/NetworkMapWebServiceTest.kt | 90 ++++++++++++++++--- 16 files changed, 394 insertions(+), 188 deletions(-) create mode 100644 network-management/src/main/resources/migration/network-manager.changelog-network-map-table-change.xml diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt index a36eb089d3..a5dcf03c36 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt @@ -164,10 +164,10 @@ class HsmSigningServiceTest : HsmBaseTest() { // when initialiseSerialization() networkMapStorage.saveNetworkParameters(networkMapParameters, hsmDataSigner.signBytes(networkMapParameters.serialize().bytes)) - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() // then - val persistedNetworkMap = networkMapStorage.getActiveNetworkMap()!!.toSignedNetworkMap().verified() + val persistedNetworkMap = networkMapStorage.getNetworkMaps().publicNetworkMap!!.toSignedNetworkMap().verified() assertEquals(networkMapParameters.serialize().hash, persistedNetworkMap.networkParameterHash) assertThat(persistedNetworkMap.nodeInfoHashes).isEmpty() } 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 c9c73d33bb..eaf8757ab7 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 @@ -22,24 +22,25 @@ import java.time.Instant * Data access object interface for NetworkMap persistence layer */ // TODO This storage abstraction needs some thought. Some of the methods clearly don't make sense e.g. setParametersUpdateStatus. +// TODO: We should avoid exposing entity objects. // 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 + * Returns the network maps containing public network map and private network maps. */ - fun getActiveNetworkMap(): NetworkMapEntity? + fun getNetworkMaps(): NetworkMaps /** - * Persist the new active network map, replacing any existing network map. + * Persist the new network map for provided network ID, replacing any existing network map. + * The map will be stored as public network map if [networkId] = null. */ - fun saveNewActiveNetworkMap(networkMapAndSigned: NetworkMapAndSigned) + fun saveNewNetworkMap(networkId: String? = null, networkMapAndSigned: NetworkMapAndSigned) /** - * Retrieves node info hashes where [NodeInfoEntity.isCurrent] is true and the certificate status is [CertificateStatus.VALID] + * Retrieves node info hashes for both public and private networks where [NodeInfoEntity.isCurrent] is true and the certificate status is [CertificateStatus.VALID] * Nodes should have declared that they are using correct set of parameters. */ - // TODO "Active" is the wrong word here - fun getActiveNodeInfoHashes(): List + fun getNodeInfoHashes(): NodeInfoHashes /** * Retrieve the signed with certificate network parameters by their hash. The hash is that of the underlying @@ -79,3 +80,9 @@ interface NetworkMapStorage { */ fun switchFlagDay(update: ParametersUpdateEntity) } + +data class NetworkMaps(val publicNetworkMap: NetworkMapEntity?, val privateNetworkMap: Map) { + val allNodeInfoHashes: Set = privateNetworkMap.flatMap { it.value.networkMap.nodeInfoHashes }.toSet() + (publicNetworkMap?.networkMap?.nodeInfoHashes ?: emptySet()) +} + +data class NodeInfoHashes(val publicNodeInfoHashes: List, val privateNodeInfoHashes: Map>) \ No newline at end of file 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 3d61fc789c..d8432d3a78 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 @@ -26,20 +26,21 @@ import java.time.Instant * Database implementation of the [NetworkMapStorage] interface */ class PersistentNetworkMapStorage(private val database: CordaPersistence) : NetworkMapStorage { - override fun getActiveNetworkMap(): NetworkMapEntity? { + companion object { + // Used internally to identify global network map in database table. + private const val PUBLIC_NETWORK_ID = "PUBLIC_NETWORK" + } + + override fun getNetworkMaps(): NetworkMaps { return database.transaction { - val builder = session.criteriaBuilder - val query = builder.createQuery(NetworkMapEntity::class.java).run { - from(NetworkMapEntity::class.java).run { - orderBy(builder.desc(get(NetworkMapEntity::version.name))) - } - } - // We want the latest signed entry - session.createQuery(query).setMaxResults(1).uniqueResult() + val networkMapEntities = session.createQuery("from ${NetworkMapEntity::class.java.name}", NetworkMapEntity::class.java) + .resultList + .associateBy { it.id } + NetworkMaps(networkMapEntities[PUBLIC_NETWORK_ID], networkMapEntities.filterKeys { it != PUBLIC_NETWORK_ID }) } } - override fun saveNewActiveNetworkMap(networkMapAndSigned: NetworkMapAndSigned) { + override fun saveNewNetworkMap(networkId: String?, networkMapAndSigned: NetworkMapAndSigned) { val (networkMap, signedNetworkMap) = networkMapAndSigned database.transaction { val networkParametersEntity = checkNotNull(getNetworkParametersEntity(networkMap.networkParameterHash)) { @@ -48,7 +49,8 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw check(networkParametersEntity.isSigned) { "Network parameters ${networkMap.networkParameterHash} are not signed" } - session.save(NetworkMapEntity( + session.merge(NetworkMapEntity( + id = networkId ?: PUBLIC_NETWORK_ID, networkMap = networkMap, signature = signedNetworkMap.sig.bytes, certificate = signedNetworkMap.sig.by, @@ -65,10 +67,10 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw } } - override fun getActiveNodeInfoHashes(): List { + override fun getNodeInfoHashes(): NodeInfoHashes { return database.transaction { val builder = session.criteriaBuilder - val query = builder.createQuery(String::class.java).run { + val query = builder.createTupleQuery().run { from(NodeInfoEntity::class.java).run { val certStatusExpression = get(NodeInfoEntity::certificateSigningRequest.name) .get(CertificateSigningRequestEntity::certificateData.name) @@ -77,10 +79,16 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw // isn't needed. val certStatusEq = builder.equal(certStatusExpression, CertificateStatus.VALID) val isCurrentNodeInfo = builder.isTrue(get(NodeInfoEntity::isCurrent.name)) - select(get(NodeInfoEntity::nodeInfoHash.name)).where(builder.and(certStatusEq, isCurrentNodeInfo)) + + val networkIdSelector = get(NodeInfoEntity::certificateSigningRequest.name) + .get(CertificateSigningRequestEntity::privateNetwork.name) + .get(PrivateNetworkEntity::networkId.name) + + multiselect(networkIdSelector, get(NodeInfoEntity::nodeInfoHash.name)).where(builder.and(certStatusEq, isCurrentNodeInfo)) } } - session.createQuery(query).resultList.map { SecureHash.parse(it) } + val allNodeInfos = session.createQuery(query).resultList.groupBy { it[0]?.toString() ?: PUBLIC_NETWORK_ID }.mapValues { it.value.map { SecureHash.parse(it.get(1, String::class.java)) } } + NodeInfoHashes(allNodeInfos[PUBLIC_NETWORK_ID] ?: emptyList(), allNodeInfos.filterKeys { it != PUBLIC_NETWORK_ID }) } } @@ -90,19 +98,15 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw val hash = serialized.hash return database.transaction { val entity = getNetworkParametersEntity(hash) - val newNetworkParamsEntity = if (entity != null) { - entity.copy( - signature = signature?.bytes, - certificate = signature?.by - ) - } else { - NetworkParametersEntity( - networkParameters = networkParameters, - hash = hash.toString(), - signature = signature?.bytes, - certificate = signature?.by - ) - } + val newNetworkParamsEntity = entity?.copy( + signature = signature?.bytes, + certificate = signature?.by + ) ?: NetworkParametersEntity( + networkParameters = networkParameters, + hash = hash.toString(), + signature = signature?.bytes, + certificate = signature?.by + ) session.merge(newNetworkParamsEntity) as NetworkParametersEntity } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt index 86dd4b73de..0b0d841264 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt @@ -14,16 +14,20 @@ import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap +import org.hibernate.envers.Audited +import org.hibernate.envers.RelationTargetAuditMode import java.io.Serializable import java.security.cert.X509Certificate +import java.time.Instant import javax.persistence.* @Entity +@Audited @Table(name = "network_map") class NetworkMapEntity( @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) - val version: Long? = null, + @Column(name = "id") + val id: String, @Lob @Column(name = "serialized_network_map", nullable = false) @@ -41,7 +45,11 @@ class NetworkMapEntity( @ManyToOne(optional = false, fetch = FetchType.EAGER) @JoinColumn(name = "network_parameters", nullable = false) - val networkParameters: NetworkParametersEntity + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) + val networkParameters: NetworkParametersEntity, + + @Column(nullable = false) + val timestamp: Instant = Instant.now() ) : Serializable { fun toSignedNetworkMap(): SignedNetworkMap { return SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(certificate, signature)) 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 d680842844..7fc38f29a5 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,13 +11,17 @@ 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 com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus.FLAG_DAY +import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus.NEW +import com.r3.corda.networkmanage.common.utils.join import net.corda.core.crypto.SecureHash import net.corda.core.node.NetworkParameters import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMapAndSigned +import net.corda.nodeapi.internal.network.ParametersUpdate class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) { private companion object { @@ -27,12 +31,46 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private /** * Signs the network map and latest network parameters if they haven't been signed yet. */ - fun signNetworkMap() { - val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters() - if (latestNetworkParameters == null) { - logger.debug("No network parameters present") - return + fun signNetworkMaps() { + val (publicNetworkMap, privateNetworkMaps) = networkMapStorage.getNetworkMaps() + val (publicNodeInfoHashes, privateNodeInfoHashes) = networkMapStorage.getNodeInfoHashes() + + val (networkParameterHash, parametersUpdate) = maybeUpdateNetworkParameters(publicNetworkMap?.networkMap?.networkParameterHash) + logger.debug { "Current network parameters: $networkParameterHash" } + + // Process public network map. + maybeSignNetworkMap(publicNetworkMap, publicNodeInfoHashes, parametersUpdate, networkParameterHash) + + // Process each private network map. + privateNetworkMaps.join(privateNodeInfoHashes).forEach { networkId, (currentNetworkMap, nodeInfoHashes) -> + maybeSignNetworkMap(currentNetworkMap, nodeInfoHashes, parametersUpdate, networkParameterHash, networkId) } + } + + private fun maybeSignNetworkMap(currentNetworkMap: NetworkMapEntity?, nodeInfoHashes: List?, parametersUpdate: ParametersUpdate?, networkParameterHash: SecureHash, networkId: String? = null) { + val printableNetworkId = networkId ?: "Public Network" + if (currentNetworkMap == null) { + logger.info("There is currently no network map for network '$printableNetworkId'") + } else { + logger.debug { "Current network map for network '$printableNetworkId': ${currentNetworkMap.networkMap}" } + } + + logger.debug { "Retrieved node info hashes for network '$printableNetworkId' :\n${nodeInfoHashes?.joinToString("\n")}" } + + val newNetworkMap = NetworkMap(nodeInfoHashes ?: emptyList(), networkParameterHash, parametersUpdate) + logger.debug { "Potential new network map for network '$printableNetworkId': $newNetworkMap" } + + if (currentNetworkMap?.networkMap != newNetworkMap) { + val newNetworkMapAndSigned = NetworkMapAndSigned(newNetworkMap) { signer.signBytes(it.bytes) } + networkMapStorage.saveNewNetworkMap(networkId, newNetworkMapAndSigned) + logger.info("Signed new network map for network '$printableNetworkId' : $newNetworkMap") + } else { + logger.debug("Current network map for network '$printableNetworkId' is up-to-date") + } + } + + private fun maybeUpdateNetworkParameters(currentNetworkParametersHash: SecureHash?): Pair { + val latestNetworkParameters = requireNotNull(networkMapStorage.getLatestNetworkParameters()) { "No network parameters present" } logger.debug { "Retrieved latest network parameters: ${latestNetworkParameters.networkParameters}" } val parametersUpdate = networkMapStorage.getCurrentParametersUpdate() @@ -41,16 +79,6 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private "The latest network parameters are not the scheduled updated ones" } - val activeNetworkMap = networkMapStorage.getActiveNetworkMap() - if (activeNetworkMap == null) { - logger.info("There is currently no network map") - } else { - logger.debug { "Current network map: ${activeNetworkMap.networkMap}" } - } - - val activeNetworkParameters = activeNetworkMap?.networkParameters - logger.debug { "Current network map parameters: ${activeNetworkParameters?.networkParameters}" } - // We persist signed parameters only if they were not persisted before (they are not in currentSignedNetworkMap as // normal parameters or as an update) if (!latestNetworkParameters.isSigned) { @@ -59,29 +87,14 @@ 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?.status == FLAG_DAY || activeNetworkParameters == null) { + val parametersToNetworkMap = if (parametersUpdate?.status == FLAG_DAY || currentNetworkParametersHash == null) { parametersUpdate?.let { networkMapStorage.switchFlagDay(it) } - latestNetworkParameters + SecureHash.parse(latestNetworkParameters.hash) } else { - activeNetworkParameters + currentNetworkParametersHash } - val nodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes() - logger.debug { "Retrieved node info hashes:\n${nodeInfoHashes.joinToString("\n")}" } - - val newNetworkMap = NetworkMap( - nodeInfoHashes, - SecureHash.parse(parametersToNetworkMap.hash), - parametersUpdate?.let { if (it.status == NEW) it.toParametersUpdate() else null }) - logger.debug { "Potential new network map: $newNetworkMap" } - - if (activeNetworkMap?.networkMap != newNetworkMap) { - val newNetworkMapAndSigned = NetworkMapAndSigned(newNetworkMap) { signer.signBytes(it.bytes) } - networkMapStorage.saveNewActiveNetworkMap(newNetworkMapAndSigned) - logger.info("Signed new network map: $newNetworkMap") - } else { - logger.debug("Current network map is up-to-date") - } + return Pair(parametersToNetworkMap, parametersUpdate?.let { if (it.status == NEW) it.toParametersUpdate() else null }) } fun signAndPersistNetworkParameters(networkParameters: NetworkParameters) { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt index 3028e2a37e..5c408796c7 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt @@ -86,3 +86,5 @@ fun PKCS10CertificationRequest.getCertRole(): CertRole { * Helper method to extract email from certificate signing request. */ fun PKCS10CertificationRequest.getEmail(): String = firstAttributeValue(BCStyle.E).toString() + +fun Map.join(otherMap: Map): Map> = (keys + otherMap.keys).map { it to Pair(get(it), otherMap[it]) }.toMap() 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 60caebc71b..d4f8a1873b 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 @@ -84,7 +84,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, val scheduledExecutor = Executors.newScheduledThreadPool(1) scheduledExecutor.scheduleAtFixedRate({ try { - localNetworkMapSigner.signNetworkMap() + localNetworkMapSigner.signNetworkMaps() } catch (e: Exception) { // Log the error and carry on. logger.error("Unable to sign network map", e) @@ -201,7 +201,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, private fun handleSetNetworkParameters(setNetParams: NetworkParametersCmd.Set) { logger.info("maxMessageSize is not currently wired in the nodes") - val activeNetParams = networkMapStorage.getActiveNetworkMap()?.networkParameters?.networkParameters + val activeNetParams = networkMapStorage.getNetworkMaps().publicNetworkMap?.networkParameters?.networkParameters if (activeNetParams == null) { require(setNetParams.parametersUpdate == null) { "'parametersUpdate' specified in network parameters file but there are no network parameters to update" @@ -253,7 +253,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, 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 activeNetParams = networkMapStorage.getNetworkMaps().publicNetworkMap?.networkParameters check(parametersUpdate.networkParameters.isSigned) { "Parameters we are trying to switch to haven't been signed yet" } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt index 5a3fbf7082..313c15f090 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt @@ -14,13 +14,14 @@ import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.LoadingCache import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage +import com.r3.corda.networkmanage.common.persistence.NetworkMaps import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage +import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity import com.r3.corda.networkmanage.doorman.NetworkMapConfig import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData -import net.corda.core.crypto.sha256 import net.corda.core.internal.CertRole import net.corda.core.internal.readObject import net.corda.core.node.NetworkParameters @@ -34,12 +35,14 @@ import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.X509Utilities.validateCertPath import net.corda.nodeapi.internal.crypto.x509 import net.corda.nodeapi.internal.crypto.x509Certificates -import net.corda.nodeapi.internal.network.SignedNetworkMap import java.io.InputStream import java.security.InvalidKeyException import java.security.SignatureException import java.security.cert.CertPathValidatorException import java.time.Duration +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit import javax.servlet.http.HttpServletRequest import javax.ws.rs.* @@ -60,15 +63,11 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, const val NETWORK_MAP_PATH = "network-map" } - private val networkMapCache: LoadingCache = Caffeine.newBuilder() + private val networkMapCache: LoadingCache = Caffeine.newBuilder() .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) .build { - networkMapStorage.getActiveNetworkMap()?.let { - logger.info("Re-publishing network map") - val networkMap = it.networkMap - val signedNetworkMap = it.toSignedNetworkMap() - CachedData(signedNetworkMap, networkMap.nodeInfoHashes.toSet(), it.networkParameters.networkParameters) - } + logger.info("Re-publishing network map") + networkMapStorage.getNetworkMaps() } private val nodeInfoCache: LoadingCache = Caffeine.newBuilder() @@ -76,9 +75,9 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, .softValues() .build(nodeInfoStorage::getNodeInfo) - private val currentSignedNetworkMap: SignedNetworkMap? get() = networkMapCache.get(true)?.signedNetworkMap - private val currentNodeInfoHashes: Set get() = networkMapCache.get(true)?.nodeInfoHashes ?: emptySet() - private val currentNetworkParameters: NetworkParameters? get() = networkMapCache.get(true)?.networkParameters + private val networkMaps: NetworkMaps? get() = networkMapCache[true] + private val currentNodeInfoHashes: Set get() = networkMaps?.allNodeInfoHashes ?: emptySet() + private val currentNetworkParameters: NetworkParameters? get() = networkMaps?.publicNetworkMap?.networkParameters?.networkParameters @POST @Path("publish") @@ -124,7 +123,14 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) - fun getNetworkMap(): Response = createResponse(currentSignedNetworkMap, addCacheTimeout = true) + fun getNetworkMap(): Response = createNetworkMapResponse(networkMaps?.publicNetworkMap) + + @GET + @Path("{id}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getNetworkMap(@PathParam("id") privateNetworkID: String?): Response = createNetworkMapResponse(networkMaps?.privateNetworkMap?.get(privateNetworkID)) + + private fun createNetworkMapResponse(networkMap: NetworkMapEntity?) = createResponse(networkMap?.toSignedNetworkMap(), addCacheTimeout = true, timestamp = networkMap?.timestamp) @GET @Path("node-info/{nodeInfoHash}") @@ -182,12 +188,15 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, } } - private fun createResponse(payload: Any?, addCacheTimeout: Boolean = false): Response { + private fun createResponse(payload: Any?, addCacheTimeout: Boolean = false, timestamp: Instant? = null): Response { return if (payload != null) { val ok = Response.ok(payload.serialize().bytes) if (addCacheTimeout) { ok.header("Cache-Control", "max-age=${Duration.ofMillis(config.cacheTimeout).seconds}") } + timestamp?.let { + ok.header("Last-Modified", DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("GMT")).format(it)) + } ok } else { status(Response.Status.NOT_FOUND) @@ -211,8 +220,4 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, class NetworkMapNotInitialisedException(message: String?) : Exception(message) class RequestException(message: String) : Exception(message) - - private data class CachedData(val signedNetworkMap: SignedNetworkMap, - val nodeInfoHashes: Set, - val networkParameters: NetworkParameters) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt index dc65244d59..a513755ba8 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt @@ -55,7 +55,7 @@ class NetworkMapProcessor(private val config: NetworkMapCertificateConfig, val networkMapSigner = NetworkMapSigner(networkMapStorage, signer) try { logger.info("Executing network map signing...") - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() } catch (e: Exception) { logger.error("Exception thrown while signing network map", e) } diff --git a/network-management/src/main/resources/migration/network-manager.changelog-master.xml b/network-management/src/main/resources/migration/network-manager.changelog-master.xml index 7ff18a526c..f1b23a1e6f 100644 --- a/network-management/src/main/resources/migration/network-manager.changelog-master.xml +++ b/network-management/src/main/resources/migration/network-manager.changelog-master.xml @@ -13,4 +13,5 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> + diff --git a/network-management/src/main/resources/migration/network-manager.changelog-network-map-table-change.xml b/network-management/src/main/resources/migration/network-manager.changelog-network-map-table-change.xml new file mode 100644 index 0000000000..293679aa31 --- /dev/null +++ b/network-management/src/main/resources/migration/network-manager.changelog-network-map-table-change.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt index ac2e9c78f7..aa03ef442c 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt @@ -1,5 +1,6 @@ package com.r3.corda.networkmanage +import com.r3.corda.networkmanage.common.persistence.NetworkMaps import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity import net.corda.core.crypto.SecureHash @@ -10,6 +11,7 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.ParametersUpdate import net.corda.testing.common.internal.testNetworkParameters +import java.time.Instant fun createNetworkParametersEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), networkParameters: NetworkParameters = testNetworkParameters()): NetworkParametersEntity { @@ -31,23 +33,41 @@ fun createNetworkParametersEntityUnsigned(networkParameters: NetworkParameters = ) } -fun createNetworkMapEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), - netParamsEntity: NetworkParametersEntity, - nodeInfoHashes: List = emptyList(), - parametersUpdate: ParametersUpdate? = null): NetworkMapEntity { +fun createNetworkMaps(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), + netParamsEntity: NetworkParametersEntity, + nodeInfoHashes: List = emptyList(), + privateNodeInfoHashes: Map> = emptyMap(), + parametersUpdate: ParametersUpdate? = null, + timestamp: Instant = Instant.now()): NetworkMaps { + val publicMapEntity = createNetworkMapEntity("PUBLIC_NETWORK", nodeInfoHashes, netParamsEntity, parametersUpdate, signingCertAndKeyPair, timestamp) + val privateNetworkMaps = privateNodeInfoHashes.mapValues { + createNetworkMapEntity(it.key, it.value, netParamsEntity, parametersUpdate, signingCertAndKeyPair, timestamp) + } + return NetworkMaps(publicMapEntity, privateNetworkMaps) +} + +private fun createNetworkMapEntity(id: String, + nodeInfoHashes: List, + netParamsEntity: NetworkParametersEntity, + parametersUpdate: ParametersUpdate?, + signingCertAndKeyPair: CertificateAndKeyPair, + timestamp: Instant): NetworkMapEntity { val networkMap = NetworkMap(nodeInfoHashes, SecureHash.parse(netParamsEntity.hash), parametersUpdate) val signedNetworkMap = signingCertAndKeyPair.sign(networkMap) return NetworkMapEntity( + id = id, networkMap = networkMap, signature = signedNetworkMap.sig.bytes, certificate = signedNetworkMap.sig.by, - networkParameters = netParamsEntity - ) + networkParameters = netParamsEntity, + timestamp = timestamp) } -fun createNetworkMapEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), - networkParameters: NetworkParameters = testNetworkParameters(), - nodeInfoHashes: List = emptyList()): NetworkMapEntity { +fun createNetworkMaps(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), + networkParameters: NetworkParameters = testNetworkParameters(), + nodeInfoHashes: List = emptyList(), + privateNodeInfoHashes: Map> = emptyMap(), + timestamp: Instant = Instant.now()): NetworkMaps { val netParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair, networkParameters) - return createNetworkMapEntity(signingCertAndKeyPair, netParamsEntity, nodeInfoHashes) + return createNetworkMaps(signingCertAndKeyPair, netParamsEntity, nodeInfoHashes, privateNodeInfoHashes, timestamp = timestamp) } 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 4f41e03d75..d5a329e54f 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 @@ -13,6 +13,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.persistence.entity.ParametersUpdateEntity +import com.r3.corda.networkmanage.common.persistence.entity.PrivateNetworkEntity import com.r3.corda.networkmanage.common.persistence.entity.UpdateStatus import net.corda.core.crypto.SecureHash import net.corda.core.serialization.serialize @@ -33,6 +34,7 @@ import org.junit.Before import org.junit.Test import java.security.cert.X509Certificate import java.time.Instant +import java.util.* class PersistentNetworkMapStorageTest : TestBase() { private lateinit var persistence: CordaPersistence @@ -74,23 +76,15 @@ class PersistentNetworkMapStorageTest : TestBase() { val networkMapAndSigned = NetworkMapAndSigned(networkMap) { networkMapCertAndKeyPair.sign(networkMap).sig } // when - networkMapStorage.saveNewActiveNetworkMap(networkMapAndSigned) + networkMapStorage.saveNewNetworkMap(networkMapAndSigned = networkMapAndSigned) // then - val activeNetworkMapEntity = networkMapStorage.getActiveNetworkMap()!! - val activeSignedNetworkMap = activeNetworkMapEntity.toSignedNetworkMap() + val networkMaps = networkMapStorage.getNetworkMaps() + val activeSignedNetworkMap = networkMaps.publicNetworkMap!!.toSignedNetworkMap() val activeNetworkMap = activeSignedNetworkMap.verifiedNetworkMapCert(rootCaCert) - val activeNetworkParametersEntity = activeNetworkMapEntity.networkParameters - val activeSignedNetworkParameters = activeNetworkParametersEntity.toSignedNetworkParameters() - val activeNetworkParameters = activeSignedNetworkParameters.verifiedNetworkMapCert(rootCaCert) assertThat(activeNetworkMap).isEqualTo(networkMap) assertThat(activeSignedNetworkMap.sig).isEqualTo(networkMapAndSigned.signed.sig) - assertThat(activeNetworkParameters).isEqualTo(networkParameters) - assertThat(activeSignedNetworkParameters.sig).isEqualTo(networkParametersSig) - assertThat(activeNetworkParametersEntity.hash) - .isEqualTo(activeNetworkMap.networkParameterHash.toString()) - .isEqualTo(networkParametersHash) } @Test @@ -105,26 +99,40 @@ class PersistentNetworkMapStorageTest : TestBase() { } @Test - fun `getValidNodeInfoHashes returns only for current node-infos`() { + fun `getValidNodeInfoHashes returns node-infos for public and private networks`() { // given // Create node infos. - val (signedNodeInfoA) = createValidSignedNodeInfo("TestA", requestStorage) - val (signedNodeInfoB) = createValidSignedNodeInfo("TestB", requestStorage) + + val nodes = listOf("TestA", "TestB", "TestC", "TestD", "TestE") + + val nodeInfos = nodes.map { it to createValidSignedNodeInfo(it, requestStorage) }.toMap() // Put signed node info data - val nodeInfoHashA = nodeInfoStorage.putNodeInfo(signedNodeInfoA) - val nodeInfoHashB = nodeInfoStorage.putNodeInfo(signedNodeInfoB) - persistence.transaction { - val entity = session.find(NodeInfoEntity::class.java, nodeInfoHashA.toString()) - session.merge(entity.copy(isCurrent = false)) + val storedNodeInfos = nodeInfos.mapValues { nodeInfoStorage.putNodeInfo(it.value.first) } + + val (testNet1, testNet2) = persistence.transaction { + val testNet1 = PrivateNetworkEntity(UUID.randomUUID().toString(), "TestNet1").apply { session.save(this) } + val testNet2 = PrivateNetworkEntity(UUID.randomUUID().toString(), "TestNet2").apply { session.save(this) } + + // set different private network for node info + session.find(NodeInfoEntity::class.java, storedNodeInfos["TestA"].toString()).apply { + session.merge(certificateSigningRequest.copy(privateNetwork = testNet1)) + } + session.find(NodeInfoEntity::class.java, storedNodeInfos["TestC"].toString()).apply { + session.merge(certificateSigningRequest.copy(privateNetwork = testNet2)) + } + Pair(testNet1, testNet2) } // when - val validNodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes() + val nodeInfoHashes = networkMapStorage.getNodeInfoHashes() // then - assertThat(validNodeInfoHashes).containsOnly(nodeInfoHashB) + assertThat(nodeInfoHashes.publicNodeInfoHashes).containsOnlyElementsOf(storedNodeInfos.filterKeys { it !in setOf("TestA", "TestC") }.values) + assertThat(nodeInfoHashes.privateNodeInfoHashes.keys).containsOnlyElementsOf(listOf(testNet1.networkId, testNet2.networkId)) + assertThat(nodeInfoHashes.privateNodeInfoHashes[testNet1.networkId]).containsOnlyElementsOf(listOf(storedNodeInfos["TestA"])) + assertThat(nodeInfoHashes.privateNodeInfoHashes[testNet2.networkId]).containsOnlyElementsOf(listOf(storedNodeInfos["TestC"])) } @Test @@ -169,7 +177,7 @@ class PersistentNetworkMapStorageTest : TestBase() { val parameterUpdate = networkMapStorage.getCurrentParametersUpdate()!! networkMapStorage.switchFlagDay(parameterUpdate) // when - val validNodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes() + val validNodeInfoHashes = networkMapStorage.getNodeInfoHashes().publicNodeInfoHashes // then assertThat(validNodeInfoHashes).containsOnly(nodeInfoHashB) } @@ -196,7 +204,7 @@ class PersistentNetworkMapStorageTest : TestBase() { val parameterUpdate = networkMapStorage.getCurrentParametersUpdate()!! networkMapStorage.switchFlagDay(parameterUpdate) // when - val validNodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes() + val validNodeInfoHashes = networkMapStorage.getNodeInfoHashes().publicNodeInfoHashes // then assertThat(validNodeInfoHashes).containsOnly(nodeInfoHashB) } 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 0f87e44d76..af1b2b80d1 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.NetworkMapEntity import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name @@ -127,14 +128,14 @@ class PersistentNodeInfoStorageTest : TestBase() { val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(node1) assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified()) - assertTrue(networkMapStorage.getActiveNodeInfoHashes().contains(nodeInfo1Hash)) + assertTrue(networkMapStorage.getNodeInfoHashes().publicNodeInfoHashes.contains(nodeInfo1Hash)) // This should replace the node info. val nodeInfo2Hash = nodeInfoStorage.putNodeInfo(node2) // Old node info should be removed from list of current node info hashes, but still accessible if required. - assertThat(networkMapStorage.getActiveNodeInfoHashes()).doesNotContain(nodeInfo1Hash) - assertThat(networkMapStorage.getActiveNodeInfoHashes()).contains(nodeInfo2Hash) + assertThat(networkMapStorage.getNodeInfoHashes().publicNodeInfoHashes).doesNotContain(nodeInfo1Hash) + assertThat(networkMapStorage.getNodeInfoHashes().publicNodeInfoHashes).contains(nodeInfo2Hash) assertNotNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash)) assertEquals(nodeInfo2, nodeInfoStorage.getNodeInfo(nodeInfo2.serialize().hash)?.verified()) } 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 0e304af1ea..58aad75253 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 @@ -13,9 +13,11 @@ package com.r3.corda.networkmanage.common.signer 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.NetworkMaps +import com.r3.corda.networkmanage.common.persistence.NodeInfoHashes 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.createNetworkMaps import com.r3.corda.networkmanage.createNetworkParametersEntity import com.r3.corda.networkmanage.createNetworkParametersEntityUnsigned import net.corda.core.crypto.Crypto @@ -67,29 +69,29 @@ class NetworkMapSignerTest : TestBase() { @Test fun `signNetworkMap builds and signs network map and network parameters`() { // given - val nodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()) + val nodeInfoHashes = NodeInfoHashes(listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()), emptyMap()) val latestNetParams = testNetworkParameters(epoch = 3) val latestNetParamsEntity = createNetworkParametersEntityUnsigned(latestNetParams) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetParamsEntity) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(nodeInfoHashes) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(nodeInfoHashes) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(NetworkMaps(null, emptyMap())) whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(null) // when - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() // then // Verify networkMapStorage calls - verify(networkMapStorage).getActiveNodeInfoHashes() - verify(networkMapStorage).getActiveNetworkMap() + verify(networkMapStorage).getNodeInfoHashes() + verify(networkMapStorage).getNetworkMaps() verify(networkMapStorage).getCurrentParametersUpdate() verify(networkMapStorage).getLatestNetworkParameters() argumentCaptor().apply { - verify(networkMapStorage).saveNewActiveNetworkMap(capture()) + verify(networkMapStorage).saveNewNetworkMap(anyOrNull(), capture()) val capturedNetworkMap = firstValue.networkMap // Parameters in network map got swapped for latest ones. assertEquals(latestNetParams.serialize().hash, capturedNetworkMap.networkParameterHash) - assertThat(capturedNetworkMap.nodeInfoHashes).isEqualTo(nodeInfoHashes) + assertThat(capturedNetworkMap.nodeInfoHashes).isEqualTo(nodeInfoHashes.publicNodeInfoHashes) } val paramsCaptor = argumentCaptor() val signatureCaptor = argumentCaptor() @@ -102,17 +104,17 @@ class NetworkMapSignerTest : TestBase() { fun `signNetworkMap does NOT create a new network map if there are no changes`() { // given val netParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair) - val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, netParamsEntity, emptyList()) + val netMapEntity = createNetworkMaps(signingCertAndKeyPair, netParamsEntity, emptyList()) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(netParamsEntity) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(NodeInfoHashes(emptyList(), emptyMap())) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(netMapEntity) // when - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() // then // Verify networkMapStorage is not called - verify(networkMapStorage, never()).saveNewActiveNetworkMap(any()) + verify(networkMapStorage, never()).saveNewNetworkMap(any(), any()) verify(networkMapStorage, never()).saveNetworkParameters(any(), any()) } @@ -121,21 +123,21 @@ class NetworkMapSignerTest : TestBase() { // given val netParams = testNetworkParameters() whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntityUnsigned(netParams)) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(NodeInfoHashes(emptyList(), emptyMap())) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(NetworkMaps(null, emptyMap())) whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(null) // when - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() // then // Verify networkMapStorage calls - verify(networkMapStorage).getActiveNodeInfoHashes() - verify(networkMapStorage).getActiveNetworkMap() + verify(networkMapStorage).getNodeInfoHashes() + verify(networkMapStorage).getNetworkMaps() verify(networkMapStorage).getLatestNetworkParameters() verify(networkMapStorage).getCurrentParametersUpdate() argumentCaptor().apply { - verify(networkMapStorage).saveNewActiveNetworkMap(capture()) + verify(networkMapStorage).saveNewNetworkMap(anyOrNull(), capture()) assertEquals(netParams.serialize().hash, firstValue.networkMap.networkParameterHash) } val paramsCaptor = argumentCaptor() @@ -150,20 +152,20 @@ class NetworkMapSignerTest : TestBase() { // given val currentNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair) val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2)) - val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue())) - val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, currentNetworkParameters, emptyList(), null) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity) + val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters, "Update time", Instant.ofEpochMilli(random63BitValue())) + val netMapEntity = createNetworkMaps(signingCertAndKeyPair, currentNetworkParameters) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(netMapEntity) whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(parametersUpdate) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(updateNetworkParameters) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(NodeInfoHashes(emptyList(), emptyMap())) // when - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() // then // Verify networkMapStorage calls - verify(networkMapStorage).getActiveNetworkMap() - verify(networkMapStorage).getActiveNodeInfoHashes() + verify(networkMapStorage).getNetworkMaps() + verify(networkMapStorage).getNodeInfoHashes() verify(networkMapStorage).getLatestNetworkParameters() verify(networkMapStorage).getCurrentParametersUpdate() @@ -177,34 +179,34 @@ class NetworkMapSignerTest : TestBase() { @Test fun `signNetworkMap fails if there is parameter update without relevant parameters stored`() { val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2)) - val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue())) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) + val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters, "Update time", Instant.ofEpochMilli(random63BitValue())) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(NetworkMaps(null, emptyMap())) whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(parametersUpdate) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(NodeInfoHashes(emptyList(), emptyMap())) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity()) verify(networkMapStorage, never()).saveNetworkParameters(any(), any()) - verify(networkMapStorage, never()).saveNewActiveNetworkMap(any()) + verify(networkMapStorage, never()).saveNewNetworkMap(any(), any()) } @Test fun `setting flag day on parameters update changes parameters inside network map`() { val activeNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair, testNetworkParameters(epoch = 1)) val updateNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair, testNetworkParameters(epoch = 2)) - val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue())) - val activeNetworkMap = createNetworkMapEntity(signingCertAndKeyPair, activeNetworkParameters, emptyList(), parametersUpdate.toParametersUpdate()) + val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters, "Update time", Instant.ofEpochMilli(random63BitValue())) + val activeNetworkMaps = createNetworkMaps(signingCertAndKeyPair, activeNetworkParameters, parametersUpdate = parametersUpdate.toParametersUpdate()) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(activeNetworkMap) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(activeNetworkMaps) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(NodeInfoHashes(emptyList(), emptyMap())) whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(parametersUpdate.copy(status = UpdateStatus.FLAG_DAY)) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(updateNetworkParameters) // when - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() //then argumentCaptor().apply { - verify(networkMapStorage).saveNewActiveNetworkMap(capture()) + verify(networkMapStorage).saveNewNetworkMap(anyOrNull(), capture()) val netMap = firstValue.networkMap assertEquals(SecureHash.parse(updateNetworkParameters.hash), netMap.networkParameterHash) assertEquals(emptyList(), netMap.nodeInfoHashes) @@ -216,20 +218,20 @@ class NetworkMapSignerTest : TestBase() { fun `cancel update test`() { val activeNetworkParameters = createNetworkParametersEntity(signingCertAndKeyPair, testNetworkParameters(epoch = 1)) val updateNetworkParameters = createNetworkParametersEntityUnsigned(testNetworkParameters(epoch = 2)) - val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters,"Update time", Instant.ofEpochMilli(random63BitValue())) - val activeNetworkMap = createNetworkMapEntity(signingCertAndKeyPair, activeNetworkParameters, emptyList(), parametersUpdate.toParametersUpdate()) + val parametersUpdate = ParametersUpdateEntity(0, updateNetworkParameters, "Update time", Instant.ofEpochMilli(random63BitValue())) + val activeNetworkMaps = createNetworkMaps(signingCertAndKeyPair, activeNetworkParameters, parametersUpdate = parametersUpdate.toParametersUpdate()) - whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(activeNetworkMap) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) + whenever(networkMapStorage.getNetworkMaps()).thenReturn(activeNetworkMaps) + whenever(networkMapStorage.getNodeInfoHashes()).thenReturn(NodeInfoHashes(emptyList(), emptyMap())) whenever(networkMapStorage.getCurrentParametersUpdate()).thenReturn(null) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity()) // when - networkMapSigner.signNetworkMap() + networkMapSigner.signNetworkMaps() //then argumentCaptor().apply { - verify(networkMapStorage).saveNewActiveNetworkMap(capture()) + verify(networkMapStorage).saveNewNetworkMap(anyOrNull(), capture()) val netMap = firstValue.networkMap assertEquals(SecureHash.parse(activeNetworkParameters.hash), netMap.networkParameterHash) assertEquals(emptyList(), netMap.nodeInfoHashes) diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt index 9be8ea2c25..1346e1a6e3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt @@ -14,13 +14,19 @@ import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage -import com.r3.corda.networkmanage.createNetworkMapEntity +import com.r3.corda.networkmanage.createNetworkMaps import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer import com.r3.corda.networkmanage.doorman.NetworkMapConfig -import net.corda.core.crypto.* +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash.Companion.randomSHA256 +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.* +import net.corda.core.internal.openHttpConnection +import net.corda.core.internal.post +import net.corda.core.internal.responseAs +import net.corda.core.internal.sign import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.OpaqueBytes @@ -42,6 +48,16 @@ import org.junit.Test import java.io.IOException import java.net.URL import java.security.cert.X509Certificate +import java.time.Duration +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.first +import kotlin.collections.forEach +import kotlin.collections.listOf +import kotlin.collections.mapOf import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -67,7 +83,7 @@ class NetworkMapWebServiceTest { // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) val networkMapStorage: NetworkMapStorage = mock { - on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity()) + on { getNetworkMaps() }.thenReturn(createNetworkMaps()) } val csrStorage: CertificateSigningRequestStorage = mock { on { getValidCertificatePath(any()) }.thenReturn(signedNodeInfo.verified().legalIdentitiesAndCerts.first().certPath) @@ -85,7 +101,7 @@ class NetworkMapWebServiceTest { // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) val networkMapStorage: NetworkMapStorage = mock { - on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity()) + on { getNetworkMaps() }.thenReturn(createNetworkMaps()) } val csrStorage: CertificateSigningRequestStorage = mock { on { getValidCertificatePath(any()) }.thenReturn(null) @@ -105,7 +121,7 @@ class NetworkMapWebServiceTest { // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1) val networkMapStorage: NetworkMapStorage = mock { - on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity(networkParameters = testNetworkParameters(minimumPlatformVersion = 2))) + on { getNetworkMaps() }.thenReturn(createNetworkMaps(networkParameters = testNetworkParameters(minimumPlatformVersion = 2))) } val csrStorage: CertificateSigningRequestStorage = mock { on { getValidCertificatePath(any()) }.thenReturn(signedNodeInfo.verified().legalIdentitiesAndCerts.first().certPath) @@ -123,7 +139,7 @@ class NetworkMapWebServiceTest { // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1) val networkMapStorage: NetworkMapStorage = mock { - on { getActiveNetworkMap() }.thenReturn(null) + on { getNetworkMaps() }.thenReturn(null) } val csrStorage: CertificateSigningRequestStorage = mock { on { getValidCertificatePath(any()) }.thenReturn(signedNodeInfo.verified().legalIdentitiesAndCerts.first().certPath) @@ -138,19 +154,69 @@ class NetworkMapWebServiceTest { @Test fun `get network map`() { - val networkMapEntity = createNetworkMapEntity( + val networkMaps = createNetworkMaps( signingCertAndKeyPair = signingCertAndKeyPair, nodeInfoHashes = listOf(randomSHA256(), randomSHA256())) val networkMapStorage: NetworkMapStorage = mock { - on { getActiveNetworkMap() }.thenReturn(networkMapEntity) + on { getNetworkMaps() }.thenReturn(networkMaps) } NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, mock(), testNetworkMapConfig)).use { it.start() val signedNetworkMapResponse = it.doGet("") - verify(networkMapStorage, times(1)).getActiveNetworkMap() - assertEquals(signedNetworkMapResponse.verifiedNetworkMapCert(rootCaCert), networkMapEntity.networkMap) + verify(networkMapStorage, times(1)).getNetworkMaps() + assertEquals(signedNetworkMapResponse.verifiedNetworkMapCert(rootCaCert), networkMaps.publicNetworkMap!!.networkMap) + } + } + + @Test + fun `network map response contains correct http header parameter`() { + val timestamp = Instant.now() + val networkMaps = createNetworkMaps( + signingCertAndKeyPair = signingCertAndKeyPair, + nodeInfoHashes = listOf(randomSHA256(), randomSHA256()), + timestamp = timestamp) + + val networkMapStorage: NetworkMapStorage = mock { + on { getNetworkMaps() }.thenReturn(networkMaps) + } + + NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, mock(), testNetworkMapConfig)).use { + it.start() + val headers = URL("http://${it.hostAndPort}/network-map/").openHttpConnection().headerFields + assertEquals("max-age=${Duration.ofMillis(testNetworkMapConfig.cacheTimeout).seconds}", headers["Cache-Control"]?.first()) + assertEquals(DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("GMT")).format(timestamp), headers["Last-Modified"]?.first()) + } + } + + @Test + fun `get private network map`() { + val publicNodeInfoHashes = listOf(randomSHA256(), randomSHA256()) + val privateNodeInfoHashes = mapOf("PrivateNet1" to listOf(randomSHA256(), randomSHA256()), "PrivateNet2" to listOf(randomSHA256(), randomSHA256())) + + val networkMaps = createNetworkMaps( + signingCertAndKeyPair = signingCertAndKeyPair, + nodeInfoHashes = publicNodeInfoHashes, + privateNodeInfoHashes = privateNodeInfoHashes + ) + + val networkMapStorage: NetworkMapStorage = mock { + on { getNetworkMaps() }.thenReturn(networkMaps) + } + + NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, mock(), testNetworkMapConfig)).use { + it.start() + val signedNetworkMapResponse = it.doGet("") + verify(networkMapStorage, times(1)).getNetworkMaps() + assertEquals(signedNetworkMapResponse.verifiedNetworkMapCert(rootCaCert), networkMaps.publicNetworkMap!!.networkMap) + + networkMaps.privateNetworkMap.forEach { (key, privateNetworkMapEntity) -> + val response = it.doGet(key) + // Result cached. + verify(networkMapStorage, times(1)).getNetworkMaps() + assertEquals(response.verifiedNetworkMapCert(rootCaCert), privateNetworkMapEntity.networkMap) + } } } @@ -165,7 +231,7 @@ class NetworkMapWebServiceTest { // Mock network map storage val networkMapStorage: NetworkMapStorage = mock { - on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity(nodeInfoHashes = listOf(nodeInfoHash))) + on { getNetworkMaps() }.thenReturn(createNetworkMaps(nodeInfoHashes = listOf(nodeInfoHash))) } NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, mock(), testNetworkMapConfig)).use {