mirror of
https://github.com/corda/corda.git
synced 2025-06-13 20:58:19 +00:00
[CORDA-2235]: Add overrides for network parameters via command line and file (#4279)
* Temp commit * Print the error message first by default, makes error output more natural. * Polishing * Further modifications after testing * Documentation updates * Couple of fixes after review * Removing unnecessary tests * Fix broken test * Add interface to bootstrapper for testign * Added unit tests * Remove unused class * Fix up bootstrapper unit tests and add a couple more * Refactor the tests slightly * Review comments * Couple of minor tweaks
This commit is contained in:
@ -3,7 +3,6 @@ package net.corda.nodeapi.internal.network
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
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.*
|
||||
@ -19,7 +18,6 @@ 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.*
|
||||
@ -36,6 +34,7 @@ import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
@ -56,7 +55,7 @@ class NetworkBootstrapper
|
||||
internal constructor(private val initSerEnv: Boolean,
|
||||
private val embeddedCordaJar: () -> InputStream,
|
||||
private val nodeInfosGenerator: (List<Path>) -> List<Path>,
|
||||
private val contractsJarConverter: (Path) -> ContractsJar) {
|
||||
private val contractsJarConverter: (Path) -> ContractsJar) : NetworkBootstrapperWithOverridableParameters {
|
||||
|
||||
constructor() : this(
|
||||
initSerEnv = true,
|
||||
@ -128,6 +127,9 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
throw IllegalStateException("Error while generating node info file. Please check the logs in $nodeDir.")
|
||||
}
|
||||
}
|
||||
|
||||
const val DEFAULT_MAX_MESSAGE_SIZE: Int = 10485760
|
||||
const val DEFAULT_MAX_TRANSACTION_SIZE: Int = 524288000
|
||||
}
|
||||
|
||||
sealed class NotaryCluster {
|
||||
@ -189,14 +191,15 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
}
|
||||
|
||||
/** Entry point for the tool */
|
||||
fun bootstrap(directory: Path, copyCordapps: Boolean, minimumPlatformVersion: Int, packageOwnership: Map<String, 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!
|
||||
override fun bootstrap(directory: Path, copyCordapps: Boolean, networkParameterOverrides: NetworkParametersOverrides) {
|
||||
require(networkParameterOverrides.minimumPlatformVersion == null || networkParameterOverrides.minimumPlatformVersion <= PLATFORM_VERSION) { "Minimum platform version cannot be greater than $PLATFORM_VERSION" }
|
||||
// Don't accidentally 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()
|
||||
paths.filter { it.toString().endsWith(".jar") && !it.isSameAs(bootstrapperJar) && it.fileName.toString() != "corda.jar" }
|
||||
.toList()
|
||||
}
|
||||
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false, minimumPlatformVersion = minimumPlatformVersion, packageOwnership = packageOwnership)
|
||||
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false, networkParametersOverrides = networkParameterOverrides)
|
||||
}
|
||||
|
||||
private fun bootstrap(
|
||||
@ -204,8 +207,7 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
cordappJars: List<Path>,
|
||||
copyCordapps: Boolean,
|
||||
fromCordform: Boolean,
|
||||
minimumPlatformVersion: Int = PLATFORM_VERSION,
|
||||
packageOwnership: Map<String, PublicKey?> = emptyMap()
|
||||
networkParametersOverrides: NetworkParametersOverrides = NetworkParametersOverrides()
|
||||
) {
|
||||
directory.createDirectories()
|
||||
println("Bootstrapping local test network in $directory")
|
||||
@ -250,8 +252,9 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
println("Gathering notary identities")
|
||||
val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs)
|
||||
println("Generating contract implementations whitelist")
|
||||
// Only add contracts to the whitelist from unsigned jars
|
||||
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.filter { !isSigned(it) }.map(contractsJarConverter))
|
||||
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, minimumPlatformVersion, packageOwnership)
|
||||
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, networkParametersOverrides)
|
||||
if (newNetParams != existingNetParams) {
|
||||
println("${if (existingNetParams == null) "New" else "Updated"} $newNetParams")
|
||||
} else {
|
||||
@ -283,7 +286,8 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
println("Generating node directory for $nodeName")
|
||||
val nodeDir = (directory / nodeName).createDirectories()
|
||||
confFile.copyTo(nodeDir / "node.conf", REPLACE_EXISTING)
|
||||
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.copyTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
|
||||
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }
|
||||
?.copyTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
|
||||
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
@ -378,50 +382,42 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
throw IllegalStateException(msg.toString())
|
||||
}
|
||||
|
||||
private fun defaultNetworkParametersWith(notaryInfos: List<NotaryInfo>,
|
||||
whitelist: Map<String, List<AttachmentId>>): NetworkParameters {
|
||||
return NetworkParameters(
|
||||
minimumPlatformVersion = PLATFORM_VERSION,
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE,
|
||||
maxTransactionSize = DEFAULT_MAX_TRANSACTION_SIZE,
|
||||
whitelistedContractImplementations = whitelist,
|
||||
packageOwnership = emptyMap(),
|
||||
epoch = 1,
|
||||
eventHorizon = 30.days
|
||||
)
|
||||
}
|
||||
|
||||
private fun installNetworkParameters(
|
||||
notaryInfos: List<NotaryInfo>,
|
||||
whitelist: Map<String, List<AttachmentId>>,
|
||||
existingNetParams: NetworkParameters?,
|
||||
nodeDirs: List<Path>,
|
||||
minimumPlatformVersion: Int,
|
||||
packageOwnership: Map<String, PublicKey?>
|
||||
networkParametersOverrides: NetworkParametersOverrides
|
||||
): NetworkParameters {
|
||||
// TODO Add config for maxMessageSize and maxTransactionSize
|
||||
val netParams = if (existingNetParams != null) {
|
||||
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,
|
||||
val newNetParams = existingNetParams
|
||||
.copy(notaries = notaryInfos, whitelistedContractImplementations = whitelist)
|
||||
.overrideWith(networkParametersOverrides)
|
||||
if (newNetParams != existingNetParams) {
|
||||
newNetParams.copy(
|
||||
modifiedTime = Instant.now(),
|
||||
whitelistedContractImplementations = whitelist,
|
||||
packageOwnership = updatePackageOwnership,
|
||||
epoch = existingNetParams.epoch + 1
|
||||
)
|
||||
} else {
|
||||
existingNetParams
|
||||
}
|
||||
} else {
|
||||
NetworkParameters(
|
||||
minimumPlatformVersion = minimumPlatformVersion,
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
maxMessageSize = 10485760,
|
||||
maxTransactionSize = 524288000,
|
||||
whitelistedContractImplementations = whitelist,
|
||||
packageOwnership = packageOwnership.filterNotNullValues(),
|
||||
epoch = 1,
|
||||
eventHorizon = 30.days
|
||||
)
|
||||
defaultNetworkParametersWith(notaryInfos, whitelist).overrideWith(networkParametersOverrides)
|
||||
}
|
||||
val copier = NetworkParametersCopier(netParams, overwriteFile = true)
|
||||
nodeDirs.forEach(copier::install)
|
||||
@ -435,7 +431,7 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
|
||||
// cluster and is shared by all the other members. This is the notary identity.
|
||||
2 -> legalIdentities[1]
|
||||
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
|
||||
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenario: $this")
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,3 +460,27 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun NetworkParameters.overrideWith(override: NetworkParametersOverrides): NetworkParameters {
|
||||
return this.copy(
|
||||
minimumPlatformVersion = override.minimumPlatformVersion ?: this.minimumPlatformVersion,
|
||||
maxMessageSize = override.maxMessageSize ?: this.maxMessageSize,
|
||||
maxTransactionSize = override.maxTransactionSize ?: this.maxTransactionSize,
|
||||
eventHorizon = override.eventHorizon ?: this.eventHorizon,
|
||||
packageOwnership = override.packageOwnership?.map { it.javaPackageName to it.publicKey }?.toMap() ?: this.packageOwnership
|
||||
)
|
||||
}
|
||||
|
||||
data class PackageOwner(val javaPackageName: String, val publicKey: PublicKey)
|
||||
|
||||
data class NetworkParametersOverrides(
|
||||
val minimumPlatformVersion: Int? = null,
|
||||
val maxMessageSize: Int? = null,
|
||||
val maxTransactionSize: Int? = null,
|
||||
val packageOwnership: List<PackageOwner>? = null,
|
||||
val eventHorizon: Duration? = null
|
||||
)
|
||||
|
||||
interface NetworkBootstrapperWithOverridableParameters {
|
||||
fun bootstrap(directory: Path, copyCordapps: Boolean, networkParameterOverrides: NetworkParametersOverrides = NetworkParametersOverrides())
|
||||
}
|
@ -11,9 +11,12 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||
import net.corda.core.internal.NODE_INFO_DIRECTORY
|
||||
import net.corda.core.utilities.days
|
||||
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.NetworkBootstrapper.Companion.DEFAULT_MAX_MESSAGE_SIZE
|
||||
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_TRANSACTION_SIZE
|
||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.createNodeInfoAndSigned
|
||||
@ -26,6 +29,7 @@ import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Path
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import kotlin.streams.toList
|
||||
|
||||
class NetworkBootstrapperTest {
|
||||
@ -210,6 +214,24 @@ class NetworkBootstrapperTest {
|
||||
assertThat(networkParameters.epoch).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `network parameters overrides`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
val minimumPlatformVersion = 2
|
||||
val maxMessageSize = 10000
|
||||
val maxTransactionSize = 20000
|
||||
val eventHorizon = 7.days
|
||||
bootstrap(minimumPlatformVerison = minimumPlatformVersion,
|
||||
maxMessageSize = maxMessageSize,
|
||||
maxTransactionSize = maxTransactionSize,
|
||||
eventHorizon = eventHorizon)
|
||||
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
|
||||
assertThat(networkParameters.minimumPlatformVersion).isEqualTo(minimumPlatformVersion)
|
||||
assertThat(networkParameters.maxMessageSize).isEqualTo(maxMessageSize)
|
||||
assertThat(networkParameters.maxTransactionSize).isEqualTo(maxTransactionSize)
|
||||
assertThat(networkParameters.eventHorizon).isEqualTo(eventHorizon)
|
||||
}
|
||||
|
||||
private val ALICE = TestIdentity(ALICE_NAME, 70)
|
||||
private val BOB = TestIdentity(BOB_NAME, 80)
|
||||
|
||||
@ -230,7 +252,7 @@ class NetworkBootstrapperTest {
|
||||
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
|
||||
// register additional package name
|
||||
createNodeConfFile("bob", bobConfig)
|
||||
bootstrap(packageOwnership = mapOf(Pair(bobPackageName, BOB.publicKey)))
|
||||
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
|
||||
assertContainsPackageOwner("bob", mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
|
||||
}
|
||||
|
||||
@ -243,8 +265,8 @@ class NetworkBootstrapperTest {
|
||||
// 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)))
|
||||
expectedEx.expectMessage("Multiple packages added to the packageOwnership overlap.")
|
||||
bootstrap(packageOwnership = mapOf(Pair(greedyNamespace, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -253,7 +275,7 @@ class NetworkBootstrapperTest {
|
||||
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
|
||||
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
|
||||
// unregister package name
|
||||
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null)))
|
||||
bootstrap(packageOwnership = emptyMap())
|
||||
assertContainsPackageOwner("alice", emptyMap())
|
||||
}
|
||||
|
||||
@ -262,8 +284,8 @@ class NetworkBootstrapperTest {
|
||||
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)))
|
||||
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
|
||||
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -271,19 +293,10 @@ class NetworkBootstrapperTest {
|
||||
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)))
|
||||
bootstrap(packageOwnership = emptyMap())
|
||||
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 {
|
||||
@ -292,9 +305,20 @@ class NetworkBootstrapperTest {
|
||||
return bytes
|
||||
}
|
||||
|
||||
private fun bootstrap(copyCordapps: Boolean = true, packageOwnership : Map<String, PublicKey?> = emptyMap()) {
|
||||
private fun bootstrap(copyCordapps: Boolean = true,
|
||||
packageOwnership: Map<String, PublicKey>? = emptyMap(),
|
||||
minimumPlatformVerison: Int? = PLATFORM_VERSION,
|
||||
maxMessageSize: Int? = DEFAULT_MAX_MESSAGE_SIZE,
|
||||
maxTransactionSize: Int? = DEFAULT_MAX_TRANSACTION_SIZE,
|
||||
eventHorizon: Duration? = 30.days) {
|
||||
providedCordaJar = (rootDir / "corda.jar").let { if (it.exists()) it.readAll() else null }
|
||||
bootstrapper.bootstrap(rootDir, copyCordapps, PLATFORM_VERSION, packageOwnership)
|
||||
bootstrapper.bootstrap(rootDir, copyCordapps, NetworkParametersOverrides(
|
||||
minimumPlatformVersion = minimumPlatformVerison,
|
||||
maxMessageSize = maxMessageSize,
|
||||
maxTransactionSize = maxTransactionSize,
|
||||
eventHorizon = eventHorizon,
|
||||
packageOwnership = packageOwnership?.map { PackageOwner(it.key, it.value!!) }
|
||||
))
|
||||
}
|
||||
|
||||
private fun createNodeConfFile(nodeDirName: String, config: FakeNodeConfig) {
|
||||
@ -320,19 +344,23 @@ class NetworkBootstrapperTest {
|
||||
return cordappBytes
|
||||
}
|
||||
|
||||
private val Path.networkParameters: NetworkParameters get() {
|
||||
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>().verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
}
|
||||
private val Path.networkParameters: NetworkParameters
|
||||
get() {
|
||||
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>()
|
||||
.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
}
|
||||
|
||||
private val Path.nodeInfoFile: Path get() {
|
||||
return list { it.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.toList() }.single()
|
||||
}
|
||||
private val Path.nodeInfoFile: Path
|
||||
get() {
|
||||
return list { it.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.toList() }.single()
|
||||
}
|
||||
|
||||
private val Path.nodeInfo: NodeInfo get() = nodeInfoFile.readObject<SignedNodeInfo>().verified()
|
||||
|
||||
private val Path.fakeNodeConfig: FakeNodeConfig get() {
|
||||
return ConfigFactory.parseFile((this / "node.conf").toFile()).parseAs(FakeNodeConfig::class)
|
||||
}
|
||||
private val Path.fakeNodeConfig: FakeNodeConfig
|
||||
get() {
|
||||
return ConfigFactory.parseFile((this / "node.conf").toFile()).parseAs(FakeNodeConfig::class)
|
||||
}
|
||||
|
||||
private fun assertBootstrappedNetwork(cordaJar: ByteArray, vararg nodes: Pair<String, FakeNodeConfig>): NetworkParameters {
|
||||
val networkParameters = (rootDir / nodes[0].first).networkParameters
|
||||
|
Reference in New Issue
Block a user