Fix build.gradle so that publishToMavenLocal works

This commit is contained in:
Chris Barratt 2023-01-05 14:15:40 +00:00
parent 4b12cfca95
commit 2dffb38587
24 changed files with 1472 additions and 711 deletions

View File

@ -3,11 +3,6 @@ import static org.gradle.api.JavaVersion.VERSION_11
plugins {
id 'org.jetbrains.kotlin.jvm'
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend the existing build environment so that CPB and CPK files can be built.
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
// required by Corda.
id 'net.corda.plugins.cordapp-cpb2'
id 'net.corda.cordapp.cordapp-configuration'
id 'org.jetbrains.kotlin.plugin.jpa'
@ -18,93 +13,32 @@ plugins {
id 'csde'
}
group 'com.r3.hellocorda'
version '1.0-SNAPSHOT'
allprojects {
group 'com.r3.hellocorda'
version '1.0-SNAPSHOT'
def javaVersion = VERSION_11
// The CordApp section.
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the components
// subproject.
// This is required by the corda plugins to build the CorDapp.
cordapp {
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
// Enforced versioning has not been implemented yet, so enter a dummy value for now.
// The platform version will correspond to and be roughly equivalent to the Corda API version.
targetPlatformVersion platformVersion.toInteger()
minimumPlatformVersion platformVersion.toInteger()
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
name "ModuleNameHere"
versionId 1
vendor "VendorNameHere"
}
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
tasks.withType(JavaCompile) {
def javaVersion = VERSION_11
// Declare the set of Java compiler options we need to build a CorDapp.
tasks.withType(JavaCompile) {
// -parameters - Needed for reflection and serialization to work correctly.
options.compilerArgs += [
"-parameters"
]
}
}
repositories {
repositories {
// All dependencies are held in Maven Central
mavenCentral()
maven {
url = "$artifactoryContextUrl/"
}
}
}
// Declare dependencies for the modules we will use.
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are then built upon.
dependencies {
// Declare a "platform" to use the correct set of dependency versions for the version that the
// Corda API specifies.
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
// If using transistive dependencies this will provide most of the Corda-API:
// cordaProvided 'net.corda:corda-application'
// Alternatively we can explicitly specify all our Corda-API dependencies:
cordaProvided 'net.corda:corda-base'
cordaProvided 'net.corda:corda-application'
cordaProvided 'net.corda:corda-crypto'
cordaProvided 'net.corda:corda-membership'
// cordaProvided 'net.corda:corda-persistence'
cordaProvided 'net.corda:corda-serialization'
// Not yet fully implemented:
// cordaProvided 'net.corda:corda-ledger'
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
// Dependencies Required By Test Tooling
testImplementation "net.corda:corda-simulator-api:$combinedWorkerVersion"
testRuntimeOnly "net.corda:corda-simulator-runtime:$combinedWorkerVersion"
// 3rd party libraries
// Required
testImplementation "org.slf4j:slf4j-simple:2.0.0"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
}
test {
tasks.withType(Test).configureEach {
useJUnitPlatform()
}
}
publishing {
@ -116,3 +50,4 @@ publishing {
}
}
}

View File

@ -1,6 +1,7 @@
jacksonVersion = 2.13.3
jacksonVersion = 2.13.4
unirestVersion=3.13.10
cordaApiVersion=5.0.0.505-Beta1.0-HC00
cordaApiVersion=5.0.0.523-Fox1.0-RC03
# R3 internal repository
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0-RC03

View File

@ -1,4 +1,24 @@
import com.r3.csde.CsdeRpcInterface
// Note, IntelliJ does not recognise the imported Java Classes, hence they are
// highlighted in Red. However, they are recognised in the gradle compilation.
// todo: look at the declaration of the script variables, can they be combined with the declaration of the Project Context
// todo: investigate adding corda-cli to the class path then executing it directly - might not work as gradle has to set up the jar file, so its not their when you start.
// Todo: write a test flow runner helper function??
// todo: rename deployCPIsHelper
// todo: add proper logging, rather than reading Stdout
// todo: add test corda running/live task
// todo: add a test to check docker is running and display error if not + halt start corda
// todo: add a clean corda task.
import com.r3.csde.CordaLifeCycleHelper
import com.r3.csde.ProjectContext
import com.r3.csde.DeployCPIsHelper
import com.r3.csde.BuildCPIsHelper
import com.r3.csde.ProjectUtils
import com.r3.csde.CordaStatusQueries
import com.r3.csde.CreateAndRegisterVNodesHelper
plugins {
id 'java-library'
@ -18,48 +38,114 @@ configurations {
canBeResolved = true
}
notaryServerCPB {
canBeConsumed = false
canBeResolved = true
}
}
// Dependencies for supporting tools
dependencies {
combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
myPostgresJDBC 'org.postgresql:postgresql:42.4.1'
notaryServerCPB("com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-server:$cordaNotaryPluginsVersion") {
artifact {
classifier = 'package'
extension = 'cpb'
}
}
implementation "org.codehaus.groovy:groovy-json:3.0.9"
}
// task groupings
def cordaGroup = 'csde-corda' // corda lifecycle tasks
def cordappGroup = 'csde-cordapp' // tasks to build and deploy corDapps
def queriesGroup = 'csde-queries' // tasks which query corda status
def supportingGroup = 'supporting' // tasks which should be hidden from the csde user
def pluginGroupName = "CSDE"
def pluginImplGroupName = "other"
def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5"
def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli"
def cordaBinDir = System.getenv("CSDE_CORDA_BIN") ?: System.getProperty('user.home') + "/.corda/corda5"
def cordaCliBinDir = System.getenv("CSDE_CORDA_CLI") ?:System.getProperty('user.home') + "/.corda/cli"
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
def cordaNotaryServerDir = cordaBinDir + "/notaryserver"
def signingCertAlias="gradle-plugin-default-key"
// You will receive an error if this is not a autotyped object.
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem".
// Get error if this is not a autotyped object
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
def signingCertFName = rootDir.toString() + "/config/gradle-plugin-default-key.pem"
def keystoreAlias = "my-signing-key"
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat"
// todo: can we rely on the build directory always being /workflow/build? aslo, is the
// workflow directory the correct place to build the cpb to. shoudl it be the main build directory.
def workflowBuildDir = rootDir.toString() + "/workflows/build"
// Need to read things from cordapp plugin
def cpiName = 'cpi name'
// todo: Need to read things from cordapp plugin - the cordapp names will be changed by the user
def appCpiName = 'cpi name'
def notaryCpiName = 'CSDE Notary Server CPI'
def csdeHelper = new CsdeRpcInterface(project,
// todo: there should be a better way to set up these project context variables.
def projectContext = new ProjectContext(project,
cordaClusterURL.toString(),
cordaRpcUser,
cordaRpcPasswd,
devEnvWorkspace,
// todo: why is this not obtained in the groovy def's abouve - its inconsistent.
new String("${System.getProperty("java.home")}/bin"),
dbContainerName,
cordaJDBCDir,
combiWorkerPidCacheFile
combiWorkerPidCacheFile,
signingCertAlias,
signingCertFName,
keystoreAlias,
keystoreFName,
keystoreCertFName,
appCpiName,
notaryCpiName,
devEnvWorkspace,
cordaCliBinDir,
cordaNotaryServerDir,
workflowBuildDir,
cordaNotaryPluginsVersion
)
def utils = new ProjectUtils()
// Initiate workspace folder
tasks.register('projInit') {
group = supportingGroup
doLast {
mkdir devEnvWorkspace
}
}
// CordaLifeCycle tasks
def cordaLifeCycle = new CordaLifeCycleHelper(projectContext)
tasks.register("startCorda") {
group = cordaGroup
dependsOn('getDevCordaLite', 'getPostgresJDBC')
doLast {
mkdir devEnvWorkspace
cordaLifeCycle.startCorda()
}
}
tasks.register("stopCorda") {
group = cordaGroup
doLast {
cordaLifeCycle.stopCorda()
}
}
tasks.register("getPostgresJDBC") {
group = pluginImplGroupName
group = supportingGroup
doLast {
copy {
from configurations.myPostgresJDBC
@ -68,184 +154,100 @@ tasks.register("getPostgresJDBC") {
}
}
tasks.register('projInit') {
group = pluginImplGroupName
doLast {
mkdir devEnvWorkspace
}
}
tasks.register("createGroupPolicy") {
group = pluginImplGroupName
dependsOn('projInit')
doLast {
def groupPolicyFName = new String("${devEnvWorkspace}/GroupPolicy.json")
def devnetFName = new String("$rootDir/config/dev-net.json")
File groupPolicyFile = new File(groupPolicyFName)
File devnetFile = new File(devnetFName)
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
def configX500Ids = csdeHelper.getConfigX500Ids()
println("createGroupPolicy: Creating a GroupPolicy")
javaexec {
classpath = files("$cordaCliBinDir/corda-cli.jar")
jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"]
standardOutput = new FileOutputStream(groupPolicyFName)
LinkedList<String> myArgs = new LinkedList<String>()
myArgs.add("mgm")
myArgs.add("groupPolicy")
configX500Ids.forEach {
myArgs.add("--name")
myArgs.add("$it")
}
myArgs.add("--endpoint-protocol=1")
myArgs.add("--endpoint=http://localhost:1080")
args = myArgs
}
} else {
println("createPolicyTask: everything up to date; nothing to do.")
}
}
}
tasks.register("getDevCordaLite", Copy) {
group = pluginImplGroupName
group = supportingGroup
from configurations.combinedWorker
into cordaBinDir
}
tasks.register('createKeystore') {
group = pluginImplGroupName
dependsOn('projInit')
doLast {
File keystoreFile = new File(keystoreFName)
if(!keystoreFile.exists()) {
println('createKeystore: Create a keystore')
exec {
commandLine "${System.getProperty("java.home")}/bin/keytool", "-genkeypair",
"-alias", keystoreAlias,
"-keystore", keystoreFName,
"-storepass", "keystore password",
"-dname", "CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB",
"-keyalg", "RSA",
"-storetype", "pkcs12",
"-validity", "4000"
}
// Add the default signing key to the keystore.
exec {
commandLine "${System.getProperty("java.home")}/bin/keytool", "-importcert",
"-keystore", keystoreFName,
"-storepass", "keystore password",
"-noprompt",
"-alias", signingCertAlias,
"-file", signingCertFName
}
// keytool -exportcert -rfc -alias "signing key 1" -keystore signingkeys.pfx -storepass "keystore password" -file signingkey1.pem
exec {
commandLine "${System.getProperty("java.home")}/bin/keytool",
"-exportcert", "-rfc", "-alias", keystoreAlias,
"-keystore", keystoreFName,
"-storepass", "keystore password",
"-file", keystoreCertFName
}
}
else {
println('createKeystore: keystore already created; nothing to do.')
}
}
}
// Corda status queries
def cordaStatusQueries = new CordaStatusQueries(projectContext)
tasks.register('buildCPI') {
group = pluginGroupName
dependsOn('build', 'createGroupPolicy', 'createKeystore')
doLast{
def cpiFile= buildDir.toString() + "/" + project.archivesBaseName + "-" + project.version + ".cpi"
delete { delete cpiFile }
File srcDir
srcDir = file('build/libs')
// Create a file collection using a closure.
def collection = layout.files { srcDir.listFiles() }
def cpbs = collection.filter { it.getName().endsWith(".cpb") }
javaexec {
classpath = files("$cordaCliBinDir/corda-cli.jar")
jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"]
args = ['package', 'create-cpi',
'--cpb', cpbs.singleFile.absolutePath,
'--group-policy', "${devEnvWorkspace}/GroupPolicy.json",
'--cpi-name', cpiName,
'--cpi-version', project.version,
'--file', cpiFile,
'--keystore', "${devEnvWorkspace}/signingkeys.pfx",
'--storepass', 'keystore password',
'--key', 'my-signing-key' ]
}
}
}
tasks.register("deployCPI") {
group = pluginImplGroupName
dependsOn('buildCPI')
doLast {
csdeHelper.uploadCertificate(signingCertAlias, signingCertFName)
csdeHelper.uploadCertificate(keystoreAlias, keystoreCertFName)
csdeHelper.deployCPI("${buildDir}/${project.archivesBaseName}-${project.version}.cpi", cpiName, project.version)
}
}
tasks.register("createAndRegVNodes") {
group = pluginImplGroupName
dependsOn('deployCPI')
doLast {
csdeHelper.createAndRegVNodes()
}
}
tasks.register('listVNodes') {
group = pluginGroupName
group = queriesGroup
doLast {
csdeHelper.listVNodes()
cordaStatusQueries.listVNodes()
}
}
tasks.register('listCPIs') {
group = pluginImplGroupName
group = queriesGroup
doLast {
csdeHelper.listCPIs()
cordaStatusQueries.listCPIs()
}
}
// Empty task, this acts as the user entry point task.
tasks.register('deployCordapp') {
group = pluginGroupName
dependsOn("createAndRegVNodes")
}
// Build CPI tasks
tasks.register("startCorda") {
group = pluginGroupName
dependsOn('getDevCordaLite', 'getPostgresJDBC')
def buildCPIsHelper = new BuildCPIsHelper(projectContext)
tasks.register("1-createGroupPolicy") {
group = cordappGroup
dependsOn('projInit')
doLast {
mkdir devEnvWorkspace
csdeHelper.startCorda()
buildCPIsHelper.createGroupPolicy()
}
}
tasks.register("stopCorda") {
group = pluginGroupName
tasks.register("getNotaryServerCPB", Copy) {
group = supportingGroup
from configurations.notaryServerCPB
into cordaNotaryServerDir
}
tasks.register('2-createKeystore') {
group = cordappGroup
dependsOn('projInit')
doLast {
csdeHelper.stopCorda()
buildCPIsHelper.createKeyStore()
}
}
tasks.register('3-buildCPIs') {
group = cordappGroup
def dependsOnTasks = subprojects.collect {it.tasks.findByName("build") }
dependsOnTasks.add('1-createGroupPolicy')
dependsOnTasks.add('2-createKeystore')
dependsOnTasks.add('getNotaryServerCPB')
dependsOn dependsOnTasks
doLast{
buildCPIsHelper.buildCPIs()
}
}
// deploy CPI tasks
def deployCPIsHelper = new DeployCPIsHelper(projectContext)
tasks.register("4-deployCPIs") {
group = cordappGroup
dependsOn('3-buildCPIs')
doLast {
deployCPIsHelper.deployCPIs()
}
}
// create and register Vnodes Tasks
def createAndRegisterVNodesHelper = new CreateAndRegisterVNodesHelper(projectContext)
tasks.register("5-createAndRegVNodes") {
group = cordappGroup
dependsOn('4-deployCPIs')
doLast {
createAndRegisterVNodesHelper.createAndRegVNodes()
}
}
// Empty task, just acts as the Task user entry point task.
tasks.register('quickDeployCordapp') {
group = cordappGroup
dependsOn("5-createAndRegVNodes")
}

View File

@ -0,0 +1,270 @@
package com.r3.csde;
import java.io.*;
import java.util.LinkedList;
public class BuildCPIsHelper {
public ProjectContext pc;
public ProjectUtils utils ;
public BuildCPIsHelper(ProjectContext _pc) {
pc = _pc;
utils = new ProjectUtils(pc);
}
public void createGroupPolicy() throws IOException {
File groupPolicyFile = new File(String.format("%s/GroupPolicy.json", pc.devEnvWorkspace));
File devnetFile = new File(String.format("%s/config/dev-net.json", pc.project.getRootDir()));
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
pc.out.println("createGroupPolicy: Creating a GroupPolicy");
LinkedList<String> configX500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
LinkedList<String> commandList = new LinkedList<>();
commandList.add(String.format("%s/java", pc.javaBinDir));
commandList.add(String.format("-Dpf4j.pluginsDir=%s/plugins/", pc.cordaCliBinDir));
commandList.add("-jar");
commandList.add(String.format("%s/corda-cli.jar", pc.cordaCliBinDir));
commandList.add("mgm");
commandList.add("groupPolicy");
for (String id : configX500Ids) {
commandList.add("--name");
commandList.add(id);
}
commandList.add("--endpoint-protocol=1");
commandList.add("--endpoint=http://localhost:1080");
ProcessBuilder pb = new ProcessBuilder(commandList);
pb.redirectErrorStream(true);
Process proc = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
// todo add exception catching
FileWriter fileWriter = new FileWriter(groupPolicyFile);
String line;
while (( line = reader.readLine()) != null){
fileWriter.write(line + "\n");
}
fileWriter.close();
} else {
pc.out.println("createPolicyTask: everything up to date; nothing to do.");
}
}
public void createKeyStore() throws IOException, InterruptedException {
File keystoreFile = new File(pc.keystoreFName);
if(!keystoreFile.exists()) {
pc.out.println("createKeystore: Create a keystore");
generateKeyPair();
addDefaultSigningKey();
exportCert();
} else {
pc.out.println("createKeystore: keystore already created; nothing to do.");
}
}
private void generateKeyPair() throws IOException, InterruptedException {
LinkedList<String> cmdArray = new LinkedList<>();
cmdArray.add(pc.javaBinDir + "/keytool");
cmdArray.add("-genkeypair");
cmdArray.add("-alias");
cmdArray.add(pc.keystoreAlias);
cmdArray.add("-keystore");
cmdArray.add(pc.keystoreFName);
cmdArray.add("-storepass");
cmdArray.add("keystore password");
cmdArray.add("-dname");
cmdArray.add("CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB");
cmdArray.add("-keyalg");
cmdArray.add("RSA");
cmdArray.add("-storetype");
cmdArray.add("pkcs12");
cmdArray.add("-validity");
cmdArray.add("4000");
ProcessBuilder pb = new ProcessBuilder(cmdArray);
pb.redirectErrorStream(true);
Process proc = pb.start();
proc.waitFor();
}
private void addDefaultSigningKey() throws IOException, InterruptedException {
LinkedList<String> cmdArray = new LinkedList<>();
cmdArray.add(pc.javaBinDir + "/keytool");
cmdArray.add("-importcert");
cmdArray.add("-keystore");
cmdArray.add(pc.keystoreFName);
cmdArray.add("-storepass");
cmdArray.add("keystore password");
cmdArray.add("-noprompt");
cmdArray.add("-alias");
cmdArray.add(pc.signingCertAlias);
cmdArray.add("-file");
cmdArray.add(pc.signingCertFName);
ProcessBuilder pb = new ProcessBuilder(cmdArray);
pb.redirectErrorStream(true);
Process proc = pb.start();
proc.waitFor();
}
private void exportCert() throws IOException, InterruptedException {
LinkedList<String> cmdArray = new LinkedList<>();
cmdArray.add(pc.javaBinDir + "/keytool");
cmdArray.add("-exportcert");
cmdArray.add("-rfc");
cmdArray.add("-alias");
cmdArray.add(pc.keystoreAlias);
cmdArray.add("-keystore");
cmdArray.add(pc.keystoreFName);
cmdArray.add("-storepass");
cmdArray.add("keystore password");
cmdArray.add("-file");
cmdArray.add(pc.keystoreCertFName);
ProcessBuilder pb = new ProcessBuilder(cmdArray);
pb.redirectErrorStream(true);
Process proc = pb.start();
proc.waitFor();
}
public void buildCPIs() throws IOException, InterruptedException, CsdeException {
createCorDappCPI();
createNotaryCPI();
}
private void createCorDappCPI() throws IOException, InterruptedException, CsdeException {
String appCPIFilePath = pc.workflowBuildDir + "/" +
pc.project.getRootProject().getName() + "-" +
pc.project.getVersion() + ".cpi";
File appCPIFile = new File(appCPIFilePath);
appCPIFile.delete();
File srcDir = new File(pc.workflowBuildDir + "/libs");
File[] appCPBs = srcDir.listFiles(( x , name ) -> name.endsWith(".cpb"));
if (appCPBs == null) throw new CsdeException("Expecting exactly one CPB but no CPB found.");
if (appCPBs.length != 1) throw new CsdeException("Expecting exactly one CPB but more than one found.");
pc.out.println("appCpbs:");
pc.out.println(appCPBs[0].getAbsolutePath());
LinkedList<String> commandList = new LinkedList<>();
commandList.add(String.format("%s/java", pc.javaBinDir));
commandList.add(String.format("-Dpf4j.pluginsDir=%s/plugins/", pc.cordaCliBinDir));
commandList.add("-jar");
commandList.add(String.format("%s/corda-cli.jar", pc.cordaCliBinDir));
commandList.add("package");
commandList.add("create-cpi");
commandList.add("--cpb");
commandList.add(appCPBs[0].getAbsolutePath());
commandList.add("--group-policy");
commandList.add(pc.devEnvWorkspace + "/GroupPolicy.json");
commandList.add("--cpi-name");
commandList.add(pc.appCPIName);
commandList.add("--cpi-version");
commandList.add(pc.project.getVersion().toString());
commandList.add("--file");
commandList.add(appCPIFilePath);
commandList.add("--keystore");
commandList.add(pc.devEnvWorkspace + "/signingkeys.pfx");
commandList.add("--storepass");
commandList.add("keystore password");
commandList.add("--key");
commandList.add("my-signing-key"); // todo: should be passed as context property
ProcessBuilder pb = new ProcessBuilder(commandList);
pb.redirectErrorStream(true);
Process proc = pb.start();
proc.waitFor();
// todo: work out how to capture error code better than the following code
// BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
// File tempOutputFile = new File(String.format("%s/tempOutput.txt", pc.devEnvWorkspace));
// tempOutputFile.delete();
// FileWriter fileWriter = new FileWriter(tempOutputFile);
// String line;
// while (( line = reader.readLine()) != null){
// fileWriter.write(line + "\n");
// }
// fileWriter.close();
}
private void createNotaryCPI() throws CsdeException, IOException, InterruptedException {
String notaryCPIFilePath = pc.workflowBuildDir + "/" +
pc.notaryCPIName.replace(' ', '-').toLowerCase() + "-" +
pc.project.getVersion() + ".cpi";
File notaryCPIFile = new File(notaryCPIFilePath);
notaryCPIFile.delete();
File srcDir = new File(pc.cordaNotaryServiceDir);
File[] notaryCPBs = srcDir.listFiles(( x , name ) -> name.endsWith(".cpb") && name.contains(pc.cordaNotaryPluginsVersion));
if (notaryCPBs == null) throw new CsdeException("Expecting exactly one notary CPB but no CPB found.");
if (notaryCPBs.length != 1) throw new CsdeException("Expecting exactly one notary CPB but more than one found.");
pc.out.println("notaryCpbs:");
pc.out.println(notaryCPBs[0]);
LinkedList<String> commandList = new LinkedList<>();
commandList.add(String.format("%s/java", pc.javaBinDir));
commandList.add(String.format("-Dpf4j.pluginsDir=%s/plugins/", pc.cordaCliBinDir));
commandList.add("-jar");
commandList.add(String.format("%s/corda-cli.jar", pc.cordaCliBinDir));
commandList.add("package");
commandList.add("create-cpi");
commandList.add("--cpb");
commandList.add(notaryCPBs[0].getAbsolutePath());
commandList.add("--group-policy");
commandList.add(pc.devEnvWorkspace + "/GroupPolicy.json");
commandList.add("--cpi-name");
commandList.add(pc.notaryCPIName);
commandList.add("--cpi-version");
commandList.add(pc.project.getVersion().toString());
commandList.add("--file");
commandList.add(notaryCPIFilePath);
commandList.add("--keystore");
commandList.add(pc.devEnvWorkspace + "/signingkeys.pfx");
commandList.add("--storepass");
commandList.add("keystore password");
commandList.add("--key");
commandList.add("my-signing-key");
ProcessBuilder pb = new ProcessBuilder(commandList);
pb.redirectErrorStream(true);
Process proc = pb.start();
proc.waitFor();
}
// todo: this might be needed for improved logging
private void printCmdArray(LinkedList<String> cmdArray) {
for (int i = 0; i < cmdArray.size(); i++) {
pc.out.print(cmdArray.get(i) + " ");
}
}
}

View File

@ -0,0 +1,94 @@
package com.r3.csde;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;
/**
* Manages Bringing corda up, testing for liveness and taking corda down
*/
public class CordaLifeCycleHelper {
ProjectContext pc;
ProjectUtils utils;
public CordaLifeCycleHelper(ProjectContext _pc) {
pc = _pc;
utils = new ProjectUtils(pc);
}
public void startCorda() throws IOException {
PrintStream pidStore = new PrintStream(new FileOutputStream(pc.cordaPidCache));
File combinedWorkerJar = pc.project.getConfigurations().getByName("combinedWorker").getSingleFile();
// todo: make consistent with other ProcessBuilder set ups (use cmdArray)
new ProcessBuilder(
"docker",
"run", "-d", "--rm",
"-p", "5432:5432",
"--name", pc.dbContainerName,
"-e", "POSTGRES_DB=cordacluster",
"-e", "POSTGRES_USER=postgres",
"-e", "POSTGRES_PASSWORD=password",
"postgres:latest").start();
// todo: is there a better way of doing this - ie poll for readiness
utils.rpcWait(10000);
ProcessBuilder procBuild = new ProcessBuilder(pc.javaBinDir + "/java",
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
"-jar",
combinedWorkerJar.toString(),
"--instanceId=0",
"-mbus.busType=DATABASE",
"-spassphrase=password",
"-ssalt=salt",
"-spassphrase=password",
"-ssalt=salt",
"-ddatabase.user=user",
"-ddatabase.pass=password",
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
"-ddatabase.jdbc.directory="+pc.JDBCDir);
procBuild.redirectErrorStream(true);
Process proc = procBuild.start();
pidStore.print(proc.pid());
pc.out.println("Corda Process-id="+proc.pid());
// todo: should poll for readiness before returning
// Chris comment - We probably do not want to poll for readiness here.
// The combined-worker takes serveral minutes to come up.
// It might be better to warn the user of that and have the readiness detection and polling logic used in other tasks involved in creating v-nodes and deploying the CPI.
// Matt comment - I'm not sure I agree, we need to investigate
}
public void stopCorda() throws IOException, CsdeException {
File cordaPIDFile = new File(pc.cordaPidCache);
if(cordaPIDFile.exists()) {
Scanner sc = new Scanner(cordaPIDFile);
long pid = sc.nextLong();
pc.out.println("pid to kill=" + pid);
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
new ProcessBuilder("Powershell", "-Command", "Stop-Process", "-Id", Long.toString(pid), "-PassThru").start();
} else {
new ProcessBuilder("kill", "-9", Long.toString(pid)).start();
}
Process proc = new ProcessBuilder("docker", "stop", pc.dbContainerName).start();
cordaPIDFile.delete();
}
else {
throw new CsdeException("Cannot stop the Combined worker\nCached process ID file " + pc.cordaPidCache + " missing.\nWas the combined worker not started?");
}
}
}

View File

@ -0,0 +1,63 @@
package com.r3.csde;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import kong.unirest.json.JSONArray;
import kong.unirest.json.JSONObject;
import kong.unirest.HttpResponse;
public class CordaStatusQueries {
ProjectContext pc;
public CordaStatusQueries(ProjectContext _pc){ pc = _pc; }
public HttpResponse<JsonNode> getVNodeInfo() {
Unirest.config().verifySsl(false);
return Unirest.get(pc.baseURL + "/api/v1/virtualnode/")
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
}
public void listVNodesVerbose() {
HttpResponse<JsonNode> vnodeResponse = getVNodeInfo();
pc.out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString());
}
// X500Name, shorthash, cpiname
public void listVNodes() {
HttpResponse<JsonNode> vnodeResponse = getVNodeInfo();
JSONArray virtualNodesJson = (JSONArray) vnodeResponse.getBody().getObject().get("virtualNodes");
pc.out.println("X500 Name\tHolding identity short hash\tCPI Name");
for(Object o: virtualNodesJson){
if(o instanceof JSONObject) {
JSONObject idObj = ((JSONObject) o).getJSONObject("holdingIdentity");
JSONObject cpiObj = ((JSONObject) o).getJSONObject("cpiIdentifier");
pc.out.print("\"" + idObj.get("x500Name") + "\"");
pc.out.print("\t\"" + idObj.get("shortHash") + "\"");
pc.out.println("\t\"" + cpiObj.get("cpiName") + "\"");
}
}
}
public HttpResponse<JsonNode> getCpiInfo() {
Unirest.config().verifySsl(false);
return Unirest.get(pc.baseURL + "/api/v1/cpi/")
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
}
public void listCPIs() {
HttpResponse<JsonNode> cpiResponse = getCpiInfo();
JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
for(Object o: jArray){
if(o instanceof JSONObject) {
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
pc.out.print("cpiName=" + idObj.get("cpiName"));
pc.out.println(", cpiVersion=" + idObj.get("cpiVersion"));
}
}
}
}

View File

@ -0,0 +1,289 @@
package com.r3.csde;
import com.fasterxml.jackson.databind.ObjectMapper;
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import kong.unirest.json.JSONArray;
import kong.unirest.json.JSONObject;
import net.corda.v5.base.types.MemberX500Name;
import org.jetbrains.annotations.NotNull;
import javax.naming.ConfigurationException;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static java.net.HttpURLConnection.*;
public class CreateAndRegisterVNodesHelper {
ProjectContext pc;
ProjectUtils utils;
CordaStatusQueries queries;
public CreateAndRegisterVNodesHelper(ProjectContext _pc) {
pc = _pc;
queries = new CordaStatusQueries(pc);
utils = new ProjectUtils(pc);
}
public void createAndRegVNodes() throws IOException, CsdeException, ConfigurationException {
Unirest.config().verifySsl(false);
String appCpiCheckSum = getLastCPIUploadChkSum( pc.CPIUploadStatusFName );
String notaryCpiCheckSum = getLastCPIUploadChkSum( pc.CPIUploadStatusFName, "-NotaryServer" );
LinkedList<String> x500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
// For each identity check that it already exists.
Set<MemberX500Name> existingX500 = new HashSet<>();
HttpResponse<JsonNode> vnodeListResponse = queries.getVNodeInfo();
JSONArray virtualNodesJson = (JSONArray) vnodeListResponse.getBody().getObject().get("virtualNodes");
for(Object o: virtualNodesJson){
if(o instanceof JSONObject) {
JSONObject idObj = ((JSONObject) o).getJSONObject("holdingIdentity");
String x500id = (String) idObj.get("x500Name");
existingX500.add(MemberX500Name.parse( x500id) );
}
}
Map<String, CompletableFuture<HttpResponse<JsonNode>>> responses = new LinkedHashMap<>();
// Create the VNodes
for(String x500id: x500Ids) {
if(!existingX500.contains(MemberX500Name.parse(x500id) )) {
String cpiCheckSum = getNotaryRepresentatives().containsKey(x500id) ? notaryCpiCheckSum : appCpiCheckSum;
pc.out.println("Creating VNode for x500id=\"" + x500id + "\" cpi checksum=" + cpiCheckSum);
responses.put(x500id, Unirest
.post(pc.baseURL + "/api/v1/virtualnode")
.body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }")
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJsonAsync()
);
}
else {
pc.out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists.");
}
}
pc.out.println("Waiting for VNode creation results...");
for (Map.Entry<String, CompletableFuture<HttpResponse<JsonNode>>> response: responses.entrySet()) {
try {
HttpResponse<JsonNode> jsonNode = response.getValue().get();
// need to check this and report errors.
// 200/HTTP_OK - OK
// 409/HTTP_CONFLICT - Vnode already exists
// 500/HTTP_INTERNAL_ERROR
// - Can mean that the request timed out.
// - However, the cluster may still have created the V-node successfully, so we want to poll later.
pc.out.println("Vnode creation end point status:" + jsonNode.getStatus());
switch(jsonNode.getStatus()) {
case HTTP_OK: break;
case HTTP_CONFLICT: break;
case HTTP_INTERNAL_ERROR: break;
default:
utils.reportError(jsonNode);
}
} catch (ExecutionException | InterruptedException e) {
throw new CsdeException("Unexpected exception while waiting for response to " +
"membership submission for holding identity" + response.getKey());
}
}
Map<String, String> OKHoldingX500AndShortIds = pollForVNodeShortHoldingHashIds(x500Ids, 60, 5000);
// Register the VNodes
responses.clear();
for(String okId: OKHoldingX500AndShortIds.keySet()) {
responses.put(okId, Unirest
.post(pc.baseURL + "/api/v1/membership/" + OKHoldingX500AndShortIds.get(okId))
.body(getMemberRegistrationBody(okId))
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJsonAsync( response ->
pc.out.println("Vnode membership submission for \"" + okId + "\"" +
System.lineSeparator() + response.getBody().toPrettyString()))
);
}
pc.out.println("Vnode membership requests submitted, waiting for acknowledgement from MGM...");
for (Map.Entry<String, CompletableFuture<HttpResponse<JsonNode>>> response: responses.entrySet()) {
try {
response.getValue().get();
} catch (ExecutionException | InterruptedException e) {
throw new CsdeException("Unexpected exception while waiting for response to " +
"membership submission for holding identity" + response.getKey());
}
}
pollForCompleteMembershipRegistration(OKHoldingX500AndShortIds);
}
public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName) throws IOException, NullPointerException {
return getLastCPIUploadChkSum(CPIUploadStatusFName, "");
}
public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName,
String uploadStatusQualifier) throws IOException, NullPointerException {
String qualifiedCPIUploadStatusFName =
CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json");
ObjectMapper mapper = new ObjectMapper();
FileInputStream in = new FileInputStream(qualifiedCPIUploadStatusFName);
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
String checksum = jsonNode.get("cpiFileChecksum").toString();
if(checksum == null || checksum.equals("null")) {
throw new NullPointerException("Missing cpiFileChecksum in file " +
qualifiedCPIUploadStatusFName + " with contents:" + jsonNode);
}
return checksum;
}
// KV pairs of representative x500 name and corresponding notary service x500 name
public Map<String, String> getNotaryRepresentatives() throws IOException, ConfigurationException {
if (pc.notaryRepresentatives == null) {
pc.notaryRepresentatives = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
FileInputStream in = new FileInputStream(pc.X500ConfigFile);
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
List<String> identities = utils.getConfigX500Ids(pc.X500ConfigFile);
for (com.fasterxml.jackson.databind.JsonNode notary : jsonNode.get("notaries")) {
String svcX500Id = utils.jsonNodeToString(notary.get("serviceX500Name"));
com.fasterxml.jackson.databind.JsonNode repsForThisService = notary.get("representatives");
if (repsForThisService.isEmpty()) {
throw new ConfigurationException(
"Notary service \"" + svcX500Id + "\" must have at least one representative.");
} else if (repsForThisService.size() > 1) {
// Temporary restriction while the MGM only supports a 1-1 association
throw new ConfigurationException(
"Notary service \"" + svcX500Id + "\" can only have a single representative at this time.");
}
for (com.fasterxml.jackson.databind.JsonNode representative : repsForThisService) {
String repAsString = utils.jsonNodeToString(representative);
if (identities.contains(repAsString)) {
pc.notaryRepresentatives.put(repAsString, svcX500Id);
} else {
throw new ConfigurationException(
"Notary representative \"" + repAsString + "\" is not a valid identity");
}
}
}
}
return pc.notaryRepresentatives;
}
private String getMemberRegistrationBody(String memberX500Name) throws ConfigurationException, IOException {
Map<String, String> notaryReps = getNotaryRepresentatives();
String context = "\"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\"" + (
notaryReps.containsKey(memberX500Name)
? ", \"corda.roles.0\" : \"notary\", " +
"\"corda.notary.service.name\" : \"" + notaryReps.get(memberX500Name) + "\", " +
// This will need revisiting in the long term when additional protocols are added, and will
// need to be specified in config. We will also need to review the hard-coded name once
// notary plugin selection logic is re-instated in CORE-7248.
"\"corda.notary.service.plugin\" : \"net.corda.notary.NonValidatingNotary\""
: ""
);
return "{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { " + context + " } } }";
}
Map<String, String> pollForVNodeShortHoldingHashIds(List<String> x500Ids, int retryCount, int coolDownMs ) throws CsdeException {
HashMap<String, String> x500NameToShortHashes = new HashMap<>();
Set<String> vnodesToCheck = new HashSet<String>(x500Ids);
while(!vnodesToCheck.isEmpty() && retryCount-- > 0) {
utils.rpcWait(coolDownMs);
kong.unirest.json.JSONArray virtualNodes = (JSONArray) queries.getVNodeInfo().getBody().getObject().get("virtualNodes");
Map<String, String> vnodesMap = new HashMap<String, String>();
for (Object virtualNode : virtualNodes) {
if (virtualNode instanceof JSONObject) {
JSONObject idObj = ((JSONObject) virtualNode).getJSONObject("holdingIdentity");
vnodesMap.put(idObj.get("x500Name").toString(), idObj.get("shortHash").toString());
}
}
for(String x500Name: vnodesToCheck) {
if(vnodesMap.containsKey(x500Name)) {
x500NameToShortHashes.put(x500Name, vnodesMap.get(x500Name));
}
}
vnodesMap.keySet().forEach(vnodesToCheck::remove);
}
if(!vnodesToCheck.isEmpty()) {
throw new CsdeException("VNode creation timed out. Not all expected vnodes were reported as created:" + vnodesToCheck.toString());
}
return x500NameToShortHashes;
}
private void pollForCompleteMembershipRegistration(Map<String, String> X500ToShortIdHash) throws CsdeException {
HashSet<String> vnodesToCheck = new HashSet<String>(X500ToShortIdHash.keySet());
LinkedList<String> approved = new LinkedList<String>();
while (!vnodesToCheck.isEmpty()) {
utils.rpcWait(2000);
approved.clear();
for (String vnodeX500 : vnodesToCheck) {
try {
pc.out.println("Checking membership registration progress for v-node '" + vnodeX500 + "':");
HttpResponse<JsonNode> statusResponse = Unirest
.get(pc.baseURL + "/api/v1/membership/" + X500ToShortIdHash.get(vnodeX500) + "/")
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
if (isMembershipRegComplete(statusResponse)) {
approved.add(vnodeX500);
}
} catch (Exception e) {
throw new CsdeException("Error when registering V-Node '" + vnodeX500 + "'", e);
}
}
approved.forEach(vnodesToCheck::remove);
}
}
private boolean isMembershipRegComplete(HttpResponse<JsonNode> response) throws CsdeException {
if(response.getStatus() == HTTP_OK) {
JsonNode responseBody = response.getBody();
pc.out.println(responseBody.toPrettyString());
if(responseBody.getArray().length() > 0) {
JSONObject memRegStatusInfo = (JSONObject) responseBody
.getArray()
.getJSONObject(0);
String memRegStatus = memRegStatusInfo.get("registrationStatus").toString();
if (memRegStatus.equals("DECLINED")) {
throw new CsdeException("V-Node membership registration declined by Corda");
}
return memRegStatus.equals("APPROVED");
}
else {
return false;
}
}
else {
utils.reportError(response);
}
return false;
}
}

