CORDA-1312: Network bootstrapper copies any CorDapp jars into each nodes' cordapps dir (#2974)

This commit is contained in:
Shams Asari 2018-04-17 15:38:25 +01:00 committed by GitHub
parent 1288f63998
commit 60323cca15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 35 deletions

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=4.0.12
gradlePluginsVersion=4.0.13
kotlinVersion=1.2.20
platformVersion=4
guavaVersion=21.0

View File

@ -19,8 +19,10 @@ Unreleased
* Shell (embedded available only in dev mode or via SSH) connects to the node via RPC instead of using the ``CordaRPCOps`` object directly.
To enable RPC connectivity ensure nodes ``rpcSettings.address`` and ``rpcSettings.adminAddress`` settings are present.
* The network bootstrapper uses the existing network parameters file to update the current contracts whitelist, and no longer
needs the whitelist.txt file.
* Changes to the network bootstrapper:
* The whitelist.txt file is no longer needed. The existing network parameters file is used to update the current contracts
whitelist.
* The CorDapp jars are also copied to each nodes' `cordapps` directory.
* Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation of internal data.

View File

@ -91,7 +91,7 @@ Whitelisting Contracts
If you want to create a *Zone whitelist* (see :doc:`api-contract-constraints`), you can pass in a list of CorDapp jars:
``java -jar network-bootstrapper.jar <nodes-root-dir> <path-to-first-corDapp> <path-to-second-corDapp> ..``
``java -jar network-bootstrapper.jar <nodes-root-dir> <1st CorDapp jar> <2nd CorDapp jar> ..``
The CorDapp jars will be hashed and scanned for ``Contract`` classes. These contract class implementations will become part
of the whitelisted contracts in the network parameters (see ``NetworkParameters.whitelistedContractImplementations`` :doc:`network-map`).
@ -111,6 +111,9 @@ For example:
net.corda.finance.contracts.asset.Cash
net.corda.finance.contracts.asset.CommercialPaper
In addition to using the CorDapp jars to update the whitelist, the bootstrapper will also copy them to all the nodes'
``cordapps`` directory.
Starting the nodes
~~~~~~~~~~~~~~~~~~

View File

