Internal driver now also does the registration for the notaries. (#2304)

Using the --just-generate-node-info flag for the notary nodes so that their identities can be submitted to the network map server, which does the network parameters generation.
This commit is contained in:
Shams Asari
2018-01-02 15:12:30 +00:00
committed by GitHub
parent fe3c2b3983
commit 730fec2eb4
14 changed files with 370 additions and 261 deletions

View File

@ -1,6 +1,11 @@
package net.corda.node.services.network
import net.corda.cordform.CordformNode
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.random63BitValue
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.internal.readAll
import net.corda.core.node.NodeInfo
@ -10,9 +15,9 @@ import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.testing.ALICE_NAME
import net.corda.testing.ROOT_CA
import net.corda.testing.BOB_NAME
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.node.internal.CompatibilityZoneParams
@ -24,12 +29,15 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.net.URL
import java.time.Instant
import kotlin.streams.toList
import kotlin.test.assertEquals
class NetworkMapTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val cacheTimeout = 1.seconds
private val portAllocation = PortAllocation.Incremental(10000)
@ -40,7 +48,9 @@ class NetworkMapTest {
fun start() {
networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort())
val address = networkMapServer.start()
compatibilityZone = CompatibilityZoneParams(URL("http://$address"))
compatibilityZone = CompatibilityZoneParams(URL("http://$address"), publishNotaries = {
networkMapServer.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()))
})
}
@After
@ -50,27 +60,35 @@ class NetworkMapTest {
@Test
fun `node correctly downloads and saves network parameters file on startup`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone,
initialiseSerialization = false, notarySpecs = emptyList()) {
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = emptyList()
) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val networkParameters = alice.configuration.baseDirectory
.list { paths -> paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().get() }
val networkParameters = (alice.configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
.readAll()
.deserialize<SignedData<NetworkParameters>>()
.verified()
// We use a random modified time above to make the network parameters unqiue so that we're sure they came
// from the server
assertEquals(networkMapServer.networkParameters, networkParameters)
}
}
@Test
fun `nodes can see each other using the http network map`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone,
initialiseSerialization = false, onNetworkParametersGeneration = { networkMapServer.networkParameters = it }) {
val alice = startNode(providedName = ALICE_NAME)
val bob = startNode(providedName = BOB_NAME)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
val bobNode = bob.get()
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false
) {
val (aliceNode, bobNode, notaryNode) = listOf(
startNode(providedName = ALICE_NAME),
startNode(providedName = BOB_NAME),
defaultNotaryNode
).transpose().getOrThrow()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
@ -80,16 +98,20 @@ class NetworkMapTest {
@Test
fun `nodes process network map add updates correctly when adding new node to network map`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone,
initialiseSerialization = false, onNetworkParametersGeneration = { networkMapServer.networkParameters = it }) {
val alice = startNode(providedName = ALICE_NAME)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false
) {
val (aliceNode, notaryNode) = listOf(
startNode(providedName = ALICE_NAME),
defaultNotaryNode
).transpose().getOrThrow()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
val bob = startNode(providedName = BOB_NAME)
val bobNode = bob.get()
val bobNode = startNode(providedName = BOB_NAME).getOrThrow()
// Wait for network map client to poll for the next update.
Thread.sleep(cacheTimeout.toMillis() * 2)
@ -102,13 +124,16 @@ class NetworkMapTest {
@Test
fun `nodes process network map remove updates correctly`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone,
initialiseSerialization = false, onNetworkParametersGeneration = { networkMapServer.networkParameters = it }) {
val alice = startNode(providedName = ALICE_NAME)
val bob = startNode(providedName = BOB_NAME)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
val bobNode = bob.get()
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false
) {
val (aliceNode, bobNode, notaryNode) = listOf(
startNode(providedName = ALICE_NAME),
startNode(providedName = BOB_NAME),
defaultNotaryNode
).transpose().getOrThrow()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
@ -124,5 +149,12 @@ class NetworkMapTest {
}
}
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) = assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) {
// 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()).containsOnly(*nodes)
}
}

View File