View File

@ -1,6 +1,9 @@
package com.r3.csde;
public class CsdeException extends Exception {
public CsdeException(String message, Throwable cause) {
super(message, cause);
}
public CsdeException(String message){
super(message);
}

View File

@ -1,415 +0,0 @@
package com.r3.csde;
import kong.unirest.json.JSONArray;
import org.gradle.api.Project;
import net.corda.v5.base.types.MemberX500Name;
import kong.unirest.Unirest;
import kong.unirest.json.JSONObject;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.Set;
import static java.lang.Thread.sleep;
public class CsdeRpcInterface {
private Project project;
private String baseURL = "https://localhost:8888";
private String rpcUser = "admin";
private String rpcPasswd = "admin";
private String workspaceDir = "workspace";
static private int retryWaitMs = 1000;
static PrintStream out = System.out;
static private String CPIUploadStatusBaseName = "CPIFileStatus.json";
static private String CPIUploadStatusFName;
static private String X500ConfigFile = "config/dev-net.json";
static private String javaBinDir;
static private String cordaPidCache = "CordaPIDCache.dat";
static private String dbContainerName;
private String JDBCDir;
private String combinedWorkerBinRe;
public CsdeRpcInterface() {
}
public CsdeRpcInterface (Project inProject,
String inBaseUrl,
String inRpcUser,
String inRpcPasswd,
String inWorkspaceDir,
String inJavaBinDir,
String inDbContainerName,
String inJDBCDir,
String inCordaPidCache
) {
project = inProject;
baseURL = inBaseUrl;
rpcUser = inRpcUser;
rpcPasswd = inRpcPasswd;
workspaceDir = inWorkspaceDir;
javaBinDir = inJavaBinDir;
cordaPidCache = inCordaPidCache;
dbContainerName = inDbContainerName;
JDBCDir = inJDBCDir;
CPIUploadStatusFName = workspaceDir +"/"+ CPIUploadStatusBaseName;
}
static private void rpcWait(int millis) {
try {
sleep(millis);
}
catch(InterruptedException e) {
throw new UnsupportedOperationException("Interrupts not supported.", e);
}
}
static private void rpcWait() {
rpcWait(retryWaitMs);
}
public LinkedList<String> getConfigX500Ids() throws IOException {
LinkedList<String> x500Ids = new LinkedList<>();
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
FileInputStream in = new FileInputStream(X500ConfigFile);
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
String idAsString = identity.toString();
x500Ids.add(idAsString.substring(1,idAsString.length()-1));
}
return x500Ids;
}
static public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName) throws IOException, NullPointerException {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
FileInputStream in = new FileInputStream(CPIUploadStatusFName);
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
String checksum = jsonNode.get("cpiFileChecksum").toString();
if(checksum == null || checksum.equals("null")) {
throw new NullPointerException("Missing cpiFileChecksum in file " + CPIUploadStatusFName+ " with contents:" + jsonNode);
}
return checksum;
}
public void reportError(@NotNull kong.unirest.HttpResponse<kong.unirest.JsonNode> response) throws CsdeException {
out.println("*** *** ***");
out.println("Unexpected response from Corda");
out.println("Status="+ response.getStatus());
out.println("*** Headers ***\n"+ response.getHeaders());
out.println("*** Body ***\n"+ response.getBody());
out.println("*** *** ***");
throw new CsdeException("Error: unexpected response from Corda.");
}
public void downloadFile(String url, String targetPath) {
Unirest.get(url)
.asFile(targetPath)
.getBody();
}
public kong.unirest.HttpResponse<kong.unirest.JsonNode> getVNodeInfo() {
Unirest.config().verifySsl(false);
return Unirest.get(baseURL + "/api/v1/virtualnode/")
.basicAuth(rpcUser, rpcPasswd)
.asJson();
}
public void listVNodesVerbose() {
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = getVNodeInfo();
out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString());
}
// X500Name, cpiname, shorthash,
public void listVNodes() {
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = getVNodeInfo();
kong.unirest.json.JSONArray virtualNodesJson = (JSONArray) vnodeResponse.getBody().getObject().get("virtualNodes");
out.println("X500 Name\tHolding identity short hash");
for(Object o: virtualNodesJson){
if(o instanceof kong.unirest.json.JSONObject) {
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("holdingIdentity");
out.print("\"" + idObj.get("x500Name") + "\"");
out.println("\t\"" + idObj.get("shortHash") + "\"");
}
}
}
public kong.unirest.HttpResponse<kong.unirest.JsonNode> getCpiInfo() {
Unirest.config().verifySsl(false);
return Unirest.get(baseURL + "/api/v1/cpi/")
.basicAuth(rpcUser, rpcPasswd)
.asJson();
}
public void listCPIs() {
kong.unirest.HttpResponse<kong.unirest.JsonNode> cpiResponse = getCpiInfo();
kong.unirest.json.JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
for(Object o: jArray){
if(o instanceof kong.unirest.json.JSONObject) {
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("id");
out.print("cpiName=" + idObj.get("cpiName"));
out.println(", cpiVersion=" + idObj.get("cpiVersion"));
}
}
}
public void uploadCertificate(String certAlias, String certFName) {
Unirest.config().verifySsl(false);
kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadResponse = Unirest.put(baseURL + "/api/v1/certificates/cluster/code-signer")
.field("alias", certAlias)
.field("certificate", new File(certFName))
.basicAuth(rpcUser, rpcPasswd)
.asJson();
out.println("Certificate/key upload, alias "+certAlias+" certificate/key file "+certFName);
out.println(uploadResponse.getBody().toPrettyString());
}
public void forceuploadCPI(String cpiFName) throws FileNotFoundException, CsdeException {
Unirest.config().verifySsl(false);
kong.unirest.HttpResponse<kong.unirest.JsonNode> jsonResponse = Unirest.post(baseURL + "/api/v1/maintenance/virtualnode/forcecpiupload/")
.field("upload", new File(cpiFName))
.basicAuth(rpcUser, rpcPasswd)
.asJson();
if(jsonResponse.getStatus() == 200) {
String id = (String) jsonResponse.getBody().getObject().get("id");
out.println("get id:\n" +id);
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = uploadStatus(id);
if (statusResponse.getStatus() == 200) {
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(CPIUploadStatusFName));
cpiUploadStatus.print(statusResponse.getBody());
out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
} else {
reportError(statusResponse);
}
}
else {
reportError(jsonResponse);
}
}
private boolean uploadStatusRetry(kong.unirest.HttpResponse<kong.unirest.JsonNode> response) {
int status = response.getStatus();
kong.unirest.JsonNode body = response.getBody();
// Do not retry if successful.
if(status == 200) {
// Retry until you get an "OK"; we may see several other responses before the "OK" response.
return !(body.getObject().get("status").equals("OK"));
}
else if (status == 400){
JSONObject details = response.getBody().getObject().getJSONObject("details");
if( details != null ){
String code = (String) details.getString("code");
return !code.equals("BAD_REQUEST");
}
else {
// 400 otherwise means some transient problem
return true;
}
}
return false;
}
public kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadStatus(String requestId) {
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = null;
do {
rpcWait(1000);
statusResponse = Unirest
.get(baseURL + "/api/v1/cpi/status/" + requestId + "/")
.basicAuth(rpcUser, rpcPasswd)
.asJson();
out.println("Upload status="+statusResponse.getStatus()+", status query response:\n"+statusResponse.getBody().toPrettyString());
}
while(uploadStatusRetry(statusResponse));
return statusResponse;
}
public void deployCPI(String cpiFName, String cpiName, String cpiVersion) throws FileNotFoundException, CsdeException {
Unirest.config().verifySsl(false);
kong.unirest.HttpResponse<kong.unirest.JsonNode> cpiResponse = getCpiInfo();
kong.unirest.json.JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
int matches = 0;
for(Object o: jArray.toList() ) {
if(o instanceof JSONObject) {
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
if((idObj.get("cpiName").toString().equals(cpiName)
&& idObj.get("cpiVersion").toString().equals(cpiVersion))) {
matches++;
}
}
}
out.println("Matching CPIS="+matches);
if(matches == 0) {
kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadResponse = Unirest.post(baseURL + "/api/v1/cpi/")
.field("upload", new File(cpiFName))
.basicAuth(rpcUser, rpcPasswd)
.asJson();
kong.unirest.JsonNode body = uploadResponse.getBody();
int status = uploadResponse.getStatus();
out.println("Upload Status:" + status);
out.println("Pretty print the body\n" + body.toPrettyString());
// Expect the ID field to be a string.
if (status == 200) {
String id = (String) body.getObject().get("id");
out.println("get id:\n" + id);
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = uploadStatus(id);
if (statusResponse.getStatus() == 200) {
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(CPIUploadStatusFName));
cpiUploadStatus.print(statusResponse.getBody());
out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
} else {
reportError(statusResponse);
}
} else {
reportError(uploadResponse);
}
}
else {
out.println("CPI already uploaded doing a 'force' upload.");
forceuploadCPI(cpiFName);
}
}
public void createAndRegVNodes() throws IOException, CsdeException{
Unirest.config().verifySsl(false);
String cpiCheckSum = getLastCPIUploadChkSum( CPIUploadStatusFName );
LinkedList<String> x500Ids = getConfigX500Ids();
LinkedList<String> OKHoldingShortIds = new LinkedList<>();
// Create a list of X500 IDs we will not need to create VNodes for.
Set<MemberX500Name> existingX500 = new HashSet<>();
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeListResponse = getVNodeInfo();
kong.unirest.json.JSONArray virtualNodesJson = (JSONArray) vnodeListResponse.getBody().getObject().get("virtualNodes");
for(Object o: virtualNodesJson){
if(o instanceof kong.unirest.json.JSONObject) {
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("holdingIdentity");
String x500id = (String) idObj.get("x500Name");
existingX500.add(MemberX500Name.parse( x500id) );
}
}
// Create the VNodes
for(String x500id: x500Ids) {
if(!existingX500.contains(MemberX500Name.parse(x500id) )) {
out.println("Creating VNode for x500id=\"" + x500id + "\" cpi checksum=" + cpiCheckSum);
kong.unirest.HttpResponse<kong.unirest.JsonNode> jsonNode = Unirest.post(baseURL + "/api/v1/virtualnode")
.body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }")
.basicAuth(rpcUser, rpcPasswd)
.asJson();
// Logging.
// Check this and report errors.
// 200 - OK.
// 409 - Vnode already exists.
if (jsonNode.getStatus() != 409) {
if (jsonNode.getStatus() != 200) {
reportError(jsonNode);
} else {
JSONObject thing = jsonNode.getBody().getObject().getJSONObject("holdingIdentity");
String shortHash = (String) thing.get("shortHash");
OKHoldingShortIds.add(shortHash);
}
}
}
else {
out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists.");
}
}
// Register the VNodes.
for(String shortHoldingIdHash: OKHoldingShortIds) {
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = Unirest.post(baseURL + "/api/v1/membership/" + shortHoldingIdHash)
.body("{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { \"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\" } } }")
.basicAuth(rpcUser, rpcPasswd)
.asJson();
out.println("Vnode membership submission:\n" + vnodeResponse.getBody().toPrettyString());
}
}
public void startCorda() throws IOException {
PrintStream pidStore = new PrintStream(new FileOutputStream(cordaPidCache));
File combinedWorkerJar = project.getConfigurations().getByName("combinedWorker").getSingleFile();
new ProcessBuilder(
"docker",
"run", "-d", "--rm",
"-p", "5432:5432",
"--name", dbContainerName,
"-e", "POSTGRES_DB=cordacluster",
"-e", "POSTGRES_USER=postgres",
"-e", "POSTGRES_PASSWORD=password",
"postgres:latest").start();
rpcWait(10000);
ProcessBuilder procBuild = new ProcessBuilder(javaBinDir + "/java",
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
"-jar",
combinedWorkerJar.toString(),
"--instanceId=0",
"-mbus.busType=DATABASE",
"-spassphrase=password",
"-ssalt=salt",
"-spassphrase=password",
"-ssalt=salt",
"-ddatabase.user=user",
"-ddatabase.pass=password",
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
"-ddatabase.jdbc.directory="+JDBCDir);
procBuild.redirectErrorStream(true);
Process proc = procBuild.start();
pidStore.print(proc.pid());
out.println("Corda Process-id="+proc.pid());
}
public void stopCorda() throws IOException, NoPidFile {
File cordaPIDFile = new File(cordaPidCache);
if(cordaPIDFile.exists()) {
Scanner sc = new Scanner(cordaPIDFile);
long pid = sc.nextLong();
out.println("pid to kill=" + pid);
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
new ProcessBuilder("Powershell", "-Command", "Stop-Process", "-Id", Long.toString(pid), "-PassThru").start();
} else {
new ProcessBuilder("kill", "-9", Long.toString(pid)).start();
}
Process proc = new ProcessBuilder("docker", "stop", dbContainerName).start();
cordaPIDFile.delete();
}
else {
throw new NoPidFile("Cannot stop the Combined worker\nCached process ID file " + cordaPidCache + " missing.\nWas the combined worker not started?");
}
}
}

