[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:
Anthony Keenan
2018-11-26 17:11:05 +00:00
committed by GitHub
parent d399e3c242
commit b7d04b1c6e
24 changed files with 787 additions and 415 deletions

View File

@ -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())
}

View File

@ -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