CORDA 2131 - Extend Network Bootstrapper to enable registration of Java Package Namespaces. (#4116)

* Package Ownership Network Parameters: add register / unregister CLI options to network bootstrapper.

* Fix 2 failing unit tests.

* Fix failing unit tests.

* Added changelog, documentation and cosmetic changes.

* Fixed exception message.

* Address PR review feedback.

* Fix typo.

* Resolve conflicts.

* Rebase, resolve conflicts and remove PackageOwner class.

* Address latest PR review feedback.

* Fix incorrect imports.

* Fix broken JUnit

* Add support for key store passwords including delimiter characters.

* Updated and improved documentation.

* Minor doc update.

* Documentation changes following PR review feedback

* Replace Bank Of Corda with Example CorDapp.
Remove references to locally built network bootstrapper.
This commit is contained in:
josecoll
2018-11-06 09:28:55 +00:00
committed by GitHub
parent e52f4bc2a7
commit 015a36dad6
12 changed files with 463 additions and 49 deletions

View File

@ -2,11 +2,13 @@ package net.corda.nodeapi.internal.network
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
import net.corda.core.node.JavaPackageName
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
@ -17,6 +19,7 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.days
import net.corda.core.utilities.filterNotNullValues
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.*
@ -30,6 +33,7 @@ import net.corda.serialization.internal.amqp.amqpMagic
import java.io.InputStream
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.security.PublicKey
import java.time.Instant
import java.util.*
import java.util.concurrent.Executors
@ -168,14 +172,14 @@ internal constructor(private val initSerEnv: Boolean,
}
/** Entry point for the tool */
fun bootstrap(directory: Path, copyCordapps: Boolean, minimumPlatformVersion: Int) {
fun bootstrap(directory: Path, copyCordapps: Boolean, minimumPlatformVersion: Int, packageOwnership : Map<JavaPackageName, PublicKey?> = emptyMap()) {
require(minimumPlatformVersion <= PLATFORM_VERSION) { "Minimum platform version cannot be greater than $PLATFORM_VERSION" }
// Don't accidently include the bootstrapper jar as a CorDapp!
val bootstrapperJar = javaClass.location.toPath()
val cordappJars = directory.list { paths ->
paths.filter { it.toString().endsWith(".jar") && !it.isSameAs(bootstrapperJar) && it.fileName.toString() != "corda.jar" }.toList()
}
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false, minimumPlatformVersion = minimumPlatformVersion)
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false, minimumPlatformVersion = minimumPlatformVersion, packageOwnership = packageOwnership)
}
private fun bootstrap(
@ -183,7 +187,8 @@ internal constructor(private val initSerEnv: Boolean,
cordappJars: List<Path>,
copyCordapps: Boolean,
fromCordform: Boolean,
minimumPlatformVersion: Int = PLATFORM_VERSION
minimumPlatformVersion: Int = PLATFORM_VERSION,
packageOwnership : Map<JavaPackageName, PublicKey?> = emptyMap()
) {
directory.createDirectories()
println("Bootstrapping local test network in $directory")
@ -223,7 +228,7 @@ internal constructor(private val initSerEnv: Boolean,
val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs)
println("Generating contract implementations whitelist")
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.filter { !isSigned(it) }.map(contractsJarConverter))
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, minimumPlatformVersion)
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, minimumPlatformVersion, packageOwnership)
if (newNetParams != existingNetParams) {
println("${if (existingNetParams == null) "New" else "Updated"} $newNetParams")
} else {
@ -355,17 +360,31 @@ internal constructor(private val initSerEnv: Boolean,
whitelist: Map<String, List<AttachmentId>>,
existingNetParams: NetworkParameters?,
nodeDirs: List<Path>,
minimumPlatformVersion: Int
minimumPlatformVersion: Int,
packageOwnership : Map<JavaPackageName, PublicKey?>
): NetworkParameters {
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
// TODO Add config for maxMessageSize and maxTransactionSize
val netParams = if (existingNetParams != null) {
if (existingNetParams.whitelistedContractImplementations == whitelist && existingNetParams.notaries == notaryInfos) {
if (existingNetParams.whitelistedContractImplementations == whitelist && existingNetParams.notaries == notaryInfos &&
existingNetParams.packageOwnership.entries.containsAll(packageOwnership.entries)) {
existingNetParams
} else {
var updatePackageOwnership = mutableMapOf(*existingNetParams.packageOwnership.map { Pair(it.key,it.value) }.toTypedArray())
packageOwnership.forEach { key, value ->
if (value == null) {
if (updatePackageOwnership.remove(key) != null)
println("Unregistering package $key")
}
else {
if (updatePackageOwnership.put(key, value) == null)
println("Registering package $key for owner ${value.toStringShort()}")
}
}
existingNetParams.copy(
notaries = notaryInfos,
modifiedTime = Instant.now(),
whitelistedContractImplementations = whitelist,
packageOwnership = updatePackageOwnership,
epoch = existingNetParams.epoch + 1
)
}
@ -377,6 +396,7 @@ internal constructor(private val initSerEnv: Boolean,
maxMessageSize = 10485760,
maxTransactionSize = 10485760,
whitelistedContractImplementations = whitelist,
packageOwnership = packageOwnership.filterNotNullValues(),
epoch = 1,
eventHorizon = 30.days
)

View File

@ -5,29 +5,28 @@ import net.corda.core.crypto.secureRandomBytes
import net.corda.core.crypto.sha256
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.node.JavaPackageName
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.*
import net.corda.testing.internal.createNodeInfoAndSigned
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import java.security.PublicKey
import kotlin.streams.toList
class NetworkBootstrapperTest {
@ -35,6 +34,10 @@ class NetworkBootstrapperTest {
@JvmField
val tempFolder = TemporaryFolder()
@Rule
@JvmField
val expectedEx: ExpectedException = ExpectedException.none()
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@ -208,6 +211,80 @@ class NetworkBootstrapperTest {
assertThat(networkParameters.epoch).isEqualTo(2)
}
private val ALICE = TestIdentity(ALICE_NAME, 70)
private val BOB = TestIdentity(BOB_NAME, 80)
private val alicePackageName = JavaPackageName("com.example.alice")
private val bobPackageName = JavaPackageName("com.example.bob")
@Test
fun `register new package namespace in existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
}
@Test
fun `register additional package namespace in existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
// register additional package name
createNodeConfFile("bob", bobConfig)
bootstrap(packageOwnership = mapOf(Pair(bobPackageName, BOB.publicKey)))
assertContainsPackageOwner("bob", mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
}
@Test
fun `attempt to register overlapping namespaces in existing network`() {
createNodeConfFile("alice", aliceConfig)
val greedyNamespace = JavaPackageName("com.example")
bootstrap(packageOwnership = mapOf(Pair(greedyNamespace, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(greedyNamespace, ALICE.publicKey)))
// register overlapping package name
createNodeConfFile("bob", bobConfig)
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("multiple packages added to the packageOwnership overlap.")
bootstrap(packageOwnership = mapOf(Pair(bobPackageName, BOB.publicKey)))
}
@Test
fun `unregister single package namespace in network of one`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
// unregister package name
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null)))
assertContainsPackageOwner("alice", emptyMap())
}
@Test
fun `unregister single package namespace in network of many`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
// unregister package name
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null)))
assertContainsPackageOwner("alice", mapOf(Pair(bobPackageName, BOB.publicKey)))
}
@Test
fun `unregister all package namespaces in existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
// unregister all package names
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null), Pair(bobPackageName, null)))
assertContainsPackageOwner("alice", emptyMap())
}
@Test
fun `register and unregister sample package namespace in network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(alicePackageName, null)))
assertContainsPackageOwner("alice", emptyMap())
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null), Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
}
private val rootDir get() = tempFolder.root.toPath()
private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
@ -216,9 +293,9 @@ class NetworkBootstrapperTest {
return bytes
}
private fun bootstrap(copyCordapps: Boolean = true) {
private fun bootstrap(copyCordapps: Boolean = true, packageOwnership : Map<JavaPackageName, PublicKey?> = emptyMap()) {
providedCordaJar = (rootDir / "corda.jar").let { if (it.exists()) it.readAll() else null }
bootstrapper.bootstrap(rootDir, copyCordapps, PLATFORM_VERSION)
bootstrapper.bootstrap(rootDir, copyCordapps, PLATFORM_VERSION, packageOwnership)
}
private fun createNodeConfFile(nodeDirName: String, config: FakeNodeConfig) {
@ -286,5 +363,10 @@ class NetworkBootstrapperTest {
}
}
private fun assertContainsPackageOwner(nodeDirName: String, packageOwners: Map<JavaPackageName, PublicKey>) {
val networkParams = (rootDir / nodeDirName).networkParameters
assertThat(networkParams.packageOwnership).isEqualTo(packageOwners)
}
data class FakeNodeConfig(val myLegalName: CordaX500Name, val notary: NotaryConfig? = null)
}