View File

@ -0,0 +1,188 @@
package com.r3.csde;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import kong.unirest.json.JSONArray;
import kong.unirest.json.JSONObject;
import kong.unirest.HttpResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_OK;
public class DeployCPIsHelper {
public DeployCPIsHelper() {
}
ProjectContext pc;
CordaStatusQueries queries;
ProjectUtils utils;
public DeployCPIsHelper(ProjectContext _pc) {
pc = _pc;
queries = new CordaStatusQueries(pc);
utils = new ProjectUtils(pc);
}
public void deployCPIs() throws FileNotFoundException, CsdeException{
uploadCertificate(pc.signingCertAlias, pc.signingCertFName);
uploadCertificate(pc.keystoreAlias, pc.keystoreCertFName);
// todo: make consistent with other string building code - remove String.format
String appCPILocation = String.format("%s/%s-%s.cpi",
pc.workflowBuildDir,
pc.project.getName(),
pc.project.getVersion());
deployCPI(appCPILocation, pc.appCPIName,pc.project.getVersion().toString());
String notaryCPILocation = String.format("%s/%s-%s.cpi",
pc.workflowBuildDir,
pc.notaryCPIName.replace(' ','-').toLowerCase(),
pc.project.getVersion());
deployCPI(notaryCPILocation,
pc.notaryCPIName,
pc.project.getVersion().toString(),
"-NotaryServer" );
}
public void uploadCertificate(String certAlias, String certFName) {
Unirest.config().verifySsl(false);
HttpResponse<JsonNode> uploadResponse = Unirest.put(pc.baseURL + "/api/v1/certificates/cluster/code-signer")
.field("alias", certAlias)
.field("certificate", new File(certFName))
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
pc.out.println("Certificate/key upload, alias "+certAlias+" certificate/key file "+certFName);
pc.out.println(uploadResponse.getBody().toPrettyString());
}
public void forceuploadCPI(String cpiFName) throws FileNotFoundException, CsdeException {
forceuploadCPI(cpiFName, "");
}
public void forceuploadCPI(String cpiFName, String uploadStatusQualifier) throws FileNotFoundException, CsdeException {
Unirest.config().verifySsl(false);
HttpResponse<JsonNode> jsonResponse = Unirest.post(pc.baseURL + "/api/v1/maintenance/virtualnode/forcecpiupload/")
.field("upload", new File(cpiFName))
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
if(jsonResponse.getStatus() == HTTP_OK) {
String id = (String) jsonResponse.getBody().getObject().get("id");
pc.out.println("get id:\n" +id);
HttpResponse<JsonNode> statusResponse = uploadStatus(id);
if (statusResponse.getStatus() == HTTP_OK) {
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(
pc.CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json" )));
cpiUploadStatus.print(statusResponse.getBody());
pc.out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
} else {
utils.reportError(statusResponse);
}
}
else {
utils.reportError(jsonResponse);
}
}
private boolean uploadStatusRetry(HttpResponse<JsonNode> response) {
int status = response.getStatus();
JsonNode body = response.getBody();
// Do not retry on success // todo: need to think through the possible outcomes here - what if the bodyTitle is null, it won't retry
if(status == HTTP_OK) {
// Keep retrying until we get "OK" may move through "Validating upload", "Persisting CPI"
return !(body.getObject().get("status").equals("OK"));
}
else if (status == HTTP_BAD_REQUEST){
String bodyTitle = response.getBody().getObject().getString("title");
return bodyTitle != null && bodyTitle.matches("No such requestId=[-0-9a-f]+");
}
return false;
}
public HttpResponse<JsonNode> uploadStatus(String requestId) {
HttpResponse<JsonNode> statusResponse = null;
do {
utils.rpcWait(1000);
statusResponse = Unirest
.get(pc.baseURL + "/api/v1/cpi/status/" + requestId + "/")
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
pc.out.println("Upload status="+statusResponse.getStatus()+", status query response:\n"+statusResponse.getBody().toPrettyString());
}
while(uploadStatusRetry(statusResponse));
return statusResponse;
}
public void deployCPI(String cpiFName, String cpiName, String cpiVersion) throws FileNotFoundException, CsdeException {
deployCPI(cpiFName, cpiName, cpiVersion, "");
}
public void deployCPI(String cpiFName,
String cpiName,
String cpiVersion,
String uploadStatusQualifier) throws FileNotFoundException, CsdeException {
// todo: where is the primary instance declared?
Unirest.config().verifySsl(false);
HttpResponse<JsonNode> cpiResponse = queries.getCpiInfo();
JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
int matches = 0;
for(Object o: jArray.toList() ) {
if(o instanceof JSONObject) {
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
if((idObj.get("cpiName").toString().equals(cpiName)
&& idObj.get("cpiVersion").toString().equals(cpiVersion))) {
matches++;
}
}
}
pc.out.println("Matching CPIS="+matches);
if(matches == 0) {
HttpResponse<JsonNode> uploadResponse = Unirest.post(pc.baseURL + "/api/v1/cpi/")
.field("upload", new File(cpiFName))
.basicAuth(pc.rpcUser, pc.rpcPasswd)
.asJson();
JsonNode body = uploadResponse.getBody();
int status = uploadResponse.getStatus();
pc.out.println("Upload Status:" + status);
pc.out.println("Pretty print the body\n" + body.toPrettyString());
// We expect the id field to be a string.
if (status == HTTP_OK) {
String id = (String) body.getObject().get("id");
pc.out.println("get id:\n" + id);
HttpResponse<JsonNode> statusResponse = uploadStatus(id);
if (statusResponse.getStatus() == HTTP_OK) {
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(
pc.CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json" )));
cpiUploadStatus.print(statusResponse.getBody());
pc.out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
} else {
utils.reportError(statusResponse);
}
} else {
utils.reportError(uploadResponse);
}
}
else {
pc.out.println("CPI already uploaded doing a 'force' upload.");
forceuploadCPI(cpiFName);
}
}
}

View File

@ -1,7 +0,0 @@
package com.r3.csde;
public class NoPidFile extends Exception {
public NoPidFile(String message){
super(message);
}
}

View File

@ -0,0 +1,83 @@
package com.r3.csde;
import org.gradle.api.Project;
import java.io.PrintStream;
import java.util.Map;
public class ProjectContext {
Project project;
String baseURL = "https://localhost:8888";
String rpcUser = "admin";
String rpcPasswd = "admin";
String workspaceDir = "workspace";
int retryWaitMs = 1000;
PrintStream out = System.out;
String CPIUploadStatusBaseName = "CPIFileStatus.json";
String CPIUploadStatusFName;
String X500ConfigFile = "config/dev-net.json";
String javaBinDir;
String cordaPidCache = "CordaPIDCache.dat";
String dbContainerName;
String JDBCDir;
String combinedWorkerBinRe;
Map<String, String> notaryRepresentatives = null;
String signingCertAlias;
String signingCertFName;
String keystoreAlias;
String keystoreFName;
String keystoreCertFName;
String appCPIName;
String notaryCPIName;
String devEnvWorkspace;
String cordaCliBinDir;
String cordaNotaryServiceDir;
String workflowBuildDir;
String cordaNotaryPluginsVersion;
public ProjectContext (Project inProject,
String inBaseUrl,
String inRpcUser,
String inRpcPasswd,
String inWorkspaceDir,
String inJavaBinDir,
String inDbContainerName,
String inJDBCDir,
String inCordaPidCache,
String inSigningCertAlias,
String inSigningCertFName,
String inKeystoreAlias,
String inKeystoreFName,
String inKeystoreCertFName,
String inAppCPIName,
String inNotaryCPIName,
String inDevEnvWorkspace,
String inCordaCLiBinDir,
String inCordaNotaryServiceDir,
String inWorkflowBuildDir,
String inCordaNotaryPluginsVersion
) {
project = inProject;
baseURL = inBaseUrl;
rpcUser = inRpcUser;
rpcPasswd = inRpcPasswd;
workspaceDir = inWorkspaceDir;
javaBinDir = inJavaBinDir;
cordaPidCache = inCordaPidCache;
dbContainerName = inDbContainerName;
JDBCDir = inJDBCDir;
CPIUploadStatusFName = workspaceDir + "/" + CPIUploadStatusBaseName;
signingCertAlias = inSigningCertAlias;
signingCertFName = inSigningCertFName;
keystoreAlias = inKeystoreAlias;
keystoreFName = inKeystoreFName;
keystoreCertFName = inKeystoreCertFName;
appCPIName = inAppCPIName;
notaryCPIName = inNotaryCPIName;
devEnvWorkspace = inDevEnvWorkspace;
cordaCliBinDir = inCordaCLiBinDir;
cordaNotaryServiceDir = inCordaNotaryServiceDir;
workflowBuildDir = inWorkflowBuildDir;
cordaNotaryPluginsVersion = inCordaNotaryPluginsVersion;
}
}

View File

@ -0,0 +1,71 @@
package com.r3.csde;
import com.fasterxml.jackson.databind.ObjectMapper;
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import org.jetbrains.annotations.NotNull;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.LinkedList;
import static java.lang.Thread.sleep;
public class ProjectUtils {
ProjectContext pc;
ProjectUtils(ProjectContext _pc) {
pc = _pc;
}
void rpcWait(int millis) {
try {
sleep(millis);
}
catch(InterruptedException e) {
throw new UnsupportedOperationException("Interrupts not supported.", e);
}
}
void rpcWait() {
rpcWait( pc.retryWaitMs);
}
public LinkedList<String> getConfigX500Ids(String configFile) throws IOException {
LinkedList<String> x500Ids = new LinkedList<>();
// com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
ObjectMapper mapper = new ObjectMapper();
FileInputStream in = new FileInputStream(configFile);
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
x500Ids.add(jsonNodeToString(identity));
}
return x500Ids;
}
public String jsonNodeToString(com.fasterxml.jackson.databind.JsonNode jsonNode) {
String jsonString = jsonNode.toString();
return jsonString.substring(1, jsonString.length()-1);
}
public void downloadFile(String url, String targetPath) {
Unirest.get(url)
.asFile(targetPath)
.getBody();
}
public void reportError(@NotNull HttpResponse<JsonNode> response) throws CsdeException {
pc.out.println("*** *** ***");
pc.out.println("Unexpected response from Corda");
pc.out.println("Status="+ response.getStatus());
pc.out.println("*** Headers ***\n"+ response.getHeaders());
pc.out.println("*** Body ***\n"+ response.getBody());
pc.out.println("*** *** ***");
throw new CsdeException("Error: unexpected response from Corda.");
}
}

View File

@ -1,4 +0,0 @@
import com.r3.csde.CsdeRpcInterface;
public class CsdeRpcInterfaceTests {
}

View File

@ -1,7 +1,12 @@
{
"identities" : [
"identities" : [
"CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
"CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB"
]
"CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
"CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB"],
"notaries" : [
{
"serviceX500Name": "CN=NotaryService, OU=Test Dept, O=R3, L=London, C=GB",
"representatives": ["CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB"]
}]
}

91
contracts/build.gradle Normal file
View File

@ -0,0 +1,91 @@
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
// required by Corda.
id 'net.corda.plugins.cordapp-cpb2'
id 'org.jetbrains.kotlin.jvm'
id 'maven-publish'
}
// Declare dependencies for the modules we will use.
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
// R3 builds kotlin-stdlib-jdk8-osgi from Kotlin's kotlin-stdlib-jdk8.
// NB:
// Kotlin's kotlin-osgi-bundle does not provide all of the Kotlin API that is required,
// There is no kotlin-stdlib-jdk11, but one is not needed even though we are targetting Java 11.
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
// Corda API specified.
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
// If using transistive dependencies this will provide most of Corda-API:
// cordaProvided 'net.corda:corda-application'
// Alternatively we can explicitly specify all our Corda-API dependencies:
cordaProvided 'net.corda:corda-base'
cordaProvided 'net.corda:corda-application'
cordaProvided 'net.corda:corda-crypto'
cordaProvided 'net.corda:corda-membership'
// cordaProvided 'net.corda:corda-persistence'
cordaProvided 'net.corda:corda-serialization'
cordaProvided 'net.corda:corda-ledger-utxo'
cordaProvided 'net.corda:corda-ledger-consensual'
// CorDapps that use the UTXO ledger must include at least one notary client plugin
cordapp "com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-client:$cordaNotaryPluginsVersion"
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
// This are shared so should be here.
// Dependencies Required By Test Tooling
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
// 3rd party libraries
// Required
testImplementation "org.slf4j:slf4j-simple:2.0.0"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Optional but used by exmaple tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
}
// The CordApp section.
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the components
// subproject.
// This is required by the corda plugins to build the CorDapp.
cordapp {
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
// Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
// The platform version will correspond to and be roughly equivalent to the Corda API version.
targetPlatformVersion = platformVersion.toInteger()
minimumPlatformVersion = platformVersion.toInteger()
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
name "ContractsModuleNameHere"
versionId 1
vendor "VendorNameHere"
}
}
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
}

