diff --git a/bridge/bridgecapsule/build.gradle b/bridge/bridgecapsule/build.gradle index 065f546d6e..c1e4b5f46f 100644 --- a/bridge/bridgecapsule/build.gradle +++ b/bridge/bridgecapsule/build.gradle @@ -64,6 +64,10 @@ jar { baseName 'corda-firewall' } +capsule { + version capsule_version +} + task buildFirewallJar(type: FatCapsule, dependsOn: project(':bridge').jar) { applicationClass 'net.corda.bridge.Firewall' archiveName "corda-firewall-${corda_release_version}.jar" diff --git a/build.gradle b/build.gradle index 2776b05010..87b15040a0 100644 --- a/build.gradle +++ b/build.gradle @@ -19,9 +19,9 @@ buildscript { ext.quasar_group = 'co.paralleluniverse' ext.quasar_version = '0.7.10' - // gradle-capsule-plugin:1.0.2 contains capsule:1.0.1 - // TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3 - ext.capsule_version = '1.0.1' + // gradle-capsule-plugin:1.0.2 contains capsule:1.0.1 by default. + // We must configure it manually to use the latest capsule version. + ext.capsule_version = '1.0.3' ext.asm_version = '5.0.4' ext.artemis_version = '2.6.2' @@ -119,8 +119,8 @@ buildscript { plugins { // TODO The capsule plugin requires the newer DSL plugin block.It would be nice if we could unify all the plugins into one style, // but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle. - // Version 1.0.2 of this plugin uses capsule:1.0.1 - id "us.kirchmeier.capsule" version "1.0.2" + // Version 1.0.2 of this plugin uses capsule:1.0.1 by default. + id 'us.kirchmeier.capsule' version '1.0.2' apply false // Add the shadow plugin to the plugins classpath for the entire project. id 'com.github.johnrengelman.shadow' version '2.0.4' apply false @@ -482,6 +482,6 @@ if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BU } wrapper { - gradleVersion = "4.10" + gradleVersion = "4.10.1" distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f73107db58..4e974715fd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 23fcce28dd..dafa767440 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -41,15 +41,21 @@ object X509Utilities { val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512 val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256 - // TODO This class is more of a general purpose utility class and as such these constants belong elsewhere + // TODO This class is more of a general purpose utility class and as such these constants belong elsewhere. // Aliases for private keys and certificates. const val CORDA_ROOT_CA = "cordarootca" const val CORDA_INTERMEDIATE_CA = "cordaintermediateca" const val CORDA_CLIENT_TLS = "cordaclienttls" const val CORDA_CLIENT_CA = "cordaclientca" - // TODO These don't need to be prefixes, but can be the full aliases. + // TODO These don't need to be prefixes, but can be the full aliases. However, because they are used as key aliases + // we should ensure that: + // a) they always contain valid characters, preferably [A-Za-z0-9] in order to be supported by the majority of + // crypto service implementations (i.e., HSMs). + // b) they are at most 127 chars in length (i.e., as of 2018, Azure Key Vault does not support bigger aliases). const val NODE_IDENTITY_ALIAS_PREFIX = "identity" + // TODO Hyphen (-) seems to be supported by the major HSM vendors, but we should consider remove it in the + // future and stick to [A-Za-z0-9]. const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary" val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index dcf25002f9..f88b5ad5e7 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -33,6 +33,10 @@ targetCompatibility = 1.6 jar.enabled = false +capsule { + version capsule_version +} + task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) { applicationClass 'net.corda.node.Corda' archiveName "corda-enterprise-${corda_release_version}.jar" diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index f4b3d2b87a..f3d4057a9a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -891,11 +891,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val privateKeyAlias = "$id-private-key" if (privateKeyAlias !in keyStore) { - singleName ?: throw IllegalArgumentException( - "Unable to find in the key store the identity of the distributed notary the node is part of") + // We shouldn't have a distributed notary at this stage, so singleName should NOT be null. + requireNotNull(singleName) { + "Unable to find in the key store the identity of the distributed notary the node is part of" + } log.info("$privateKeyAlias not found in key store, generating fresh key!") - // TODO This check shouldn't be needed - check(singleName == configuration.myLegalName) keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair()) } @@ -904,7 +904,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // TODO: Use configuration to indicate composite key should be used instead of public key for the identity. val compositeKeyAlias = "$id-composite-key" val certificates = if (compositeKeyAlias in keyStore) { - // Use composite key instead if it exists + // Use composite key instead if it exists. val certificate = keyStore[compositeKeyAlias] // We have to create the certificate chain for the composite key manually, this is because we don't have a keystore // provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate + diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index caaaecd44e..f787d9ee62 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -70,64 +70,87 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") { */ override fun runProgram(): Int { val startTime = System.currentTimeMillis() - if (!canNormalizeEmptyPath()) { - println("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest supported version.") - println("Corda will now exit...") - return ExitCodes.FAILURE - } + // Step 1. Check for supported Java version. + if (!isValidJavaVersion()) return ExitCodes.FAILURE - val registrationMode = checkRegistrationMode() - - // TODO: Reconsider if automatic re-registration should be applied when something failed during initial registration. - // There might be cases where the node user should investigate what went wrong before registering again. - if (registrationMode && !cmdLineOptions.isRegistration) { - println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.") - // Pretend that the node was started with `--initial-registration` to help prevent user error. - cmdLineOptions.isRegistration = true - } - - // We do the single node check before we initialise logging so that in case of a double-node start it + // Step 2. We do the single node check before we initialise logging so that in case of a double-node start it // doesn't mess with the running node's logs. enforceSingleNodeIsRunning(cmdLineOptions.baseDirectory) + // Step 3. Initialise logging. initLogging() - // Register all cryptography [Provider]s. + + // Step 4. Register all cryptography [Provider]s. // Required to install our [SecureRandom] before e.g., UUID asks for one. // This needs to go after initLogging(netty clashes with our logging). Crypto.registerProviders() + // Step 5. Print banner and basic node info. val versionInfo = getVersionInfo() - drawBanner(versionInfo) Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path")) + // Step 6. Load and validate node configuration. val configuration = (attempt { loadConfiguration() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success::value) ?: return ExitCodes.FAILURE - val errors = configuration.validate() if (errors.isNotEmpty()) { logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}") return ExitCodes.FAILURE } + // Step 7. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization. attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return ExitCodes.FAILURE + // Step 8. Any actions required before starting up the Corda network layer. attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE - cmdLineOptions.nodeRegistrationOption?.let { - // Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig] - attempt { registerWithNetwork(configuration, versionInfo, it) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE - // At this point the node registration was successful. We can delete the marker file. - deleteNodeRegistrationMarker(cmdLineOptions.baseDirectory) - return ExitCodes.SUCCESS + // Step 9. Check if in registration mode. + checkAndRunRegistrationMode(configuration, versionInfo)?.let { + return if (it) ExitCodes.SUCCESS + else ExitCodes.FAILURE } + // Step 10. Log startup info. logStartupInfo(versionInfo, configuration) + // Step 11. Start node: create the node, check for other command-line options, add extra logging etc. attempt { startNode(configuration, versionInfo, startTime) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError) as? Try.Success ?: return ExitCodes.FAILURE return ExitCodes.SUCCESS } + private fun checkAndRunRegistrationMode(configuration: NodeConfiguration, versionInfo: VersionInfo): Boolean? { + checkUnfinishedRegistration() + cmdLineOptions.nodeRegistrationOption?.let { + // Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig] + attempt { registerWithNetwork(configuration, versionInfo, it) }.doOnException(handleRegistrationError) as? Try.Success + ?: return false + // At this point the node registration was successful. We can delete the marker file. + deleteNodeRegistrationMarker(cmdLineOptions.baseDirectory) + return true + } + return null + } + + private fun isValidJavaVersion(): Boolean { + if (!canNormalizeEmptyPath()) { + println("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest supported version.") + println("Corda will now exit...") + return false + } + return true + } + + // TODO: Reconsider if automatic re-registration should be applied when something failed during initial registration. + // There might be cases where the node user should investigate what went wrong before registering again. + private fun checkUnfinishedRegistration() { + if (checkRegistrationMode() && !cmdLineOptions.isRegistration) { + println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.") + // Pretend that the node was started with `--initial-registration` to help prevent user error. + cmdLineOptions.isRegistration = true + } + } + private fun attempt(action: () -> RESULT): Try = Try.on(action) private fun Exception.isExpectedWhenStartingNode() = startNodeExpectedErrors.any { error -> error.isInstance(this) } @@ -275,6 +298,7 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") { logLoadedCorDapps(node.services.cordappProvider.cordapps) node.nodeReadyFuture.thenMatch({ + // Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits. val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 val name = nodeInfo.legalIdentitiesAndCerts.first().name.organisation Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec") diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index b3186b8d7b..1a56fdb5da 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -46,7 +46,7 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path, private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) { companion object { - const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" + const val SELF_SIGNED_PRIVATE_KEY = "SelfSignedPrivateKey" val logger = contextLogger() } @@ -154,6 +154,8 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path, // Save private key and certificate chain to the key store. with(nodeKeystore.value) { setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = keyPassword) + // The key was temporarily stored as SELF_SIGNED_PRIVATE_KEY, but now that it's signed by the Doorman we + // can delete this old record. internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY) save() } @@ -164,6 +166,7 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path, // Create or load self signed keypair from the key store. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. if (alias !in this) { + // NODE_CA should be TLS compatible due to the cert hierarchy structure. val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair) // Save to the key store. diff --git a/samples/notary-demo/README.md b/samples/notary-demo/README.md index 569c56351b..ee474a8d18 100644 --- a/samples/notary-demo/README.md +++ b/samples/notary-demo/README.md @@ -2,17 +2,13 @@ Notary demo ----------- This demo shows a party getting transactions notarised by either a single-node or a distributed notary service. -All versions of the demo start two counterparty nodes. - -One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit -them for notarisation. The Raft (https://raft.github.io/) version of the demo will start three distributed notary nodes. The BFT SMaRt (https://bft-smart.github.io/library/) version of the demo will start four distributed notary nodes. The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary, every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement. -In the BFT SMaRt distributed notary, three signatures are required. +In the BFT-SMaRt distributed notary, three signatures are required. You will notice that successive transactions get signed by different members of the cluster (usually allocated in a random order). To run the Raft version of the demo from the command line in Unix: @@ -22,7 +18,7 @@ To run the Raft version of the demo from the command line in Unix: Single notaries). 2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs. Wait until a "Node started up and registered in ..." message appears on each of the terminals -3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests +3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Alice Corp" node to initiate notarisation requests In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys To run from the command line in Windows: @@ -32,7 +28,7 @@ To run from the command line in Windows: Single notaries). 2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs. Wait until a "Node started up and registered in ..." message appears on each of the terminals -3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests +3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Alice Corp" node to initiate notarisation requests In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes diff --git a/samples/notary-demo/build.gradle b/samples/notary-demo/build.gradle index 081546b3ff..586d67109b 100644 --- a/samples/notary-demo/build.gradle +++ b/samples/notary-demo/build.gradle @@ -62,14 +62,6 @@ task deployNodesSingle(type: Cordform, dependsOn: 'jar') { } rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] } - node { - name "O=Bob Plc,L=Rome,C=IT" - p2pPort 10005 - rpcSettings { - address "localhost:10006" - adminAddress "localhost:10106" - } - } node { name "O=Notary Service,L=Zurich,C=CH" p2pPort 10009 @@ -95,14 +87,6 @@ task deployNodesCustom(type: Cordform, dependsOn: 'jar') { } rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] } - node { - name "O=Bob Plc,L=Rome,C=IT" - p2pPort 10005 - rpcSettings { - address "localhost:10006" - adminAddress "localhost:10106" - } - } node { name "O=Notary Service,L=Zurich,C=CH" p2pPort 10009 @@ -128,14 +112,6 @@ task deployNodesRaft(type: Cordform, dependsOn: 'jar') { } rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] } - node { - name "O=Bob Plc,L=Rome,C=IT" - p2pPort 10005 - rpcSettings { - address "localhost:10006" - adminAddress "localhost:10106" - } - } node { name "O=Notary Service 0,L=Zurich,C=CH" p2pPort 10009 @@ -200,14 +176,6 @@ task deployNodesBFT(type: Cordform, dependsOn: 'jar') { } rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] } - node { - name "O=Bob Plc,L=Rome,C=IT" - p2pPort 10005 - rpcSettings { - address "localhost:10006" - adminAddress "localhost:10106" - } - } node { name "O=Notary Service 0,L=Zurich,C=CH" p2pPort 10009 diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt index 1de2b92219..ee994cf5bc 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt @@ -3,7 +3,6 @@ package net.corda.notarydemo import net.corda.client.rpc.CordaRPCClient import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.toStringShort -import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.transactions.SignedTransaction @@ -12,6 +11,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.notarydemo.flows.DummyIssueAndMove import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.TestIdentity import java.util.concurrent.Future fun main(args: Array) { @@ -29,13 +29,8 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) { checkNotNull(id) { "No unique notary identity, try cleaning the node directories." } } - private val counterparty by lazy { - val parties = rpc.networkMapSnapshot() - parties.fold(ArrayList()) { acc, elem -> - acc.addAll(elem.legalIdentitiesAndCerts.filter { it.name == BOB_NAME }) - acc - }.single().party - } + /** A dummy identity. */ + private val counterparty = TestIdentity(BOB_NAME).party /** Makes calls to the node rpc to start transaction notarisation. */ fun notarise(count: Int) { diff --git a/tools/dbmigration/build.gradle b/tools/dbmigration/build.gradle index 4789d59a99..c548d19474 100644 --- a/tools/dbmigration/build.gradle +++ b/tools/dbmigration/build.gradle @@ -39,6 +39,6 @@ jar { } publish { - name 'tools-database-manager' disableDefaultJar = true -} \ No newline at end of file + name 'tools-database-manager' +} diff --git a/tools/explorer/capsule/build.gradle b/tools/explorer/capsule/build.gradle index 76d22baf52..e85b9915de 100644 --- a/tools/explorer/capsule/build.gradle +++ b/tools/explorer/capsule/build.gradle @@ -19,6 +19,10 @@ configurations { sourceCompatibility = 1.6 targetCompatibility = 1.6 +capsule { + version capsule_version +} + task buildExplorerJAR(type: FatCapsule, dependsOn: project(':tools:explorer').tasks.jar) { applicationClass 'net.corda.explorer.Main' archiveName "node-explorer-${corda_release_version}.jar" diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 0343c76496..66e358d299 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -134,6 +134,10 @@ jar { zip64 = true } +capsule { + version capsule_version +} + // For building a runnable jar with no other dependencies for remote JMeter slave server, that has Corda code on classpath. // Run with: java -jar jmeter-corda-.jar // No additional args required but will be passed if specified. diff --git a/tools/notary-healthcheck/client/build.gradle b/tools/notary-healthcheck/client/build.gradle index 1316abef58..79e67a4afa 100644 --- a/tools/notary-healthcheck/client/build.gradle +++ b/tools/notary-healthcheck/client/build.gradle @@ -29,6 +29,10 @@ jar { } } +capsule { + version capsule_version +} + task buildClientJar(type: FatCapsule) { applicationClass mainClassName baseName "notaryhealthcheck-client" @@ -47,4 +51,4 @@ publish { name "corda-notary-healthcheck-client" } -assemble.dependsOn buildClientJar \ No newline at end of file +assemble.dependsOn buildClientJar diff --git a/webserver/webcapsule/build.gradle b/webserver/webcapsule/build.gradle index 58a9d3325b..9d63c537a3 100644 --- a/webserver/webcapsule/build.gradle +++ b/webserver/webcapsule/build.gradle @@ -28,6 +28,10 @@ targetCompatibility = 1.6 jar.enabled = false +capsule { + version capsule_version +} + task buildWebserverJar(type: FatCapsule, dependsOn: project(':node').tasks.jar) { applicationClass 'net.corda.webserver.WebServer' archiveName "corda-webserver-${corda_release_version}.jar"