From 2832eb489bac5f22769c86ff5ca58efda89986ba Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 8 Jan 2018 21:45:39 +0000 Subject: [PATCH] Replaced DoormanIntegrationTest with NodeRegistrationTest which does a simple end-to-end registration test using the node driver. (#279) --- .../doorman/DoormanIntegrationTest.kt | 203 ------------------ .../doorman/NodeRegistrationTest.kt | 166 ++++++++++++++ .../PersistentNetworkMapStorage.kt | 2 + 3 files changed, 168 insertions(+), 203 deletions(-) delete mode 100644 network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt create mode 100644 network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt deleted file mode 100644 index 207a524aba..0000000000 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt +++ /dev/null @@ -1,203 +0,0 @@ -package com.r3.corda.networkmanage.doorman - -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever -import com.r3.corda.networkmanage.common.persistence.configureDatabase -import com.r3.corda.networkmanage.doorman.signer.LocalSigner -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sign -import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.node.NodeInfo -import net.corda.core.serialization.serialize -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.minutes -import net.corda.core.utilities.seconds -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.network.NetworkMapClient -import net.corda.node.utilities.registration.HTTPNetworkRegistrationService -import net.corda.node.utilities.registration.NetworkRegistrationHelper -import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.crypto.* -import net.corda.testing.ALICE_NAME -import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.internal.rigorousMock -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.net.URL -import java.security.cert.X509Certificate -import java.util.* -import javax.security.auth.x500.X500Principal -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class DoormanIntegrationTest { - @Rule - @JvmField - val tempFolder = TemporaryFolder() - - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule(true) - - @Test - fun `initial registration`() { - val rootCertAndKey = createDoormanRootCertificateAndKeyPair() - val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey) - - //Start doorman server - val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate) - val doormanHostAndPort = doorman.hostAndPort - // Start Corda network registration. - val config = createConfig().also { - doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL - } - config.trustStoreFile.parent.createDirectories() - loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { - it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCertAndKey.certificate) - it.save(config.trustStoreFile, config.trustStorePassword) - } - - NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() - - // Checks the keystore are created with the right certificates and keys. - assert(config.nodeKeystore.toFile().exists()) - assert(config.sslKeystore.toFile().exists()) - assert(config.trustStoreFile.toFile().exists()) - - val intermediateCACert = intermediateCertAndKey.certificate - val rootCACert = rootCertAndKey.certificate - - loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply { - assert(containsAlias(X509Utilities.CORDA_CLIENT_CA)) - assertEquals(ALICE_NAME.x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal) - assertEquals(listOf(intermediateCACert, rootCACert), getCertificateChain(X509Utilities.CORDA_CLIENT_CA).drop(1).toList()) - } - - loadKeyStore(config.sslKeystore, config.keyStorePassword).apply { - assert(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - assertEquals(ALICE_NAME.x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal) - assertEquals(listOf(intermediateCACert, rootCACert), getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).drop(2).toList()) - } - - loadKeyStore(config.trustStoreFile, config.trustStorePassword).apply { - assert(containsAlias(X509Utilities.CORDA_ROOT_CA)) - assertEquals(rootCACert.subjectX500Principal, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subjectX500Principal) - } - - doorman.close() - } - - @Test - fun `nodeInfo is published to the network map`() { - // Given - val rootCertAndKey = createDoormanRootCertificateAndKeyPair() - val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey) - - //Start doorman server - val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate) - val doormanHostAndPort = doorman.hostAndPort - - // Start Corda network registration. - val config = createConfig().also { - doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL - } - config.trustStoreFile.parent.createDirectories() - loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also { - it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCertAndKey.certificate) - it.save(config.trustStoreFile, config.trustStorePassword) - } - - NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() - - // Publish NodeInfo - val networkMapClient = NetworkMapClient(config.compatibilityZoneURL!!, rootCertAndKey.certificate) - - val keyStore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) - val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) - val identityKeyPair = Crypto.generateKeyPair() - val identityCert = X509Utilities.createCertificate( - CertificateType.LEGAL_IDENTITY, - clientCA.certificate, - clientCA.keyPair, - ALICE_NAME.x500Principal, - identityKeyPair.public) - val certPath = X509CertificateFactory().generateCertPath(identityCert, *clientCertPath) - val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) - val nodeInfoBytes = nodeInfo.serialize() - - // When - val signedNodeInfo = SignedNodeInfo(nodeInfoBytes, listOf(identityKeyPair.private.sign(nodeInfoBytes.bytes))) - networkMapClient.publish(signedNodeInfo) - - // Then - val networkMapNodeInfo = networkMapClient.getNodeInfo(nodeInfoBytes.hash) - assertNotNull(networkMapNodeInfo) - assertEquals(nodeInfo, networkMapNodeInfo) - - doorman.close() - } - - private fun createConfig(): NodeConfiguration { - return rigorousMock().also { - doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory - doReturn(ALICE_NAME).whenever(it).myLegalName - doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory - doReturn(it.certificatesDirectory / "truststore.jks").whenever(it).trustStoreFile - doReturn(it.certificatesDirectory / "nodekeystore.jks").whenever(it).nodeKeystore - doReturn(it.certificatesDirectory / "sslkeystore.jks").whenever(it).sslKeystore - doReturn("trustpass").whenever(it).trustStorePassword - doReturn("cordacadevpass").whenever(it).keyStorePassword - doReturn("iTest@R3.com").whenever(it).emailAddress - } - } -} - - -fun createDoormanIntermediateCertificateAndKeyPair(rootCa: CertificateAndKeyPair): CertificateAndKeyPair { - val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val intermediateCACert = X509Utilities.createCertificate( - CertificateType.INTERMEDIATE_CA, - rootCa.certificate, - rootCa.keyPair, - X500Principal("CN=Integration Test Corda Node Intermediate CA,O=R3 Ltd,L=London,C=GB"), - keyPair.public) - return CertificateAndKeyPair(intermediateCACert, keyPair) -} - -fun createDoormanRootCertificateAndKeyPair(): CertificateAndKeyPair { - val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCaCert = X509Utilities.createSelfSignedCACertificate( - X500Principal("CN=Integration Test Corda Node Root CA,O=R3 Ltd,L=London,C=GB"), - keyPair) - return CertificateAndKeyPair(rootCaCert, keyPair) -} - -fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties { - val props = Properties() - props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") - props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") - props.setProperty("dataSource.user", "sa") - props.setProperty("dataSource.password", "") - return props -} - -fun startDoorman(intermediateCACertAndKey: CertificateAndKeyPair, rootCACert: X509Certificate): NetworkManagementServer { - val signer = LocalSigner(intermediateCACertAndKey.keyPair, arrayOf(intermediateCACertAndKey.certificate, rootCACert)) - //Start doorman server - return startDoorman(signer) -} - -fun startDoorman(localSigner: LocalSigner? = null): NetworkManagementServer { - val database = configureDatabase(makeTestDataSourceProperties()) - //Start doorman server - val server = NetworkManagementServer() - server.start(NetworkHostAndPort("localhost", 0), database, localSigner, testNetworkParameters(emptyList()), NetworkMapConfig(1.minutes.toMillis(), 1.minutes.toMillis()), DoormanConfig(true, null, 3.seconds.toMillis())) - - return server -} \ No newline at end of file diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt new file mode 100644 index 0000000000..061e7af82a --- /dev/null +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt @@ -0,0 +1,166 @@ +package com.r3.corda.networkmanage.doorman + +import com.r3.corda.networkmanage.common.persistence.configureDatabase +import com.r3.corda.networkmanage.doorman.signer.LocalSigner +import net.corda.cordform.CordformNode +import net.corda.core.crypto.random63BitValue +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.concurrent.transpose +import net.corda.core.internal.div +import net.corda.core.internal.exists +import net.corda.core.internal.list +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueAndPaymentFlow +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.driver.NodeHandle +import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.IntegrationTest +import net.corda.testing.internal.IntegrationTestSchemas +import net.corda.testing.internal.createDevIntermediateCaCertPath +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.CompatibilityZoneParams +import net.corda.testing.node.internal.internalDriver +import net.corda.testing.singleIdentity +import org.assertj.core.api.Assertions.assertThat +import org.junit.* +import java.net.URL +import java.security.cert.X509Certificate +import java.util.* +import kotlin.streams.toList + +// This is the same test as the one in net.corda.node.utilities.registration but using the real doorman and with some +// extra checks on the network map. +class NodeRegistrationTest : IntegrationTest() { + companion object { + private val notaryName = CordaX500Name("NotaryService", "Zurich", "CH") + private val aliceName = CordaX500Name("Alice", "London", "GB") + private val genevieveName = CordaX500Name("Genevieve", "London", "GB") + + @ClassRule + @JvmField + val databaseSchemas = IntegrationTestSchemas(notaryName.organisation, aliceName.organisation, genevieveName.organisation) + + private val timeoutMillis = 5.seconds.toMillis() + } + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule(true) + + private val portAllocation = PortAllocation.Incremental(10000) + private val serverAddress = portAllocation.nextHostAndPort() + private val dbId = random63BitValue().toString() + + private lateinit var rootCaCert: X509Certificate + private lateinit var intermediateCa: CertificateAndKeyPair + + private var server: NetworkManagementServer? = null + + @Before + fun init() { + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() + rootCaCert = rootCa.certificate + this.intermediateCa = intermediateCa + } + + @After + fun cleanUp() { + server?.close() + } + + @Test + fun `register nodes with doorman and then they transact with each other`() { + // Start the server without the network parameters since we don't have them yet + server = startNetworkManagementServer(networkParameters = null) + val compatibilityZone = CompatibilityZoneParams( + URL("http://$serverAddress"), + publishNotaries = { notaryInfos -> + // Restart the server once we're able to generate the network parameters + server!!.close() + server = startNetworkManagementServer(testNetworkParameters(notaryInfos)) + // Once restarted we delay starting the nodes to make sure the network map server has processed the + // network parameters, otherwise the nodes will fail to start as the network parameters won't be + // available for them to download. + Thread.sleep(2 * timeoutMillis) + }, + rootCert = rootCaCert + ) + + internalDriver( + portAllocation = portAllocation, + compatibilityZone = compatibilityZone, + initialiseSerialization = false, + notarySpecs = listOf(NotarySpec(notaryName)), + extraCordappPackagesToScan = listOf("net.corda.finance") + ) { + val (alice, notary) = listOf( + startNode(providedName = aliceName), + defaultNotaryNode + ).transpose().getOrThrow() + + alice.onlySeesFromNetworkMap(alice, notary) + notary.onlySeesFromNetworkMap(alice, notary) + + val genevieve = startNode(providedName = genevieveName).getOrThrow() + + // Wait for the nodes to poll again + Thread.sleep(timeoutMillis * 2) + + // Make sure the new node is visible to everyone + alice.onlySeesFromNetworkMap(alice, genevieve, notary) + notary.onlySeesFromNetworkMap(alice, genevieve, notary) + genevieve.onlySeesFromNetworkMap(alice, genevieve, notary) + + // Check the nodes can communicate among themselves (and the notary). + val anonymous = false + genevieve.rpc.startFlow( + ::CashIssueAndPaymentFlow, + 1000.DOLLARS, + OpaqueBytes.of(12), + alice.nodeInfo.singleIdentity(), + anonymous, + defaultNotaryIdentity + ).returnValue.getOrThrow() + } + } + + + private fun startNetworkManagementServer(networkParameters: NetworkParameters?): NetworkManagementServer { + return NetworkManagementServer().apply { + start( + serverAddress, + configureDatabase(makeTestDataSourceProperties()), + LocalSigner(intermediateCa.keyPair, arrayOf(intermediateCa.certificate, rootCaCert)), + networkParameters, + networkParameters?.let { NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis) }, + DoormanConfig(approveAll = true, jiraConfig = null, approveInterval = timeoutMillis) + ) + } + } + + private fun NodeHandle.onlySeesFromNetworkMap(vararg nodes: NodeHandle) { + // Make sure the nodes aren't getting the node infos from their additional directories + val nodeInfosDir = configuration.baseDirectory / CordformNode.NODE_INFO_DIRECTORY + if (nodeInfosDir.exists()) { + assertThat(nodeInfosDir.list { it.toList() }).isEmpty() + } + assertThat(rpc.networkMapSnapshot()).containsOnlyElementsOf(nodes.map { it.nodeInfo }) + } + + // TODO Use the other dbs in the integration tests + private fun makeTestDataSourceProperties(): Properties { + val props = Properties() + props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") + props.setProperty("dataSource.url", "jdbc:h2:mem:${dbId}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") + props.setProperty("dataSource.user", "sa") + props.setProperty("dataSource.password", "") + return props + } +} 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 331170a8b1..314d2b9f83 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 @@ -88,6 +88,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence, privat return database.transaction { val builder = session.criteriaBuilder val query = builder.createQuery(NetworkParametersEntity::class.java).run { + // TODO a limit of 1 since we only need the first result from(NetworkParametersEntity::class.java).run { orderBy(builder.desc(get(NetworkParametersEntity::version.name))) } @@ -102,6 +103,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence, privat val builder = session.criteriaBuilder val query = builder.createQuery(NetworkMapEntity::class.java).run { from(NetworkMapEntity::class.java).run { + // TODO a limit of 1 since we only need the first result where(builder.isNotNull(get(NetworkMapEntity::signature.name))) orderBy(builder.desc(get(NetworkMapEntity::version.name))) }