View File

@ -2,11 +2,19 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
# cordaApiVersion=5.0.0.190-DevPreview-2
cordaApiVersion=5.0.0.505-Beta1.0-HC00
cordaApiVersion=5.0.0.523-Fox1.0-RC03
# Settings For Development Utilities
combinedWorkerVersion=5.0.0.0-Fox1.0-RC03
simulatorVersion=5.0.0.0-Fox1.0-RC03
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
cordaNotaryPluginsVersion=5.0.0.0-Fox1.0-RC03
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
cordaPluginsVersion=7.0.0
cordaPluginsVersion=7.0.1-Fox1.0-RC03
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@ -24,10 +32,6 @@ mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
hamcrestVersion=2.2
# Settings For Development Utilities
combinedWorkerVersion=5.0.0.0-Beta1.0-HC00
simulatorVersion=5.0.0.0-Beta1.0-HC00
cordaClusterURL=https://localhost:8888
cordaRpcUser=admin
cordaRpcPasswd=admin
@ -35,4 +39,4 @@ devEnvWorkspace=workspace
dbContainerName=CSDEpostgresql
# R3 internal repository
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0-RC03

View File

@ -21,6 +21,8 @@ pluginManagement {
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'csde-cordapp-template-java'
include ':workflows'
include ':contracts'

94
workflows/build.gradle Normal file
View File

@ -0,0 +1,94 @@
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
// required by Corda.
id 'net.corda.plugins.cordapp-cpb2'
id 'org.jetbrains.kotlin.jvm'
id 'maven-publish'
}
// Declare dependencies for the modules we will use.
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
// From other subprojects:
cordapp project(':contracts')
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
// R3 builds kotlin-stdlib-jdk8-osgi from Kotlin's kotlin-stdlib-jdk8.
// NB:
// Kotlin's kotlin-osgi-bundle does not provide all of the Kotlin API that is required,
// There is no kotlin-stdlib-jdk11, but one is not needed even though we are targetting Java 11.
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
// Corda API specified.
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
// If using transistive dependencies this will provide most of Corda-API:
// cordaProvided 'net.corda:corda-application'
// Alternatively we can explicitly specify all our Corda-API dependencies:
cordaProvided 'net.corda:corda-base'
cordaProvided 'net.corda:corda-application'
cordaProvided 'net.corda:corda-crypto'
cordaProvided 'net.corda:corda-membership'
// cordaProvided 'net.corda:corda-persistence'
cordaProvided 'net.corda:corda-serialization'
cordaProvided 'net.corda:corda-ledger-utxo'
cordaProvided 'net.corda:corda-ledger-consensual'
// CorDapps that use the UTXO ledger must include at least one notary client plugin
cordapp "com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-client:$cordaNotaryPluginsVersion"
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
// This are shared so should be here.
// Dependencies Required By Test Tooling
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
// 3rd party libraries
// Required
testImplementation "org.slf4j:slf4j-simple:2.0.0"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Optional but used by exmaple tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
}
// The CordApp section.
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the components
// subproject.
// This is required by the corda plugins to build the CorDapp.
cordapp {
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
// Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
// The platform version will correspond to and be roughly equivalent to the Corda API version.
targetPlatformVersion = platformVersion.toInteger()
minimumPlatformVersion = platformVersion.toInteger()
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
name "WorkflowsModuleNameHere"
versionId 1
vendor "VendorNameHere"
}
}
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
}

