mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Overhaul of the Bank of Corda demo to fix two problems it had:
1. The runRPCCashIssue and runWebCashIssue gradle tasks didn't work because they were using the wrong ports 2. Notary lookup was failing because the lookup name didn't include the correct CN for the notary name (this slipped through when reverting the network parameters) The ports change occurred in #1922 which was attempting the fix the runIssuer gradle task. This is actually a misleading and redundant task as all it does is start up the nodes, which is what the documented deployNodes already does. The ports runIssuer allocated to the nodes were different to the ones specified in deployNodes. To make sure we have integration tests which closely match deployNodes, the BoC demo has been updated to make use of CordformDefinition. This keeps the node definitions in one place, removing the need to have disparate files in sync. runIssuer has been removed.
This commit is contained in:
parent
b45d9e957b
commit
5c53a91785
@ -1,15 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="BankOfCordaDriverKt - Issue Web" type="JetRunConfigurationType" factoryName="Kotlin">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.bank.BankOfCordaDriverKt" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--role ISSUE_CASH_WEB --quantity 100 --currency USD" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<module name="bank-of-corda-demo_main" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -1,15 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="BankOfCordaDriverKt - Run Stack" type="JetRunConfigurationType" factoryName="Kotlin">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.bank.BankOfCordaDriverKt" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--role ISSUER" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<module name="bank-of-corda-demo_main" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -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'
|
||||
|
@ -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
|
||||
typesafeConfigVersion=1.3.1
|
||||
jsr305Version=3.0.2
|
||||
|
@ -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}"
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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<Consumer<? super CordformNode>> nodeConfigurers = new ArrayList<>();
|
||||
private Path nodesDirectory = Paths.get("build", "nodes");
|
||||
private final List<Consumer<CordformNode>> nodeConfigurers = new ArrayList<>();
|
||||
private final List<String> 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<Consumer<CordformNode>> getNodeConfigurers() {
|
||||
return nodeConfigurers;
|
||||
}
|
||||
|
||||
public void addNode(Consumer<CordformNode> configurer) {
|
||||
nodeConfigurers.add(configurer);
|
||||
}
|
||||
|
||||
public List<String> 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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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<Node>()
|
||||
|
||||
/**
|
||||
@ -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<File> {
|
||||
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, Process> {
|
||||
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) }
|
||||
}
|
||||
|
@ -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<String>("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<File> = 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
|
||||
}
|
||||
}
|
||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -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}"
|
||||
|
@ -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?
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ class BankOfCordaRPCClientTest {
|
||||
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet<String>() + 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
|
||||
|
@ -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<String>) {
|
||||
BankOfCordaCordform().deployNodes()
|
||||
}
|
||||
}
|
||||
|
||||
object IssueCash {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
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<Currency>): SignedTransaction {
|
||||
return BankOfCordaClientApi.requestRPCIssue(NetworkHostAndPort("localhost", BOC_RPC_PORT), createParams(amount, NOTARY_NAME))
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun requestWebIssue(amount: Amount<Currency>) {
|
||||
BankOfCordaClientApi.requestWebIssue(NetworkHostAndPort("localhost", BOC_WEB_PORT), createParams(amount, NOTARY_NAME))
|
||||
}
|
||||
|
||||
private fun createParams(amount: Amount<Currency>, 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 <quantity> --currency <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,
|
||||
}
|
||||
}
|
@ -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<String>) {
|
||||
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<String>) {
|
||||
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<CashPaymentFlow>(),
|
||||
startFlow<CashConfigDataFlow>(),
|
||||
startFlow<CashExitFlow>(),
|
||||
startFlow<CashIssueAndPaymentFlow>(),
|
||||
startFlow<CashConfigDataFlow>(),
|
||||
all()
|
||||
))
|
||||
val bankOfCorda = startNode(
|
||||
providedName = BOC.name,
|
||||
rpcUsers = listOf(bankUser))
|
||||
val bigCorpUser = User(BIGCORP_USERNAME, "test",
|
||||
permissions = setOf(
|
||||
startFlow<CashPaymentFlow>(),
|
||||
startFlow<CashConfigDataFlow>(),
|
||||
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 <quantity> --currency <currency>
|
||||
|
||||
Please refer to the documentation in docs/build/index.html for more info.
|
||||
|
||||
""".trimIndent())
|
||||
parser.printHelpOn(System.out)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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<Currency>,
|
||||
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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -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<String>) = BFTNotaryCordform().runNodes()
|
||||
fun main(args: Array<String>) = 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)
|
||||
|
@ -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<String>) = CustomNotaryCordform().runNodes()
|
||||
fun main(args: Array<String>) = 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)
|
||||
|
@ -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<String>) = RaftNotaryCordform().runNodes()
|
||||
fun main(args: Array<String>) = 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)
|
||||
|
@ -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<String>) = SingleNotaryCordform().runNodes()
|
||||
fun main(args: Array<String>) = 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)
|
||||
|
@ -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) {
|
||||
|
@ -161,12 +161,6 @@ interface DriverDSLExposedInterface : CordformContext {
|
||||
*/
|
||||
fun startNode(parameters: NodeParameters): CordaFuture<NodeHandle> = startNode(defaultParameters = parameters)
|
||||
|
||||
fun startNodes(
|
||||
nodes: List<CordformNode>,
|
||||
startInSameProcess: Boolean? = null,
|
||||
maximumHeapSize: String = "200m"
|
||||
): List<CordaFuture<NodeHandle>>
|
||||
|
||||
/** Call [startWebserver] with a default maximumHeapSize. */
|
||||
fun startWebserver(handle: NodeHandle): CordaFuture<WebserverHandle> = startWebserver(handle, "200m")
|
||||
|
||||
@ -672,22 +666,21 @@ class DriverDSL(
|
||||
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize)
|
||||
}
|
||||
|
||||
override fun startNodes(nodes: List<CordformNode>, startInSameProcess: Boolean?, maximumHeapSize: String): List<CordaFuture<NodeHandle>> {
|
||||
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<NodeHandle> {
|
||||
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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user