mirror of
https://github.com/corda/corda.git
synced 2025-06-13 20:58:19 +00:00
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:
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user