@ -21,6 +21,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.testing.ROOT_CA
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.PortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CompatibilityZoneParams
@ -31,10 +32,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.*
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URL
@ -49,6 +47,12 @@ import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
class NodeRegistrationTest {
companion object {
private val notaryName = CordaX500Name("NotaryService", "Zurich", "CH")
private val aliceName = CordaX500Name("Alice", "London", "GB")
private val genevieveName = CordaX500Name("Genevieve", "London", "GB")
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
@ -72,37 +76,30 @@ class NodeRegistrationTest {
@Test
fun `node registration correct root cert`() {
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = ROOT_CA.certificate.cert)
val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverHostAndPort"),
publishNotaries = { server.networkParameters = testNetworkParameters(it) },
rootCert = ROOT_CA.certificate.cert)
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(CordaX500Name("NotaryService", "Zurich", "CH"), validating = false)),
extraCordappPackagesToScan = listOf("net.corda.finance"),
onNetworkParametersGeneration = { server.networkParameters = it }
notarySpecs = listOf(NotarySpec(notaryName)),
extraCordappPackagesToScan = listOf("net.corda.finance")
) {
val aliceName = "Alice"
val genevieveName = "Genevieve"
val nodes = listOf(
startNode(providedName = CordaX500Name(aliceName, "London", "GB")),
startNode(providedName = CordaX500Name(genevieveName, "London", "GB")),
startNode(providedName = aliceName),
startNode(providedName = genevieveName),
defaultNotaryNode
).transpose().getOrThrow()
val (alice, genevieve) = nodes
assertThat(registrationHandler.idsPolled).contains(aliceName, genevieveName)
// Notary identities are generated beforehand hence notary nodes don't go through registration.
// This test isn't specifically testing this, or relying on this behavior, though if this check fail,
// this will probably lead to the rest of the test to fail.
assertThat(registrationHandler.idsPolled).doesNotContain("NotaryService")
assertThat(registrationHandler.idsPolled).containsOnly(
aliceName.organisation,
genevieveName.organisation,
notaryName.organisation)
// Check each node has each other identity in their network map cache.
for (node in nodes) {
assertThat(node.rpc.networkMapSnapshot()).containsOnlyElementsOf(nodes.map { it.nodeInfo })
}
// Check we nodes communicate among themselves (and the notary).
// Check the nodes can communicate among themselves (and the notary).
val anonymous = false
genevieve.rpc.startFlow(
::CashIssueAndPaymentFlow,
@ -120,20 +117,22 @@ class NodeRegistrationTest {
val someRootCert = X509Utilities.createSelfSignedCACertificate(
CordaX500Name("Integration Test Corda Node Root CA", "R3 Ltd", "London", "GB"),
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someRootCert.cert)
val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverHostAndPort"),
publishNotaries = { server.networkParameters = testNetworkParameters(it) },
rootCert = someRootCert.cert)
internalDriver(
portAllocation = portAllocation,
notarySpecs = emptyList(),
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(notaryName)),
startNodesInProcess = true // We need to run the nodes in the same process so that we can capture the correct exception
) {
assertThatThrownBy {
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
defaultNotaryNode.getOrThrow()
}.isInstanceOf(CertPathValidatorException::class.java)
}
}
}
@Path("certificate")
@ -150,6 +149,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair)
certificationRequest,
rootCertAndKeyPair.keyPair,
arrayOf(rootCertAndKeyPair.certificate.cert))
require(!name.organisation.contains("\\s".toRegex())) { "Whitespace in the organisation name not supported" }
certPaths[name.organisation] = certPath
return Response.ok(name.organisation).build()
}

View File

@ -180,13 +180,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return SignedNodeInfo(serialised, listOf(signature))
}
open fun generateNodeInfo() {
open fun generateAndSaveNodeInfo(): NodeInfo {
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
initCertificate()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like
// a code smell.
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
@ -196,6 +196,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
privateKey.sign(serialised.bytes)
}
NodeInfoWatcher.saveToFile(configuration.baseDirectory, signedNodeInfo)
info
}
}

View File

@ -19,7 +19,9 @@ import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.api.SchemaService
import net.corda.node.services.config.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.VerifierType
import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AddressUtils
@ -32,6 +34,7 @@ import net.corda.nodeapi.internal.serialization.*
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import rx.Scheduler
import rx.schedulers.Schedulers
import java.time.Clock
import java.util.concurrent.atomic.AtomicInteger
@ -264,15 +267,13 @@ open class Node(configuration: NodeConfiguration,
private val _startupComplete = openFuture<Unit>()
val startupComplete: CordaFuture<Unit> get() = _startupComplete
override fun generateNodeInfo() {
override fun generateAndSaveNodeInfo(): NodeInfo {
initialiseSerialization()
super.generateNodeInfo()
return super.generateAndSaveNodeInfo()
}
override fun start(): StartedNode<Node> {
if (initialiseSerialization) {
initialiseSerialization()
}
initialiseSerialization()
val started: StartedNode<Node> = uncheckedCast(super.start())
nodeReadyFuture.thenMatch({
serverThread.execute {
@ -305,8 +306,10 @@ open class Node(configuration: NodeConfiguration,
return started
}
override fun getRxIoScheduler() = Schedulers.io()!!
override fun getRxIoScheduler(): Scheduler = Schedulers.io()
private fun initialiseSerialization() {
if (!initialiseSerialization) return
val classloader = cordappLoader.appClassLoader
nodeSerializationEnv = SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {

View File

@ -1,7 +1,6 @@
package net.corda.node.internal
import com.jcabi.manifests.Manifests
import com.typesafe.config.ConfigException
import joptsimple.OptionException
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.thenMatch
@ -126,7 +125,7 @@ open class NodeStartup(val args: Array<String>) {
val node = createNode(conf, versionInfo)
if (cmdlineOptions.justGenerateNodeInfo) {
// Perform the minimum required start-up logic to be able to write a nodeInfo to disk
node.generateNodeInfo()
node.generateAndSaveNodeInfo()
return
}
val startedNode = node.start()

View File

@ -5,7 +5,10 @@ import net.corda.core.crypto.sha256
import net.corda.core.internal.cert
import net.corda.core.serialization.serialize
import net.corda.core.utilities.seconds
import net.corda.testing.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.DEV_TRUST_ROOT
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.createNodeInfoAndSigned
import net.corda.testing.node.internal.network.NetworkMapServer
@ -30,7 +33,7 @@ class NetworkMapClientTest {
@Before
fun setUp() {
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort(), root_ca = ROOT_CA)
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
val hostAndPort = server.start()
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert)
}