@ -6,11 +6,11 @@ import net.corda.core.contracts.ContractClassName
import net.corda.core.internal.copyTo
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.read
import java.io.File
import java.io.InputStream
import java.lang.reflect.Modifier
import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
@ -18,13 +18,16 @@ import java.nio.file.StandardCopyOption
* Scans the jar for contracts.
* @returns: found contract class names or null if none found
*/
fun scanJarForContracts(cordappJarPath: String): List<ContractClassName> {
fun scanJarForContracts(cordappJar: Path): List<ContractClassName> {
val currentClassLoader = Contract::class.java.classLoader
val scanResult = FastClasspathScanner().addClassLoader(currentClassLoader).overrideClasspath(cordappJarPath, Paths.get(Contract::class.java.protectionDomain.codeSource.location.toURI()).toString()).scan()
val scanResult = FastClasspathScanner()
.addClassLoader(currentClassLoader)
.overrideClasspath(cordappJar, Paths.get(Contract::class.java.protectionDomain.codeSource.location.toURI()))
.scan()
val contracts = (scanResult.getNamesOfClassesImplementing(Contract::class.qualifiedName) ).distinct()
// Only keep instantiable contracts
return URLClassLoader(arrayOf(File(cordappJarPath).toURL()), currentClassLoader).use {
return URLClassLoader(arrayOf(cordappJar.toUri().toURL()), currentClassLoader).use {
contracts.map(it::loadClass).filter { !it.isInterface && !Modifier.isAbstract(it.modifiers) }
}.map { it.name }
}
@ -33,7 +36,7 @@ fun <T> withContractsInJar(jarInputStream: InputStream, withContracts: (List<Con
val tempFile = Files.createTempFile("attachment", ".jar")
try {
jarInputStream.copyTo(tempFile, StandardCopyOption.REPLACE_EXISTING)
val contracts = scanJarForContracts(tempFile.toAbsolutePath().toString())
val contracts = scanJarForContracts(tempFile.toAbsolutePath())
return tempFile.read { withContracts(contracts, it) }
} finally {
tempFile.deleteIfExists()

View File

@ -18,6 +18,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
import net.corda.nodeapi.internal.scanJarForContracts
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
@ -53,15 +54,15 @@ class NetworkBootstrapper {
@JvmStatic
fun main(args: Array<String>) {
val baseNodeDirectory = requireNotNull(args.firstOrNull()) { "Expecting first argument which is the nodes' parent directory" }
val cordapps = if (args.size > 1) args.toList().drop(1) else null
NetworkBootstrapper().bootstrap(Paths.get(baseNodeDirectory).toAbsolutePath().normalize(), cordapps)
val cordappJars = if (args.size > 1) args.asList().drop(1).map { Paths.get(it) } else emptyList()
NetworkBootstrapper().bootstrap(Paths.get(baseNodeDirectory).toAbsolutePath().normalize(), cordappJars)
}
}
fun bootstrap(directory: Path, cordapps: List<String>?) {
fun bootstrap(directory: Path, cordappJars: List<Path>) {
directory.createDirectories()
println("Bootstrapping local network in $directory")
generateDirectoriesIfNeeded(directory)
generateDirectoriesIfNeeded(directory, cordappJars)
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
require(nodeDirs.isNotEmpty()) { "No nodes found" }
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
@ -78,7 +79,7 @@ class NetworkBootstrapper {
println("Gathering notary identities")
val notaryInfos = gatherNotaryInfos(nodeInfoFiles)
println("Generating contract implementations whitelist")
val newWhitelist = generateWhitelist(existingNetParams, directory / EXCLUDE_WHITELIST_FILE_NAME, cordapps?.distinct())
val newWhitelist = generateWhitelist(existingNetParams, directory / EXCLUDE_WHITELIST_FILE_NAME, cordappJars)
val netParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs)
println("${if (existingNetParams == null) "New" else "Updated"} $netParams")
println("Bootstrapping complete!")
@ -88,11 +89,11 @@ class NetworkBootstrapper {
}
}
private fun generateDirectoriesIfNeeded(directory: Path) {
private fun generateDirectoriesIfNeeded(directory: Path, cordappJars: List<Path>) {
val confFiles = directory.list { it.filter { it.toString().endsWith("_node.conf") }.toList() }
val webServerConfFiles = directory.list { it.filter { it.toString().endsWith("_web-server.conf") }.toList() }
if (confFiles.isEmpty()) return
println("Node config files found in the root directory - generating node directories")
println("Node config files found in the root directory - generating node directories and copying CorDapp jars into them")
val cordaJar = extractCordaJarTo(directory)
for (confFile in confFiles) {
val nodeName = confFile.fileName.toString().removeSuffix("_node.conf")
@ -101,6 +102,8 @@ class NetworkBootstrapper {
confFile.moveTo(nodeDir / "node.conf", REPLACE_EXISTING)
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.moveTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
val cordappsDir = (nodeDir / "cordapps").createDirectories()
cordappJars.forEach { it.copyToDirectory(cordappsDir) }
}
Files.delete(cordaJar)
}
@ -108,7 +111,6 @@ class NetworkBootstrapper {
private fun extractCordaJarTo(directory: Path): Path {
val cordaJarPath = directory / "corda.jar"
if (!cordaJarPath.exists()) {
println("No corda jar found in root directory. Extracting from jar")
Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar").copyTo(cordaJarPath)
}
return cordaJarPath
@ -134,7 +136,7 @@ class NetworkBootstrapper {
check(process.waitFor() == 0) {
"Node in ${nodeDir.fileName} exited with ${process.exitValue()} when generating its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
}
nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() }
}
}
@ -182,7 +184,7 @@ class NetworkBootstrapper {
}
val msg = StringBuilder("Differing sets of network parameters were found. Make sure all the nodes have the same " +
"network parameters by copying over the correct $NETWORK_PARAMS_FILE_NAME file.\n\n")
"network parameters by copying the correct $NETWORK_PARAMS_FILE_NAME file across.\n\n")
netParamsFilesGrouped.forEach { bytes, netParamsFiles ->
netParamsFiles.map { it.parent.fileName }.joinTo(msg, ", ")
@ -211,6 +213,7 @@ class NetworkBootstrapper {
epoch = existingNetParams.epoch + 1
)
} else {
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
NetworkParameters(
minimumPlatformVersion = 1,
notaries = notaryInfos,
@ -221,28 +224,25 @@ class NetworkBootstrapper {
epoch = 1
)
}
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
val copier = NetworkParametersCopier(networkParameters, overwriteFile = true)
nodeDirs.forEach { copier.install(it) }
nodeDirs.forEach(copier::install)
return networkParameters
}
private fun generateWhitelist(networkParameters: NetworkParameters?,
excludeWhitelistFile: Path,
cordapps: List<String>?): Map<String, List<AttachmentId>> {
cordappJars: List<Path>): Map<String, List<AttachmentId>> {
val existingWhitelist = networkParameters?.whitelistedContractImplementations ?: emptyMap()
val excludeContracts = if (excludeWhitelistFile.exists()) readExcludeWhitelist(excludeWhitelistFile) else emptyList()
val excludeContracts = readExcludeWhitelist(excludeWhitelistFile)
if (excludeContracts.isNotEmpty()) {
println("Exclude contracts from whitelist: ${excludeContracts.joinToString()}}")
println("Exclude contracts from whitelist: ${excludeContracts.joinToString()}")
}
val newWhiteList = cordapps?.flatMap { cordappJarPath ->
val jarHash = Paths.get(cordappJarPath).hash
scanJarForContracts(cordappJarPath).map { contract ->
contract to jarHash
}
}?.filter { (contractClassName, _) -> contractClassName !in excludeContracts }?.toMap() ?: emptyMap()
val newWhiteList = cordappJars.flatMap { cordappJar ->
val jarHash = cordappJar.hash
scanJarForContracts(cordappJar).map { contract -> contract to jarHash }
}.filter { (contractClassName, _) -> contractClassName !in excludeContracts }.toMap()
return (newWhiteList.keys + existingWhitelist.keys).map { contractClassName ->
val existing = existingWhitelist[contractClassName] ?: emptyList()
@ -251,7 +251,9 @@ class NetworkBootstrapper {
}.toMap()
}
private fun readExcludeWhitelist(file: Path): List<String> = file.readAllLines().map(String::trim)
private fun readExcludeWhitelist(file: Path): List<String> {
return if (file.exists()) file.readAllLines().map(String::trim) else emptyList()
}
private fun NodeInfo.notaryIdentity(): Party {
return when (legalIdentities.size) {

View File

@ -33,10 +33,14 @@ data class NetworkMap(
val parametersUpdate: ParametersUpdate?
) {
override fun toString(): String {
return """${NetworkMap::class.java.simpleName}(nodeInfoHashes=
${nodeInfoHashes.joinToString("\n")}
networkParameterHash=$networkParameterHash
parametersUpdate=$parametersUpdate)"""
return """NetworkMap {
nodeInfoHashes {
${nodeInfoHashes.asSequence().take(10).joinToString("\n ")}
${if (nodeInfoHashes.size > 10) "... ${nodeInfoHashes.size - 10} more" else ""}
}
networkParameterHash=$networkParameterHash
parametersUpdate=$parametersUpdate
}"""
}
}