View File

@ -1,4 +1,4 @@
package com.r3.developers.csdetemplate;
package com.r3.developers.csdetemplate.workflows;
import net.corda.v5.base.annotations.CordaSerializable;
import net.corda.v5.base.types.MemberX500Name;

View File

@ -1,4 +1,4 @@
package com.r3.developers.csdetemplate;
package com.r3.developers.csdetemplate.workflows;
import net.corda.v5.application.flows.*;
import net.corda.v5.application.marshalling.JsonMarshallingService;
@ -91,7 +91,7 @@ public class MyFirstFlow implements RPCStartableFlow {
RequestBody for triggering the flow via http-rpc:
{
"clientRequestId": "r1",
"flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
"flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow",
"requestData": {
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
}

View File

@ -1,4 +1,4 @@
package com.r3.developers.csdetemplate;
package com.r3.developers.csdetemplate.workflows;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.InitiatedBy;

View File

@ -1,4 +1,4 @@
package com.r3.developers.csdetemplate;
package com.r3.developers.csdetemplate.workflows;
import net.corda.v5.base.types.MemberX500Name;

View File

@ -1,15 +1,12 @@
package com.r3.developers.csdetemplate;
package com.r3.developers.csdetemplate.workflows;
import net.corda.simulator.HoldingIdentity;
import net.corda.simulator.RequestData;
import net.corda.simulator.SimulatedVirtualNode;
import net.corda.simulator.Simulator;
import net.corda.v5.base.types.MemberX500Name;
import org.junit.jupiter.api.Test;
class MyFirstFlowTest {
// Names picked to match the corda network in config/dev-net.json
private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
@ -20,14 +17,10 @@ class MyFirstFlowTest {
// Instantiate an instance of the simulator.
Simulator simulator = new Simulator();
// Create Alice's and Bob HoldingIDs.
HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500);
HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500);
// Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
// Don't assign Bob's virtual node to a value. You don't need it for this particular test.
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class);
simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class);
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
// Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
@ -46,4 +39,3 @@ class MyFirstFlowTest {
assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
}
}