diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml
deleted file mode 100644
index 321d3d2d06..0000000000
--- a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml
deleted file mode 100644
index ea61b6ef8d..0000000000
--- a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index d71ffe58cc..e72f20a51a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,6 +48,7 @@ buildscript {
ext.commons_collections_version = '4.1'
ext.beanutils_version = '1.9.3'
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e'
+ ext.jsr305_version = constants.getProperty("jsr305Version")
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131'
diff --git a/constants.properties b/constants.properties
index 211f29e563..7ee707fd23 100644
--- a/constants.properties
+++ b/constants.properties
@@ -1,6 +1,7 @@
-gradlePluginsVersion=2.0.9
+gradlePluginsVersion=3.0.0
kotlinVersion=1.1.60
platformVersion=2
guavaVersion=21.0
bouncycastleVersion=1.57
-typesafeConfigVersion=1.3.1
\ No newline at end of file
+typesafeConfigVersion=1.3.1
+jsr305Version=3.0.2
diff --git a/core/build.gradle b/core/build.gradle
index ab67d23248..bd443598d7 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -78,7 +78,7 @@ dependencies {
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
// Thread safety annotations
- compile "com.google.code.findbugs:jsr305:3.0.1"
+ compile "com.google.code.findbugs:jsr305:$jsr305_version"
// Log4J: logging framework (ONLY explicitly referenced by net.corda.core.utilities.Logging.kt)
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
diff --git a/gradle-plugins/build.gradle b/gradle-plugins/build.gradle
index 88169c215c..bfab124a56 100644
--- a/gradle-plugins/build.gradle
+++ b/gradle-plugins/build.gradle
@@ -10,6 +10,7 @@ buildscript {
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
+ ext.jsr305_version = constants.getProperty("jsr305Version")
ext.kotlin_version = constants.getProperty("kotlinVersion")
repositories {
diff --git a/gradle-plugins/cordform-common/build.gradle b/gradle-plugins/cordform-common/build.gradle
index 82274e0d09..2423bcd773 100644
--- a/gradle-plugins/cordform-common/build.gradle
+++ b/gradle-plugins/cordform-common/build.gradle
@@ -11,6 +11,9 @@ version gradle_plugins_version
group 'net.corda.plugins'
dependencies {
+ // JSR 305: Nullability annotations
+ compile "com.google.code.findbugs:jsr305:$jsr305_version"
+
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java
index 047d7e6289..fc62b1bbee 100644
--- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java
+++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java
@@ -1,24 +1,40 @@
package net.corda.cordform;
+import javax.annotation.Nonnull;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
public abstract class CordformDefinition {
- public final Path driverDirectory;
- public final ArrayList> nodeConfigurers = new ArrayList<>();
+ private Path nodesDirectory = Paths.get("build", "nodes");
+ private final List> nodeConfigurers = new ArrayList<>();
+ private final List cordappPackages = new ArrayList<>();
- public CordformDefinition(Path driverDirectory) {
- this.driverDirectory = driverDirectory;
+ public Path getNodesDirectory() {
+ return nodesDirectory;
}
- public void addNode(Consumer super CordformNode> configurer) {
+ public void setNodesDirectory(Path nodesDirectory) {
+ this.nodesDirectory = nodesDirectory;
+ }
+
+ public List> getNodeConfigurers() {
+ return nodeConfigurers;
+ }
+
+ public void addNode(Consumer configurer) {
nodeConfigurers.add(configurer);
}
+ public List getCordappPackages() {
+ return cordappPackages;
+ }
+
/**
* Make arbitrary changes to the node directories before they are started.
* @param context Lookup of node directory by node name.
*/
- public abstract void setup(CordformContext context);
+ public abstract void setup(@Nonnull CordformContext context);
}
diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java
index f8c4248f99..33b433a506 100644
--- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java
+++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java
@@ -2,6 +2,9 @@ package net.corda.cordform;
import static java.util.Collections.emptyList;
import com.typesafe.config.*;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -54,7 +57,7 @@ public class CordformNode implements NodeDefinition {
*/
public void name(String name) {
this.name = name;
- config = config.withValue("myLegalName", ConfigValueFactory.fromAnyRef(name));
+ setValue("myLegalName", name);
}
/**
@@ -62,6 +65,7 @@ public class CordformNode implements NodeDefinition {
*
* @return This node's P2P address.
*/
+ @Nonnull
public String getP2pAddress() {
return config.getString("p2pAddress");
}
@@ -71,8 +75,8 @@ public class CordformNode implements NodeDefinition {
*
* @param p2pPort The Artemis messaging queue port.
*/
- public void p2pPort(Integer p2pPort) {
- config = config.withValue("p2pAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + p2pPort));
+ public void p2pPort(int p2pPort) {
+ p2pAddress(DEFAULT_HOST + ':' + p2pPort);
}
/**
@@ -81,7 +85,15 @@ public class CordformNode implements NodeDefinition {
* @param p2pAddress The Artemis messaging queue host and port.
*/
public void p2pAddress(String p2pAddress) {
- config = config.withValue("p2pAddress", ConfigValueFactory.fromAnyRef(p2pAddress));
+ setValue("p2pAddress", p2pAddress);
+ }
+
+ /**
+ * Returns the RPC address for this node, or null if one hasn't been specified.
+ */
+ @Nullable
+ public String getRpcAddress() {
+ return getOptionalString("rpcAddress");
}
/**
@@ -89,8 +101,8 @@ public class CordformNode implements NodeDefinition {
*
* @param rpcPort The Artemis RPC queue port.
*/
- public void rpcPort(Integer rpcPort) {
- config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + rpcPort));
+ public void rpcPort(int rpcPort) {
+ rpcAddress(DEFAULT_HOST + ':' + rpcPort);
}
/**
@@ -99,7 +111,31 @@ public class CordformNode implements NodeDefinition {
* @param rpcAddress The Artemis RPC queue host and port.
*/
public void rpcAddress(String rpcAddress) {
- config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(rpcAddress));
+ setValue("rpcAddress", rpcAddress);
+ }
+
+ /**
+ * Returns the address of the web server that will connect to the node, or null if one hasn't been specified.
+ */
+ @Nullable
+ public String getWebAddress() {
+ return getOptionalString("webAddress");
+ }
+
+ /**
+ * Configure a webserver to connect to the node via RPC. This port will specify the port it will listen on. The node
+ * must have an RPC address configured.
+ */
+ public void webPort(int webPort) {
+ webAddress(DEFAULT_HOST + ':' + webPort);
+ }
+
+ /**
+ * Configure a webserver to connect to the node via RPC. This address will specify the port it will listen on. The node
+ * must have an RPC address configured.
+ */
+ public void webAddress(String webAddress) {
+ setValue("webAddress", webAddress);
}
/**
@@ -108,6 +144,14 @@ public class CordformNode implements NodeDefinition {
* @param configFile The file path.
*/
public void configFile(String configFile) {
- config = config.withValue("configFile", ConfigValueFactory.fromAnyRef(configFile));
+ setValue("configFile", configFile);
+ }
+
+ private String getOptionalString(String path) {
+ return config.hasPath(path) ? config.getString(path) : null;
+ }
+
+ private void setValue(String path, Object value) {
+ config = config.withValue(path, ConfigValueFactory.fromAnyRef(value));
}
}
diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt
index 057b1861b5..f43d39f6f4 100644
--- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt
+++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt
@@ -11,10 +11,10 @@ import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.net.URLClassLoader
-import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
+import java.util.jar.JarInputStream
/**
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
@@ -23,12 +23,16 @@ import java.util.concurrent.TimeUnit
*/
@Suppress("unused")
open class Cordform : DefaultTask() {
+ private companion object {
+ private val defaultDirectory: Path = Paths.get("build", "nodes")
+ }
+
/**
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
*/
@Suppress("MemberVisibilityCanPrivate")
var definitionClass: String? = null
- private var directory = Paths.get("build", "nodes")
+ private var directory = defaultDirectory
private val nodes = mutableListOf()
/**
@@ -116,7 +120,6 @@ open class Cordform : DefaultTask() {
/**
* This task action will create and install the nodes based on the node configurations added.
*/
- @Suppress("unused")
@TaskAction
fun build() {
project.logger.info("Running Cordform task")
@@ -129,10 +132,18 @@ open class Cordform : DefaultTask() {
private fun initializeConfiguration() {
if (definitionClass != null) {
val cd = loadCordformDefinition()
+ // If the user has specified their own directory (even if it's the same default path) then let them know
+ // it's not used and should just rely on the one in CordformDefinition
+ require(directory === defaultDirectory) {
+ "'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
+ }
+ directory = cd.nodesDirectory
+ val cordapps = cd.getMatchingCordapps()
cd.nodeConfigurers.forEach {
val node = node { }
it.accept(node)
node.rootDir(directory)
+ node.installCordapps(cordapps)
}
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
} else {
@@ -142,6 +153,30 @@ open class Cordform : DefaultTask() {
}
}
+ private fun CordformDefinition.getMatchingCordapps(): List {
+ val cordappJars = project.configuration("cordapp").files
+ return cordappPackages.map { `package` ->
+ val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
+ when (cordappsWithPackage.size) {
+ 0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
+ 1 -> cordappsWithPackage[0]
+ else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
+ }
+ }
+ }
+
+ private fun File.containsPackage(`package`: String): Boolean {
+ JarInputStream(inputStream()).use {
+ while (true) {
+ val name = it.nextJarEntry?.name ?: break
+ if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
+ return true
+ }
+ }
+ return false
+ }
+ }
+
private fun generateAndInstallNodeInfos() {
generateNodeInfos()
installNodeInfos()
@@ -149,7 +184,7 @@ open class Cordform : DefaultTask() {
private fun generateNodeInfos() {
project.logger.info("Generating node infos")
- var nodeProcesses = buildNodeProcesses()
+ val nodeProcesses = buildNodeProcesses()
try {
validateNodeProcessess(nodeProcesses)
} finally {
@@ -177,7 +212,7 @@ open class Cordform : DefaultTask() {
private fun buildNodeProcess(node: Node): Pair {
node.makeLogDirectory()
- var process = ProcessBuilder(generateNodeInfoCommand())
+ val process = ProcessBuilder(generateNodeInfoCommand())
.directory(node.fullPath().toFile())
.redirectErrorStream(true)
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
@@ -224,6 +259,8 @@ open class Cordform : DefaultTask() {
}
}
}
+
private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log")
+
private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) }
}
diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt
index 9358a293b5..90b1364126 100644
--- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt
+++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt
@@ -3,7 +3,6 @@ package net.corda.plugins
import com.typesafe.config.*
import net.corda.cordform.CordformNode
import org.bouncycastle.asn1.x500.X500Name
-import org.bouncycastle.asn1.x500.RDN
import org.bouncycastle.asn1.x500.style.BCStyle
import org.gradle.api.Project
import java.io.File
@@ -39,6 +38,7 @@ class Node(private val project: Project) : CordformNode() {
private val releaseVersion = project.rootProject.ext("corda_release_version")
internal lateinit var nodeDir: File
+ private set
/**
* Sets whether this node will use HTTPS communication.
@@ -60,26 +60,6 @@ class Node(private val project: Project) : CordformNode() {
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
}
- /**
- * Set the HTTP web server port for this node. Will use localhost as the address.
- *
- * @param webPort The web port number for this node.
- */
- fun webPort(webPort: Int) {
- config = config.withValue("webAddress",
- ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort"))
- }
-
- /**
- * Set the HTTP web server address and port for this node.
- *
- * @param webAddress The web address for this node.
- */
- fun webAddress(webAddress: String) {
- config = config.withValue("webAddress",
- ConfigValueFactory.fromAnyRef(webAddress))
- }
-
/**
* Set the network map address for this node.
*
@@ -104,7 +84,6 @@ class Node(private val project: Project) : CordformNode() {
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
}
-
internal fun build() {
configureProperties()
installCordaJar()
@@ -118,19 +97,15 @@ class Node(private val project: Project) : CordformNode() {
}
internal fun rootDir(rootDir: Path) {
- if(name == null) {
+ if (name == null) {
project.logger.error("Node has a null name - cannot create node")
throw IllegalStateException("Node has a null name - cannot create node")
}
val dirName = try {
val o = X500Name(name).getRDNs(BCStyle.O)
- if (o.size > 0) {
- o.first().first.value.toString()
- } else {
- name
- }
- } catch(_ : IllegalArgumentException) {
+ if (o.isNotEmpty()) o.first().first.value.toString() else name
+ } catch (_ : IllegalArgumentException) {
// Can't parse as an X500 name, use the full string
name
}
@@ -192,9 +167,8 @@ class Node(private val project: Project) : CordformNode() {
/**
* Installs other cordapps to this node's cordapps directory.
*/
- private fun installCordapps() {
+ internal fun installCordapps(cordapps: Collection = getCordappList()) {
val cordappsDir = File(nodeDir, "cordapps")
- val cordapps = getCordappList()
project.copy {
it.apply {
from(cordapps)
@@ -280,7 +254,7 @@ class Node(private val project: Project) : CordformNode() {
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
} else {
val jar = maybeJar.singleFile
- assert(jar.isFile)
+ require(jar.isFile)
return jar
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 92165eede8..10cb7bb37b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Sat Nov 25 22:21:50 GMT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
diff --git a/node/build.gradle b/node/build.gradle
index 7e9adec300..07a4248964 100644
--- a/node/build.gradle
+++ b/node/build.gradle
@@ -54,8 +54,6 @@ dependencies {
compile project(':client:rpc')
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
- compile "com.google.code.findbugs:jsr305:3.0.1"
-
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
index e3a915f6b1..86124da7fc 100644
--- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
+++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt
@@ -29,6 +29,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
val notary: NotaryConfig?
val activeMQServer: ActiveMqServerConfiguration
val additionalNodeInfoPollingFrequencyMsec: Long
+ // TODO Remove as this is only used by the driver
val useHTTPS: Boolean
val p2pAddress: NetworkHostAndPort
val rpcAddress: NetworkHostAndPort?
diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle
index 4d291f727a..b496155694 100644
--- a/samples/bank-of-corda-demo/build.gradle
+++ b/samples/bank-of-corda-demo/build.gradle
@@ -48,51 +48,8 @@ dependencies {
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
- directory "./build/nodes"
- // This name "Notary" is hard-coded into BankOfCordaClientApi so if you change it here, change it there too.
- node {
- name "O=Notary Service,L=Zurich,C=CH"
- notary = [validating : true]
- p2pPort 10002
- rpcPort 10003
- cordapps = ["$project.group:finance:$corda_release_version"]
- }
- node {
- name "O=BankOfCorda,L=London,C=GB"
- extraConfig = [issuableCurrencies : ["USD"]]
- p2pPort 10005
- rpcPort 10006
- webPort 10007
- cordapps = ["$project.group:finance:$corda_release_version"]
- rpcUsers = [
- ['username' : "bankUser",
- 'password' : "test",
- 'permissions': ["StartFlow.net.corda.finance.flows.CashPaymentFlow",
- "StartFlow.net.corda.finance.flows.CashConfigDataFlow",
- "StartFlow.net.corda.finance.flows.CashExitFlow",
- "StartFlow.net.corda.finance.flows.CashIssueAndPaymentFlow",
- "StartFlow.net.corda.finance.flows.CashConfigDataFlow",
- "InvokeRpc.waitUntilNetworkReady",
- "InvokeRpc.wellKnownPartyFromX500Name",
- "InvokeRpc.notaryIdentities"]]
- ]
- }
- node {
- name "O=BigCorporation,L=New York,C=US"
- p2pPort 10008
- rpcPort 10009
- webPort 10010
- cordapps = ["$project.group:finance:$corda_release_version"]
- rpcUsers = [
- ['username' : "bigCorpUser",
- 'password' : "test",
- 'permissions': ["StartFlow.net.corda.finance.flows.CashPaymentFlow",
- "StartFlow.net.corda.finance.flows.CashConfigDataFlow",
- "InvokeRpc.waitUntilNetworkReady",
- "InvokeRpc.wellKnownPartyFromX500Name",
- "InvokeRpc.notaryIdentities"]]
- ]
- }
+ // CordformationDefinition is an experimental feature
+ definitionClass = 'net.corda.bank.BankOfCordaCordform'
}
task integrationTest(type: Test, dependsOn: []) {
@@ -119,16 +76,9 @@ publishing {
}
}
-task runIssuer(type: JavaExec) {
- classpath = sourceSets.main.runtimeClasspath
- main = 'net.corda.bank.BankOfCordaDriverKt'
- args '--role'
- args 'ISSUER'
-}
-
task runRPCCashIssue(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
- main = 'net.corda.bank.BankOfCordaDriverKt'
+ main = 'net.corda.bank.IssueCash'
args '--role'
args 'ISSUE_CASH_RPC'
args '--quantity'
@@ -139,7 +89,7 @@ task runRPCCashIssue(type: JavaExec) {
task runWebCashIssue(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
- main = 'net.corda.bank.BankOfCordaDriverKt'
+ main = 'net.corda.bank.IssueCash'
args '--role'
args 'ISSUE_CASH_WEB'
args '--quantity'
diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt
new file mode 100644
index 0000000000..6890793aba
--- /dev/null
+++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt
@@ -0,0 +1,16 @@
+package net.corda.bank
+
+import net.corda.finance.DOLLARS
+import net.corda.finance.POUNDS
+import net.corda.testing.internal.demorun.deployNodesThen
+import org.junit.Test
+
+class BankOfCordaCordformTest {
+ @Test
+ fun `run demo`() {
+ BankOfCordaCordform().deployNodesThen {
+ IssueCash.requestWebIssue(30000.POUNDS)
+ IssueCash.requestRpcIssue(20000.DOLLARS)
+ }
+ }
+}
diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt
deleted file mode 100644
index 0f37be7cbc..0000000000
--- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package net.corda.bank
-
-import net.corda.bank.api.BankOfCordaClientApi
-import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
-import net.corda.core.utilities.getOrThrow
-import net.corda.testing.BOC
-import net.corda.testing.driver.driver
-import org.junit.Test
-import kotlin.test.assertTrue
-
-class BankOfCordaHttpAPITest {
- @Test
- fun `issuer flow via Http`() {
- driver(extraCordappPackagesToScan = listOf("net.corda.finance"), isDebug = true) {
- val (bocNode) = listOf(
- startNode(providedName = BOC.name),
- startNode(providedName = BIGCORP_LEGAL_NAME)
- ).map { it.getOrThrow() }
- val bocApiAddress = startWebserver(bocNode).getOrThrow().listenAddress
- val issueRequestParams = IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, defaultNotaryIdentity.name)
- assertTrue(BankOfCordaClientApi(bocApiAddress).requestWebIssue(issueRequestParams))
- }
- }
-}
diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt
index 4b2e6c2580..229d74ed4b 100644
--- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt
+++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt
@@ -29,7 +29,7 @@ class BankOfCordaRPCClientTest {
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet() + commonPermissions)
val (nodeBankOfCorda, nodeBigCorporation) = listOf(
startNode(providedName = BOC.name, rpcUsers = listOf(bocManager)),
- startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO))
+ startNode(providedName = BIGCORP_NAME, rpcUsers = listOf(bigCorpCFO))
).map { it.getOrThrow() }
// Bank of Corda RPC Client
@@ -47,7 +47,7 @@ class BankOfCordaRPCClientTest {
// Register for Big Corporation Vault updates
val vaultUpdatesBigCorp = bigCorpProxy.vaultTrackByCriteria(Cash.State::class.java, criteria).updates
- val bigCorporation = bigCorpProxy.wellKnownPartyFromX500Name(BIGCORP_LEGAL_NAME)!!
+ val bigCorporation = bigCorpProxy.wellKnownPartyFromX500Name(BIGCORP_NAME)!!
// Kick-off actual Issuer Flow
val anonymous = true
diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt
new file mode 100644
index 0000000000..24dda81869
--- /dev/null
+++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt
@@ -0,0 +1,123 @@
+package net.corda.bank
+
+import joptsimple.OptionParser
+import net.corda.bank.api.BankOfCordaClientApi
+import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
+import net.corda.cordform.CordformContext
+import net.corda.cordform.CordformDefinition
+import net.corda.core.contracts.Amount
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.internal.VisibleForTesting
+import net.corda.core.transactions.SignedTransaction
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.node.services.Permissions.Companion.all
+import net.corda.node.services.config.NotaryConfig
+import net.corda.node.services.transactions.ValidatingNotaryService
+import net.corda.nodeapi.User
+import net.corda.testing.BOC
+import net.corda.testing.internal.demorun.*
+import java.util.*
+import kotlin.system.exitProcess
+
+val BIGCORP_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US")
+private val NOTARY_NAME = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH")
+private val BOC_RPC_PORT = 10006
+private val BOC_WEB_PORT = 10007
+
+class BankOfCordaCordform : CordformDefinition() {
+ init {
+ cordappPackages += "net.corda.finance"
+ node {
+ name(NOTARY_NAME)
+ notary(NotaryConfig(validating = true))
+ p2pPort(10002)
+ rpcPort(10003)
+ }
+ node {
+ name(CordaX500Name(organisation = "BankOfCorda", locality = "London", country = "GB"))
+ extraConfig = mapOf("issuableCurrencies" to listOf("USD"))
+ p2pPort(10005)
+ rpcPort(BOC_RPC_PORT)
+ webPort(BOC_WEB_PORT)
+ rpcUsers(User("bankUser", "test", setOf(all())))
+ }
+ node {
+ name(BIGCORP_NAME)
+ p2pPort(10008)
+ rpcPort(10009)
+ webPort(10010)
+ rpcUsers(User("bigCorpUser", "test", setOf(all())))
+ }
+ }
+
+ override fun setup(context: CordformContext) = Unit
+}
+
+object DeployNodes {
+ @JvmStatic
+ fun main(args: Array) {
+ BankOfCordaCordform().deployNodes()
+ }
+}
+
+object IssueCash {
+ @JvmStatic
+ fun main(args: Array) {
+ val parser = OptionParser()
+ val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).describedAs("[ISSUER|ISSUE_CASH_RPC|ISSUE_CASH_WEB]")
+ val quantity = parser.accepts("quantity").withOptionalArg().ofType(Long::class.java)
+ val currency = parser.accepts("currency").withOptionalArg().ofType(String::class.java).describedAs("[GBP|USD|CHF|EUR]")
+ val options = try {
+ parser.parse(*args)
+ } catch (e: Exception) {
+ println(e.message)
+ printHelp(parser)
+ exitProcess(1)
+ }
+
+ val role = options.valueOf(roleArg)!!
+ val amount = Amount(options.valueOf(quantity), Currency.getInstance(options.valueOf(currency)))
+ when (role) {
+ Role.ISSUE_CASH_RPC -> {
+ println("Requesting Cash via RPC ...")
+ val result = requestRpcIssue(amount)
+ println("Success!! Your transaction receipt is ${result.tx.id}")
+ }
+ Role.ISSUE_CASH_WEB -> {
+ println("Requesting Cash via Web ...")
+ requestWebIssue(amount)
+ println("Successfully processed Cash Issue request")
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun requestRpcIssue(amount: Amount): SignedTransaction {
+ return BankOfCordaClientApi.requestRPCIssue(NetworkHostAndPort("localhost", BOC_RPC_PORT), createParams(amount, NOTARY_NAME))
+ }
+
+ @VisibleForTesting
+ fun requestWebIssue(amount: Amount) {
+ BankOfCordaClientApi.requestWebIssue(NetworkHostAndPort("localhost", BOC_WEB_PORT), createParams(amount, NOTARY_NAME))
+ }
+
+ private fun createParams(amount: Amount, notaryName: CordaX500Name): IssueRequestParams {
+ return IssueRequestParams(amount, BIGCORP_NAME, "1", BOC.name, notaryName.copy(commonName = ValidatingNotaryService.id))
+ }
+
+ private fun printHelp(parser: OptionParser) {
+ println("""
+ Usage: bank-of-corda --role ISSUER
+ bank-of-corda --role (ISSUE_CASH_RPC|ISSUE_CASH_WEB) --quantity --currency
+
+ Please refer to the documentation in docs/build/index.html for more info.
+
+ """.trimIndent())
+ parser.printHelpOn(System.out)
+ }
+
+ enum class Role {
+ ISSUE_CASH_RPC,
+ ISSUE_CASH_WEB,
+ }
+}
diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt
deleted file mode 100644
index 00ada75a26..0000000000
--- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-package net.corda.bank
-
-import joptsimple.OptionParser
-import net.corda.bank.api.BankOfCordaClientApi
-import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
-import net.corda.core.identity.CordaX500Name
-import net.corda.core.utilities.NetworkHostAndPort
-import net.corda.finance.flows.CashConfigDataFlow
-import net.corda.finance.flows.CashExitFlow
-import net.corda.finance.flows.CashIssueAndPaymentFlow
-import net.corda.finance.flows.CashPaymentFlow
-import net.corda.node.services.Permissions.Companion.all
-import net.corda.node.services.Permissions.Companion.startFlow
-import net.corda.nodeapi.User
-import net.corda.testing.BOC
-import net.corda.testing.DUMMY_NOTARY
-import net.corda.testing.driver.driver
-import kotlin.system.exitProcess
-
-/**
- * This entry point allows for command line running of the Bank of Corda functions on nodes started by BankOfCordaDriver.kt.
- */
-fun main(args: Array) {
- BankOfCordaDriver().main(args)
-}
-
-const val BANK_USERNAME = "bankUser"
-const val BIGCORP_USERNAME = "bigCorpUser"
-
-val BIGCORP_LEGAL_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US")
-
-private class BankOfCordaDriver {
- enum class Role {
- ISSUE_CASH_RPC,
- ISSUE_CASH_WEB,
- ISSUER
- }
-
- fun main(args: Array) {
- val parser = OptionParser()
- val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).describedAs("[ISSUER|ISSUE_CASH_RPC|ISSUE_CASH_WEB]")
- val quantity = parser.accepts("quantity").withOptionalArg().ofType(Long::class.java)
- val currency = parser.accepts("currency").withOptionalArg().ofType(String::class.java).describedAs("[GBP|USD|CHF|EUR]")
- val options = try {
- parser.parse(*args)
- } catch (e: Exception) {
- println(e.message)
- printHelp(parser)
- exitProcess(1)
- }
-
- // What happens next depends on the role.
- // The ISSUER will launch a Bank of Corda node
- // The ISSUE_CASH will request some Cash from the ISSUER on behalf of Big Corporation node
- val role = options.valueOf(roleArg)!!
-
- try {
- when (role) {
- Role.ISSUER -> {
- driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset"), waitForAllNodesToFinish = true) {
- val bankUser = User(
- BANK_USERNAME,
- "test",
- permissions = setOf(
- startFlow(),
- startFlow(),
- startFlow(),
- startFlow(),
- startFlow(),
- all()
- ))
- val bankOfCorda = startNode(
- providedName = BOC.name,
- rpcUsers = listOf(bankUser))
- val bigCorpUser = User(BIGCORP_USERNAME, "test",
- permissions = setOf(
- startFlow(),
- startFlow(),
- all()))
- startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser))
- startWebserver(bankOfCorda.get())
- }
- }
- else -> {
- val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME,
- "1", BOC.name, DUMMY_NOTARY.name)
- when(role) {
- Role.ISSUE_CASH_RPC -> {
- println("Requesting Cash via RPC ...")
- val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10004)).requestRPCIssue(requestParams)
- println("Success!! You transaction receipt is ${result.tx.id}")
- }
- Role.ISSUE_CASH_WEB -> {
- println("Requesting Cash via Web ...")
- val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10005)).requestWebIssue(requestParams)
- if (result)
- println("Successfully processed Cash Issue request")
- }
- else -> {
- throw IllegalArgumentException("Unrecognized role: " + role)
- }
- }
- }
- }
- } catch (e: Exception) {
- println("Exception occurred: $e \n ${e.printStackTrace()}")
- exitProcess(1)
- }
- }
-
- fun printHelp(parser: OptionParser) {
- println("""
- Usage: bank-of-corda --role ISSUER
- bank-of-corda --role (ISSUE_CASH_RPC|ISSUE_CASH_WEB) --quantity --currency
-
- Please refer to the documentation in docs/build/index.html for more info.
-
- """.trimIndent())
- parser.printHelpOn(System.out)
- }
-}
-
diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt
index a50e99b727..ac0a136f7c 100644
--- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt
+++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaClientApi.kt
@@ -15,15 +15,14 @@ import java.util.*
/**
* Interface for communicating with Bank of Corda node
*/
-class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
- private val apiRoot = "api/bank"
+object BankOfCordaClientApi {
/**
* HTTP API
*/
// TODO: security controls required
- fun requestWebIssue(params: IssueRequestParams): Boolean {
- val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot)
- return api.postJson("issue-asset-request", params)
+ fun requestWebIssue(webAddress: NetworkHostAndPort, params: IssueRequestParams) {
+ val api = HttpApi.fromHostAndPort(webAddress, "api/bank")
+ api.postJson("issue-asset-request", params)
}
/**
@@ -31,8 +30,8 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
*
* @return a pair of the issuing and payment transactions.
*/
- fun requestRPCIssue(params: IssueRequestParams): SignedTransaction {
- val client = CordaRPCClient(hostAndPort)
+ fun requestRPCIssue(rpcAddress: NetworkHostAndPort, params: IssueRequestParams): SignedTransaction {
+ val client = CordaRPCClient(rpcAddress)
// TODO: privileged security controls required
client.start("bankUser", "test").use { connection ->
val rpc = connection.proxy
@@ -44,11 +43,10 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
val notaryLegalIdentity = rpc.notaryIdentities().firstOrNull { it.name == params.notaryName } ?:
throw IllegalStateException("Couldn't locate notary ${params.notaryName} in NetworkMapCache")
- val amount = Amount(params.amount, Currency.getInstance(params.currency))
val anonymous = true
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
- return rpc.startFlow(::CashIssueAndPaymentFlow, amount, issuerBankPartyRef, issueToParty, anonymous, notaryLegalIdentity)
+ return rpc.startFlow(::CashIssueAndPaymentFlow, params.amount, issuerBankPartyRef, issueToParty, anonymous, notaryLegalIdentity)
.returnValue.getOrThrow().stx
}
}
diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt
index 30614d0cdf..25daa53476 100644
--- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt
+++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt
@@ -16,11 +16,14 @@ import javax.ws.rs.core.Response
// API is accessible from /api/bank. All paths specified below are relative to it.
@Path("bank")
-class BankOfCordaWebApi(val rpc: CordaRPCOps) {
- data class IssueRequestParams(val amount: Long, val currency: String,
- val issueToPartyName: CordaX500Name, val issuerBankPartyRef: String,
- val issuerBankName: CordaX500Name,
- val notaryName: CordaX500Name)
+class BankOfCordaWebApi(private val rpc: CordaRPCOps) {
+ data class IssueRequestParams(
+ val amount: Amount,
+ val issueToPartyName: CordaX500Name,
+ val issuerBankPartyRef: String,
+ val issuerBankName: CordaX500Name,
+ val notaryName: CordaX500Name
+ )
private companion object {
private val logger = contextLogger()
@@ -47,14 +50,13 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
val notaryParty = rpc.notaryIdentities().firstOrNull { it.name == params.notaryName }
?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate notary ${params.notaryName} in network map").build()
- val amount = Amount(params.amount, Currency.getInstance(params.currency))
val anonymous = true
val issuerBankPartyRef = OpaqueBytes.of(params.issuerBankPartyRef.toByte())
// invoke client side of Issuer Flow: IssuanceRequester
// The line below blocks and waits for the future to resolve.
return try {
- rpc.startFlow(::CashIssueAndPaymentFlow, amount, issuerBankPartyRef, issueToParty, anonymous, notaryParty).returnValue.getOrThrow()
+ rpc.startFlow(::CashIssueAndPaymentFlow, params.amount, issuerBankPartyRef, issueToParty, anonymous, notaryParty).returnValue.getOrThrow()
logger.info("Issue and payment request completed successfully: $params")
Response.status(Response.Status.CREATED).build()
} catch (e: Exception) {
diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt
index d924e41287..f47c16a623 100644
--- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt
+++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt
@@ -117,7 +117,7 @@ class IRSDemoTest {
log.info("Running trade against ${nodeApi.root}")
val fileContents = loadResourceFile("net/corda/irs/web/simulation/example-irs-trade.json")
val tradeFile = fileContents.replace("tradeXXX", "trade1").replace("oracleXXX", oracle.name.toString())
- assertThat(nodeApi.postJson("deals", tradeFile)).isTrue()
+ nodeApi.postJson("deals", tradeFile)
}
private fun runUploadRates(nodeApi: HttpApi) {
diff --git a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt
index 5c434cdb19..82e81de047 100644
--- a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt
+++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt
@@ -11,10 +11,10 @@ import org.apache.commons.io.IOUtils
class IRSDemoClientApi(private val hostAndPort: NetworkHostAndPort) {
private val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot)
- fun runTrade(tradeId: String, oracleName: CordaX500Name): Boolean {
+ fun runTrade(tradeId: String, oracleName: CordaX500Name) {
val fileContents = IOUtils.toString(javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example-irs-trade.json"), Charsets.UTF_8.name())
val tradeFile = fileContents.replace("tradeXXX", tradeId).replace("oracleXXX", oracleName.toString())
- return api.postJson("deals", tradeFile)
+ api.postJson("deals", tradeFile)
}
fun runDateChange(newDate: String): Boolean {
diff --git a/samples/notary-demo/build.gradle b/samples/notary-demo/build.gradle
index 4d3f97f065..84ba21d268 100644
--- a/samples/notary-demo/build.gradle
+++ b/samples/notary-demo/build.gradle
@@ -49,22 +49,18 @@ publishing {
task deployNodes(dependsOn: ['deployNodesSingle', 'deployNodesRaft', 'deployNodesBFT', 'deployNodesCustom'])
task deployNodesSingle(type: Cordform, dependsOn: 'jar') {
- directory "./build/nodes/nodesSingle"
definitionClass = 'net.corda.notarydemo.SingleNotaryCordform'
}
task deployNodesCustom(type: Cordform, dependsOn: 'jar') {
- directory "./build/nodes/nodesCustom"
definitionClass = 'net.corda.notarydemo.CustomNotaryCordform'
}
task deployNodesRaft(type: Cordform, dependsOn: 'jar') {
- directory "./build/nodes/nodesRaft"
definitionClass = 'net.corda.notarydemo.RaftNotaryCordform'
}
task deployNodesBFT(type: Cordform, dependsOn: 'jar') {
- directory "./build/nodes/nodesBFT"
definitionClass = 'net.corda.notarydemo.BFTNotaryCordform'
}
diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt
index e0919a43ab..99e158b723 100644
--- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt
+++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt
@@ -4,7 +4,6 @@ import net.corda.cordform.CordformContext
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
import net.corda.core.identity.CordaX500Name
-import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
@@ -14,18 +13,20 @@ import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.internal.demorun.*
+import java.nio.file.Paths
-fun main(args: Array) = BFTNotaryCordform().runNodes()
+fun main(args: Array) = BFTNotaryCordform().deployNodes()
private val clusterSize = 4 // Minimum size that tolerates a faulty replica.
private val notaryNames = createNotaryNames(clusterSize)
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
// NOT use this as a design to copy.
-class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
+class BFTNotaryCordform : CordformDefinition() {
private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
init {
+ nodesDirectory = Paths.get("build", "nodes", "nodesBFT")
node {
name(ALICE.name)
p2pPort(10002)
diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt
index 3cd43d7a0e..8117ef9a60 100644
--- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt
+++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt
@@ -2,17 +2,18 @@ package net.corda.notarydemo
import net.corda.cordform.CordformContext
import net.corda.cordform.CordformDefinition
-import net.corda.core.internal.div
import net.corda.node.services.config.NotaryConfig
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.internal.demorun.*
+import java.nio.file.Paths
-fun main(args: Array) = CustomNotaryCordform().runNodes()
+fun main(args: Array) = CustomNotaryCordform().deployNodes()
-class CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
+class CustomNotaryCordform : CordformDefinition() {
init {
+ nodesDirectory = Paths.get("build", "nodes", "nodesCustom")
node {
name(ALICE.name)
p2pPort(10002)
diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt
index ff128c6edf..e6c6f54595 100644
--- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt
+++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt
@@ -13,8 +13,9 @@ import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.internal.demorun.*
+import java.nio.file.Paths
-fun main(args: Array) = RaftNotaryCordform().runNodes()
+fun main(args: Array) = RaftNotaryCordform().deployNodes()
internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") }
@@ -22,10 +23,11 @@ private val notaryNames = createNotaryNames(3)
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
// NOT use this as a design to copy.
-class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
+class RaftNotaryCordform : CordformDefinition() {
private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")
init {
+ nodesDirectory = Paths.get("build", "nodes", "nodesRaft")
node {
name(ALICE.name)
p2pPort(10002)
diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt
index 97168834c0..23381b72d1 100644
--- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt
+++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt
@@ -2,29 +2,24 @@ package net.corda.notarydemo
import net.corda.cordform.CordformContext
import net.corda.cordform.CordformDefinition
-import net.corda.core.internal.div
import net.corda.node.services.Permissions.Companion.all
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.User
-import net.corda.notarydemo.flows.DummyIssueAndMove
-import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
-import net.corda.testing.internal.demorun.name
-import net.corda.testing.internal.demorun.node
-import net.corda.testing.internal.demorun.notary
-import net.corda.testing.internal.demorun.rpcUsers
-import net.corda.testing.internal.demorun.runNodes
+import net.corda.testing.internal.demorun.*
+import java.nio.file.Paths
-fun main(args: Array) = SingleNotaryCordform().runNodes()
+fun main(args: Array) = SingleNotaryCordform().deployNodes()
val notaryDemoUser = User("demou", "demop", setOf(all()))
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
// NOT use this as a design to copy.
-class SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
+class SingleNotaryCordform : CordformDefinition() {
init {
+ nodesDirectory = Paths.get("build", "nodes", "nodesSingle")
node {
name(ALICE.name)
p2pPort(10002)
diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt
index d09d8f5a8c..2b74779a1c 100644
--- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt
+++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt
@@ -41,7 +41,7 @@ class SimmValuationTest {
assertThat(createTradeBetween(nodeAApi, nodeBParty, testTradeId)).isTrue()
assertTradeExists(nodeBApi, nodeAParty, testTradeId)
assertTradeExists(nodeAApi, nodeBParty, testTradeId)
- assertThat(runValuationsBetween(nodeAApi, nodeBParty)).isTrue()
+ runValuationsBetween(nodeAApi, nodeBParty)
assertValuationExists(nodeBApi, nodeAParty)
assertValuationExists(nodeAApi, nodeBParty)
}
@@ -66,8 +66,8 @@ class SimmValuationTest {
assertThat(trades).filteredOn { it.id == tradeId }.isNotEmpty()
}
- private fun runValuationsBetween(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty): Boolean {
- return partyApi.postJson("${counterparty.id}/portfolio/valuations/calculate", PortfolioApi.ValuationCreationParams(valuationDate))
+ private fun runValuationsBetween(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty) {
+ partyApi.postJson("${counterparty.id}/portfolio/valuations/calculate", PortfolioApi.ValuationCreationParams(valuationDate))
}
private fun assertValuationExists(partyApi: HttpApi, counterparty: PortfolioApi.ApiParty) {
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt
index d062cc9163..eab1ebd159 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt
@@ -161,12 +161,6 @@ interface DriverDSLExposedInterface : CordformContext {
*/
fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters)
- fun startNodes(
- nodes: List,
- startInSameProcess: Boolean? = null,
- maximumHeapSize: String = "200m"
- ): List>
-
/** Call [startWebserver] with a default maximumHeapSize. */
fun startWebserver(handle: NodeHandle): CordaFuture = startWebserver(handle, "200m")
@@ -672,22 +666,21 @@ class DriverDSL(
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize)
}
- override fun startNodes(nodes: List, startInSameProcess: Boolean?, maximumHeapSize: String): List> {
- return nodes.map { node ->
- portAllocation.nextHostAndPort() // rpcAddress
- val webAddress = portAllocation.nextHostAndPort()
- val name = CordaX500Name.parse(node.name)
- val rpcUsers = node.rpcUsers
- val notary = if (node.notary != null) mapOf("notary" to node.notary) else emptyMap()
- val config = ConfigHelper.loadConfig(
- baseDirectory = baseDirectory(name),
- allowMissingConfig = true,
- configOverrides = node.config + notary + mapOf(
- "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers
- )
- )
- startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize)
- }
+ internal fun startCordformNode(cordform: CordformNode): CordaFuture {
+ val name = CordaX500Name.parse(cordform.name)
+ // TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal
+ val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap()
+ val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort()
+ val notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap()
+ val rpcUsers = cordform.rpcUsers
+ val config = ConfigHelper.loadConfig(
+ baseDirectory = baseDirectory(name),
+ allowMissingConfig = true,
+ configOverrides = cordform.config + rpcAddress + notary + mapOf(
+ "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers
+ )
+ )
+ return startNodeInternal(config, webAddress, null, "200m")
}
private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle {
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt
index 188b6a30de..5155a31850 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt
@@ -4,20 +4,63 @@ package net.corda.testing.internal.demorun
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
+import net.corda.core.internal.concurrent.flatMap
+import net.corda.core.internal.concurrent.transpose
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.getOrThrow
+import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
fun CordformDefinition.clean() {
- System.err.println("Deleting: $driverDirectory")
- driverDirectory.toFile().deleteRecursively()
+ System.err.println("Deleting: $nodesDirectory")
+ nodesDirectory.toFile().deleteRecursively()
}
/**
- * Creates and starts all nodes required for the demo.
+ * Deploy the nodes specified in the given [CordformDefinition]. This will block until all the nodes and webservers
+ * have terminated.
*/
-fun CordformDefinition.runNodes() {
- driver(isDebug = true, driverDirectory = driverDirectory, portAllocation = PortAllocation.Incremental(10001), waitForAllNodesToFinish = true) {
+fun CordformDefinition.deployNodes() {
+ runNodes(waitForAllNodesToFinish = true) { }
+}
+
+/**
+ * Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes
+ * and webservers are up. After execution all these processes will be terminated.
+ */
+fun CordformDefinition.deployNodesThen(block: () -> Unit) {
+ runNodes(waitForAllNodesToFinish = false, block = block)
+}
+
+private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) {
+ val nodes = nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } }
+ val maxPort = nodes
+ .flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) }
+ .mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } }
+ .max()!!
+ driver(
+ isDebug = true,
+ driverDirectory = nodesDirectory,
+ extraCordappPackagesToScan = cordappPackages,
+ // Notaries are manually specified in Cordform so we don't want the driver automatically starting any
+ notarySpecs = emptyList(),
+ // Start from after the largest port used to prevent port clash
+ portAllocation = PortAllocation.Incremental(maxPort + 1),
+ waitForAllNodesToFinish = waitForAllNodesToFinish
+ ) {
setup(this)
- startNodes(nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } })
+ this as DriverDSL // startCordformNode is an internal API
+ nodes.map {
+ val startedNode = startCordformNode(it)
+ if (it.webAddress != null) {
+ // Start a webserver if an address for it was specified
+ startedNode.flatMap { startWebserver(it) }
+ } else {
+ startedNode
+ }
+ }.transpose().getOrThrow() // Only proceed once everything is up and running
+ println("All nodes and webservers are ready...")
+ block()
}
}
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt
index 327f85193b..cf1af9b9ab 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt
+++ b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt
@@ -6,6 +6,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import org.slf4j.LoggerFactory
+import java.io.IOException
import java.net.URL
import java.util.concurrent.TimeUnit
@@ -14,11 +15,13 @@ import java.util.concurrent.TimeUnit
*/
object HttpUtils {
private val logger = LoggerFactory.getLogger(javaClass)
+
private val client by lazy {
OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS).build()
}
+
val defaultMapper: ObjectMapper by lazy {
net.corda.client.jackson.JacksonSupport.createNonRpcMapper()
}
@@ -28,9 +31,9 @@ object HttpUtils {
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").put(body).build())
}
- fun postJson(url: URL, data: String): Boolean {
+ fun postJson(url: URL, data: String) {
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
- return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
+ makeExceptionalRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
}
fun postPlain(url: URL, data: String): Boolean {
@@ -44,6 +47,14 @@ object HttpUtils {
return mapper.readValue(parameterisedUrl, T::class.java)
}
+ // TODO Move everything to use this instead of makeRequest
+ private fun makeExceptionalRequest(request: Request) {
+ val response = client.newCall(request).execute()
+ if (!response.isSuccessful) {
+ throw IOException("${request.method()} to ${request.url()} returned a ${response.code()}: ${response.body().string()}")
+ }
+ }
+
private fun makeRequest(request: Request): Boolean {
val response = client.newCall(request).execute()