mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
Replaced DoormanIntegrationTest with NodeRegistrationTest which does a simple end-to-end registration test using the node driver. (#279)
This commit is contained in:
parent
bbcdb639ab
commit
2832eb489b
@ -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<NodeConfiguration>().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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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<String>(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<ByteArray?>(NetworkMapEntity::signature.name)))
|
||||
orderBy(builder.desc(get<String>(NetworkMapEntity::version.name)))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user