Merge pull request #12 from corda/chrisbarratt/CORE-9302-switch-to-Corda-beta1-release-build
CORE-9302 Switch to corda beta1 release build
This commit is contained in:
		
						commit
						1eb51b7fcc
					
				
							
								
								
									
										2
									
								
								.ci/Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.ci/Jenkinsfile
									
									
									
									
										vendored
									
									
								
							@ -1,4 +1,4 @@
 | 
				
			|||||||
@Library('corda-shared-build-pipeline-steps@chrisbarratt/CORE-8075-create-pipeline-for-running-the-autotester') _
 | 
					@Library('corda-shared-build-pipeline-steps@5.0') _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cordaPipeline(
 | 
					cordaPipeline(
 | 
				
			||||||
    nexusAppId: 'com.corda.CSDE-Java.5.0',
 | 
					    nexusAppId: 'com.corda.CSDE-Java.5.0',
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
# CSDE-cordapp-template-java
 | 
					# CSDE-cordapp-template-java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Note: This cut of CSDE is work in progress and has not been released yet, hence may not function as expected.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To help make the process of prototyping Cordapps on Developer Preview 2 more straight forward we have developed the Cordapp Standard Development Environment (CSDE). 
 | 
					To help make the process of prototyping Cordapps on Developer Preview 2 more straight forward we have developed the Cordapp Standard Development Environment (CSDE). 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local machine. The CSDE provides:
 | 
					The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local machine. The CSDE provides:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										90
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								build.gradle
									
									
									
									
									
								
							@ -3,11 +3,6 @@ import static org.gradle.api.JavaVersion.VERSION_11
 | 
				
			|||||||
plugins {
 | 
					plugins {
 | 
				
			||||||
    id 'org.jetbrains.kotlin.jvm'
 | 
					    id 'org.jetbrains.kotlin.jvm'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 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 'net.corda.cordapp.cordapp-configuration'
 | 
					    id 'net.corda.cordapp.cordapp-configuration'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    id 'org.jetbrains.kotlin.plugin.jpa'
 | 
					    id 'org.jetbrains.kotlin.plugin.jpa'
 | 
				
			||||||
@ -18,36 +13,14 @@ plugins {
 | 
				
			|||||||
    id 'csde'
 | 
					    id 'csde'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					allprojects {
 | 
				
			||||||
    group 'com.r3.hellocorda'
 | 
					    group 'com.r3.hellocorda'
 | 
				
			||||||
    version '1.0-SNAPSHOT'
 | 
					    version '1.0-SNAPSHOT'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def javaVersion = VERSION_11
 | 
					    def javaVersion = VERSION_11
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The CordApp section.
 | 
					    // Declare the set of Java compiler options we need to build a CorDapp.
 | 
				
			||||||
// 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 component’s
 | 
					 | 
				
			||||||
// 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 "ModuleNameHere"
 | 
					 | 
				
			||||||
        versionId 1
 | 
					 | 
				
			||||||
        vendor "VendorNameHere"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Declare the set of Kotlin compiler options we need to build a CorDapp.
 | 
					 | 
				
			||||||
    tasks.withType(JavaCompile) {
 | 
					    tasks.withType(JavaCompile) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // -parameters - Needed for reflection and serialization to work correctly.
 | 
					        // -parameters - Needed for reflection and serialization to work correctly.
 | 
				
			||||||
        options.compilerArgs += [
 | 
					        options.compilerArgs += [
 | 
				
			||||||
                "-parameters"
 | 
					                "-parameters"
 | 
				
			||||||
@ -57,65 +30,14 @@ tasks.withType(JavaCompile) {
 | 
				
			|||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
        // All dependencies are held in Maven Central
 | 
					        // All dependencies are held in Maven Central
 | 
				
			||||||
        mavenCentral()
 | 
					        mavenCentral()
 | 
				
			||||||
    maven {
 | 
					 | 
				
			||||||
        url = "$artifactoryContextUrl/"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Declare dependencies for the modules we will use.
 | 
					    tasks.withType(Test).configureEach {
 | 
				
			||||||
// 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'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 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:$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"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test {
 | 
					 | 
				
			||||||
        useJUnitPlatform()
 | 
					        useJUnitPlatform()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
publishing {
 | 
					publishing {
 | 
				
			||||||
    publications {
 | 
					    publications {
 | 
				
			||||||
        maven(MavenPublication) {
 | 
					        maven(MavenPublication) {
 | 
				
			||||||
@ -123,5 +45,7 @@ publishing {
 | 
				
			|||||||
            groupId project.group
 | 
					            groupId project.group
 | 
				
			||||||
            artifact jar
 | 
					            artifact jar
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   }
 | 
					   }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,9 +6,6 @@ plugins {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
repositories {
 | 
					repositories {
 | 
				
			||||||
    mavenCentral()
 | 
					    mavenCentral()
 | 
				
			||||||
    maven {
 | 
					 | 
				
			||||||
        url = "$artifactoryContextUrl/"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,4 @@
 | 
				
			|||||||
jacksonVersion = 2.13.3
 | 
					jacksonVersion = 2.13.4
 | 
				
			||||||
unirestVersion=3.13.10
 | 
					unirestVersion=3.13.10
 | 
				
			||||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# R3 internal repository
 | 
					cordaApiVersion=5.0.0.523-Fox1.0
 | 
				
			||||||
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
					plugins {
 | 
				
			||||||
    id 'java-library'
 | 
					    id 'java-library'
 | 
				
			||||||
@ -18,21 +38,37 @@ configurations {
 | 
				
			|||||||
        canBeResolved = true
 | 
					        canBeResolved = true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notaryServerCPB {
 | 
				
			||||||
 | 
					        canBeConsumed = false
 | 
				
			||||||
 | 
					        canBeResolved = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Dependencies for supporting tools
 | 
					// Dependencies for supporting tools
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
    combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
 | 
					    combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
 | 
				
			||||||
    myPostgresJDBC 'org.postgresql:postgresql:42.4.1'
 | 
					    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"
 | 
					    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.getenv("CSDE_CORDA_BIN") ?: System.getProperty('user.home') + "/.corda/corda5"
 | 
				
			||||||
def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5"
 | 
					def cordaCliBinDir =  System.getenv("CSDE_CORDA_CLI") ?:System.getProperty('user.home') + "/.corda/cli"
 | 
				
			||||||
def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli"
 | 
					 | 
				
			||||||
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
 | 
					def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
 | 
				
			||||||
 | 
					def cordaNotaryServerDir = cordaBinDir + "/notaryserver"
 | 
				
			||||||
def signingCertAlias="gradle-plugin-default-key"
 | 
					def signingCertAlias="gradle-plugin-default-key"
 | 
				
			||||||
// Get error if this is not a autotyped object
 | 
					// Get error if this is not a autotyped object
 | 
				
			||||||
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
 | 
					// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
 | 
				
			||||||
@ -41,25 +77,75 @@ def keystoreAlias = "my-signing-key"
 | 
				
			|||||||
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
 | 
					def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
 | 
				
			||||||
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
 | 
					def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
 | 
				
			||||||
def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat"
 | 
					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
 | 
					// todo: Need to read things from cordapp plugin - the cordapp names will be changed by the user
 | 
				
			||||||
def cpiName =  'cpi name'
 | 
					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(),
 | 
					        cordaClusterURL.toString(),
 | 
				
			||||||
        cordaRpcUser,
 | 
					        cordaRpcUser,
 | 
				
			||||||
        cordaRpcPasswd,
 | 
					        cordaRpcPasswd,
 | 
				
			||||||
        devEnvWorkspace,
 | 
					        devEnvWorkspace,
 | 
				
			||||||
 | 
					        // todo: why is this not obtained in the groovy def's abouve - its inconsistent.
 | 
				
			||||||
        new String("${System.getProperty("java.home")}/bin"),
 | 
					        new String("${System.getProperty("java.home")}/bin"),
 | 
				
			||||||
        dbContainerName,
 | 
					        dbContainerName,
 | 
				
			||||||
        cordaJDBCDir,
 | 
					        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") {
 | 
					tasks.register("getPostgresJDBC") {
 | 
				
			||||||
    group = pluginImplGroupName
 | 
					    group = supportingGroup
 | 
				
			||||||
    doLast {
 | 
					    doLast {
 | 
				
			||||||
        copy {
 | 
					        copy {
 | 
				
			||||||
            from configurations.myPostgresJDBC
 | 
					            from configurations.myPostgresJDBC
 | 
				
			||||||
@ -68,183 +154,99 @@ 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) {
 | 
					tasks.register("getDevCordaLite", Copy) {
 | 
				
			||||||
    group = pluginImplGroupName
 | 
					    group = supportingGroup
 | 
				
			||||||
    from configurations.combinedWorker
 | 
					    from configurations.combinedWorker
 | 
				
			||||||
    into cordaBinDir
 | 
					    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"
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            // Need to 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') {
 | 
					tasks.register('listVNodes') {
 | 
				
			||||||
    group = pluginGroupName
 | 
					    group = queriesGroup
 | 
				
			||||||
    doLast {
 | 
					    doLast {
 | 
				
			||||||
        csdeHelper.listVNodes()
 | 
					        cordaStatusQueries.listVNodes()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tasks.register('listCPIs') {
 | 
					tasks.register('listCPIs') {
 | 
				
			||||||
    group = pluginImplGroupName
 | 
					    group = queriesGroup
 | 
				
			||||||
    doLast {
 | 
					    doLast {
 | 
				
			||||||
        csdeHelper.listCPIs()
 | 
					        cordaStatusQueries.listCPIs()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Build CPI tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def buildCPIsHelper = new BuildCPIsHelper(projectContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tasks.register("1-createGroupPolicy") {
 | 
				
			||||||
 | 
					    group = cordappGroup
 | 
				
			||||||
 | 
					    dependsOn('projInit')
 | 
				
			||||||
 | 
					    doLast {
 | 
				
			||||||
 | 
					        buildCPIsHelper.createGroupPolicy()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tasks.register("getNotaryServerCPB", Copy) {
 | 
				
			||||||
 | 
					    group = supportingGroup
 | 
				
			||||||
 | 
					    from configurations.notaryServerCPB
 | 
				
			||||||
 | 
					    into cordaNotaryServerDir
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tasks.register('2-createKeystore') {
 | 
				
			||||||
 | 
					    group = cordappGroup
 | 
				
			||||||
 | 
					    dependsOn('projInit')
 | 
				
			||||||
 | 
					    doLast {
 | 
				
			||||||
 | 
					        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.
 | 
					// Empty task, just acts as the Task user entry point task.
 | 
				
			||||||
tasks.register('deployCordapp') {
 | 
					tasks.register('quickDeployCordapp') {
 | 
				
			||||||
    group = pluginGroupName
 | 
					    group = cordappGroup
 | 
				
			||||||
    dependsOn("createAndRegVNodes")
 | 
					    dependsOn("5-createAndRegVNodes")
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
tasks.register("startCorda") {
 | 
					 | 
				
			||||||
    group = pluginGroupName
 | 
					 | 
				
			||||||
    dependsOn('getDevCordaLite', 'getPostgresJDBC')
 | 
					 | 
				
			||||||
    doLast {
 | 
					 | 
				
			||||||
        mkdir devEnvWorkspace
 | 
					 | 
				
			||||||
        csdeHelper.startCorda()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
tasks.register("stopCorda") {
 | 
					 | 
				
			||||||
    group = pluginGroupName
 | 
					 | 
				
			||||||
    doLast {
 | 
					 | 
				
			||||||
        csdeHelper.stopCorda()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										270
									
								
								buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java
									
									
									
									
									
										Normal 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) + " ");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										94
									
								
								buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java
									
									
									
									
									
										Normal 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?");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										63
									
								
								buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java
									
									
									
									
									
										Normal 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"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,9 @@
 | 
				
			|||||||
package com.r3.csde;
 | 
					package com.r3.csde;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class CsdeException extends Exception {
 | 
					public class CsdeException extends Exception {
 | 
				
			||||||
 | 
					    public CsdeException(String message, Throwable cause) {
 | 
				
			||||||
 | 
					        super(message, cause);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    public CsdeException(String message){
 | 
					    public CsdeException(String message){
 | 
				
			||||||
        super(message);
 | 
					        super(message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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 on success
 | 
					 | 
				
			||||||
        if(status == 200) {
 | 
					 | 
				
			||||||
            // Keep retrying until we get "OK" may move through "Validateing upload", "Persisting CPI"
 | 
					 | 
				
			||||||
            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());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // We 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<>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // For each identity check that it already exists.
 | 
					 | 
				
			||||||
        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.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // need to 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?");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										188
									
								
								buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java
									
									
									
									
									
										Normal 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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,7 +0,0 @@
 | 
				
			|||||||
package com.r3.csde;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class NoPidFile extends Exception {
 | 
					 | 
				
			||||||
    public NoPidFile(String message){
 | 
					 | 
				
			||||||
        super(message);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										83
									
								
								buildSrc/src/main/java/com/r3/csde/ProjectContext.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								buildSrc/src/main/java/com/r3/csde/ProjectContext.java
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										71
									
								
								buildSrc/src/main/java/com/r3/csde/ProjectUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								buildSrc/src/main/java/com/r3/csde/ProjectUtils.java
									
									
									
									
									
										Normal 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.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +0,0 @@
 | 
				
			|||||||
import com.r3.csde.CsdeRpcInterface;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class CsdeRpcInterfaceTests {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -2,6 +2,11 @@
 | 
				
			|||||||
 "identities" : [
 | 
					 "identities" : [
 | 
				
			||||||
  "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
 | 
					  "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
 | 
				
			||||||
  "CN=Bob, 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
									
								
							
							
						
						
									
										91
									
								
								contracts/build.gradle
									
									
									
									
									
										Normal 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 component’s
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,11 +2,18 @@ kotlin.code.style=official
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Specify the version of the Corda-API to use.
 | 
					# 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.
 | 
					# 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.523-Fox1.0
 | 
				
			||||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
 | 
					
 | 
				
			||||||
 | 
					# Settings For Development Utilities
 | 
				
			||||||
 | 
					combinedWorkerVersion=5.0.0.0-Fox1.0
 | 
				
			||||||
 | 
					simulatorVersion=5.0.0.0-Fox1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
 | 
					# Specify the version of the cordapp-cpb and cordapp-cpk plugins
 | 
				
			||||||
cordaPluginsVersion=7.0.0
 | 
					cordaPluginsVersion=7.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# For the time being this just needs to be set to a dummy value.
 | 
					# For the time being this just needs to be set to a dummy value.
 | 
				
			||||||
platformVersion = 999
 | 
					platformVersion = 999
 | 
				
			||||||
@ -24,15 +31,9 @@ mockitoKotlinVersion=4.0.0
 | 
				
			|||||||
mockitoVersion=4.6.1
 | 
					mockitoVersion=4.6.1
 | 
				
			||||||
hamcrestVersion=2.2
 | 
					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
 | 
					cordaClusterURL=https://localhost:8888
 | 
				
			||||||
cordaRpcUser=admin
 | 
					cordaRpcUser=admin
 | 
				
			||||||
cordaRpcPasswd=admin
 | 
					cordaRpcPasswd=admin
 | 
				
			||||||
devEnvWorkspace=workspace
 | 
					devEnvWorkspace=workspace
 | 
				
			||||||
dbContainerName=CSDEpostgresql
 | 
					dbContainerName=CSDEpostgresql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# R3 internal repository
 | 
					 | 
				
			||||||
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
 | 
					 | 
				
			||||||
@ -2,9 +2,7 @@ pluginManagement {
 | 
				
			|||||||
    // Declare the repositories where plugins are stored.
 | 
					    // Declare the repositories where plugins are stored.
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
        gradlePluginPortal()
 | 
					        gradlePluginPortal()
 | 
				
			||||||
        maven {
 | 
					        mavenCentral()
 | 
				
			||||||
            url = "$artifactoryContextUrl/"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //  The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
 | 
					    //  The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
 | 
				
			||||||
@ -21,5 +19,9 @@ pluginManagement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
 | 
					// 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'
 | 
					rootProject.name = 'csde-cordapp-template-java'
 | 
				
			||||||
 | 
					include ':workflows'
 | 
				
			||||||
 | 
					include ':contracts'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +0,0 @@
 | 
				
			|||||||
package com.r3.developers.csdetemplate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import net.corda.v5.base.types.MemberX500Name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// A class to hold the arguments required to start the flow
 | 
					 | 
				
			||||||
public class MyFirstFlowStartArgs {
 | 
					 | 
				
			||||||
    public MemberX500Name otherMember;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public MemberX500Name getOtherMember() {
 | 
					 | 
				
			||||||
        return otherMember;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public MyFirstFlowStartArgs(MemberX500Name otherMember) {
 | 
					 | 
				
			||||||
        this.otherMember = otherMember;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Without the following we get
 | 
					 | 
				
			||||||
    // "Cannot construct instance of `com.r3.developers.csdetemplate.MyFirstFlowStartArgs` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (String)\"{\"otherMember\":\"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB\"}\"; line: 1, column: 2]"
 | 
					 | 
				
			||||||
    public MyFirstFlowStartArgs() {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										94
									
								
								workflows/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								workflows/build.gradle
									
									
									
									
									
										Normal 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 component’s
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,17 +1,17 @@
 | 
				
			|||||||
package com.r3.developers.csdetemplate;
 | 
					package com.r3.developers.csdetemplate.workflows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import net.corda.v5.base.annotations.CordaSerializable;
 | 
					import net.corda.v5.base.annotations.CordaSerializable;
 | 
				
			||||||
import net.corda.v5.base.types.MemberX500Name;
 | 
					import net.corda.v5.base.types.MemberX500Name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// A class which will contain a message, It must be marked with @CordaSerializable for Corda
 | 
					// Where a class contains a message, mark it with @CordaSerializable to enable Corda to 
 | 
				
			||||||
// to be able to send from one virtual node to another.
 | 
					// send it from one virtual node to another.
 | 
				
			||||||
@CordaSerializable
 | 
					@CordaSerializable
 | 
				
			||||||
public class Message {
 | 
					public class Message {
 | 
				
			||||||
    // public Message() {}
 | 
					 | 
				
			||||||
    public Message(MemberX500Name sender, String message) {
 | 
					    public Message(MemberX500Name sender, String message) {
 | 
				
			||||||
        this.sender = sender;
 | 
					        this.sender = sender;
 | 
				
			||||||
        this.message = message;
 | 
					        this.message = message;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MemberX500Name getSender() {
 | 
					    public MemberX500Name getSender() {
 | 
				
			||||||
        return sender;
 | 
					        return sender;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -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.flows.*;
 | 
				
			||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
 | 
					import net.corda.v5.application.marshalling.JsonMarshallingService;
 | 
				
			||||||
@ -14,50 +14,50 @@ import org.slf4j.LoggerFactory;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// MyFirstFlow is an initiating flow, it's corresponding responder flow is called MyFirstFlowResponder (defined below)
 | 
					// MyFirstFlow is an initiating flow, it's corresponding responder flow is called MyFirstFlowResponder (defined below)
 | 
				
			||||||
// to link the two sides of the flow together they need to have the same protocol.
 | 
					// to link the two sides of the flow together they need to have the same protocol.
 | 
				
			||||||
@InitiatingFlow(protocol = "another-flow")
 | 
					@InitiatingFlow(protocol = "my-first-flow")
 | 
				
			||||||
// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
 | 
					// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
 | 
				
			||||||
public class MyFirstFlow implements RPCStartableFlow {
 | 
					public class MyFirstFlow implements RPCStartableFlow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // It is useful to be able to log messages from the flows for debugging.
 | 
					    // Log messages from the flows for debugging.
 | 
				
			||||||
    private final Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
 | 
					    private final Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Corda has a set of injectable services which are injected into the flow at runtime.
 | 
					    // Corda has a set of injectable services which are injected into the flow at runtime.
 | 
				
			||||||
    // Flows declare them with @CordaInjectable, then the flows have access to their services.
 | 
					    // Flows declare them with @CordaInjectable, then the flows have access to their services.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // JsonMarshallingService provides a Service for manipulating json
 | 
					    // JsonMarshallingService provides a service for manipulating JSON.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public JsonMarshallingService jsonMarshallingService;
 | 
					    public JsonMarshallingService jsonMarshallingService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // FlowMessaging provides a service for establishing flow sessions between Virtual Nodes and
 | 
					    // FlowMessaging provides a service that establishes flow sessions between virtual nodes 
 | 
				
			||||||
    // sending and receiving payloads between them
 | 
					    // that send and receive payloads between them.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public FlowMessaging flowMessaging;
 | 
					    public FlowMessaging flowMessaging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // MemberLookup provides a service for looking up information about members of the Virtual Network which
 | 
					     // MemberLookup provides a service for looking up information about members of the virtual network which
 | 
				
			||||||
    // this CorDapp is operating in.
 | 
					     // this CorDapp operates in.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public MemberLookup memberLookup;
 | 
					    public MemberLookup memberLookup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MyFirstFlow() {}
 | 
					    public MyFirstFlow() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // When a flow is invoked it's call() method is called.
 | 
					    // When a flow is invoked its call() method is called.
 | 
				
			||||||
    // call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
 | 
					    // Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
 | 
				
			||||||
    // for a response from the other flows and services
 | 
					    // for a response from the other flows and services.
 | 
				
			||||||
    @NotNull
 | 
					    @NotNull
 | 
				
			||||||
    @Suspendable
 | 
					    @Suspendable
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public String call(@NotNull RPCRequestData requestBody) {
 | 
					    public String call(RPCRequestData requestBody) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Useful logging to follow what's happening in the console or logs
 | 
					        // Follow what happens in the console or logs.
 | 
				
			||||||
        log.info("MFF: MyFirstFlow.call() called");
 | 
					        log.info("MFF: MyFirstFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Show the requestBody in the logs - this can be used to help establish the format for starting a flow on corda
 | 
					        // Show the requestBody in the logs - this can be used to help establish the format for starting a flow on Corda.
 | 
				
			||||||
        log.info("MFF: requestBody: " + requestBody.getRequestBody());
 | 
					        log.info("MFF: requestBody: " + requestBody.getRequestBody());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation Service
 | 
					        // Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation service.
 | 
				
			||||||
        MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
 | 
					        MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Obtain the MemberX500Name of counterparty
 | 
					        // Obtain the MemberX500Name of the counterparty.
 | 
				
			||||||
        MemberX500Name otherMember = flowArgs.otherMember;
 | 
					        MemberX500Name otherMember = flowArgs.otherMember;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get our identity from the MemberLookup service.
 | 
					        // Get our identity from the MemberLookup service.
 | 
				
			||||||
@ -69,19 +69,19 @@ public class MyFirstFlow implements RPCStartableFlow {
 | 
				
			|||||||
        // Log the message to be sent.
 | 
					        // Log the message to be sent.
 | 
				
			||||||
        log.info("MFF: message.message: " + message.message);
 | 
					        log.info("MFF: message.message: " + message.message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Start a flow session with the otherMember using the FlowMessaging service
 | 
					        // Start a flow session with the otherMember using the FlowMessaging service.
 | 
				
			||||||
        // The otherMember's Virtual Node will run the corresponding MyFirstFlowResponder responder flow
 | 
					        // The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow.
 | 
				
			||||||
        FlowSession session = flowMessaging.initiateFlow(otherMember);
 | 
					        FlowSession session = flowMessaging.initiateFlow(otherMember);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Send the Payload using the send method on the session to the MyFirstFlowResponder Responder flow
 | 
					        // Send the Payload using the send method on the session to the MyFirstFlowResponder responder flow.
 | 
				
			||||||
        session.send(message);
 | 
					        session.send(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Receive a response from the Responder flow
 | 
					        // Receive a response from the responder flow.
 | 
				
			||||||
        Message response = session.receive(Message.class);
 | 
					        Message response = session.receive(Message.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // The return value of a RPCStartableFlow must always be a String, this string will be passed
 | 
					        // The return value of a RPCStartableFlow must always be a String. This will be passed
 | 
				
			||||||
        // back as the REST RPC response when the status of the flow is queried on Corda, or as the return
 | 
					        // back as the REST RPC response when the status of the flow is queried on Corda, or as the return
 | 
				
			||||||
        // value from the flow when testing using the Simulator
 | 
					        // value from the flow when testing using the simulator.
 | 
				
			||||||
        return response.message;
 | 
					        return response.message;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -91,7 +91,7 @@ public class MyFirstFlow implements RPCStartableFlow {
 | 
				
			|||||||
RequestBody for triggering the flow via http-rpc:
 | 
					RequestBody for triggering the flow via http-rpc:
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "clientRequestId": "r1",
 | 
					    "clientRequestId": "r1",
 | 
				
			||||||
    "flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
 | 
					    "flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow",
 | 
				
			||||||
    "requestData": {
 | 
					    "requestData": {
 | 
				
			||||||
        "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
 | 
					        "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -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.CordaInject;
 | 
				
			||||||
import net.corda.v5.application.flows.InitiatedBy;
 | 
					import net.corda.v5.application.flows.InitiatedBy;
 | 
				
			||||||
@ -11,36 +11,36 @@ import org.slf4j.Logger;
 | 
				
			|||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MyFirstFlowResponder is a responder flow, it's corresponding initiating flow is called MyFirstFlow (defined above)
 | 
					// MyFirstFlowResponder is a responder flow, its corresponding initiating flow is called MyFirstFlow (defined in MyFirstFlow.java)
 | 
				
			||||||
// to link the two sides of the flow together they need to have the same protocol.
 | 
					// to link the two sides of the flow together they need to have the same protocol.
 | 
				
			||||||
@InitiatedBy(protocol = "another-flow")
 | 
					@InitiatedBy(protocol = "my-first-flow")
 | 
				
			||||||
// Responder flows must inherit from ResponderFlow
 | 
					// Responder flows must inherit from ResponderFlow
 | 
				
			||||||
public class MyFirstFlowResponder implements ResponderFlow {
 | 
					public class MyFirstFlowResponder implements ResponderFlow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // It is useful to be able to log messages from the flows for debugging.
 | 
					    // Log messages from the flows for debugging.
 | 
				
			||||||
    private final Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
 | 
					    private final Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // MemberLookup provides a service for looking up information about members of the Virtual Network which
 | 
					    // MemberLookup looks for information about members of the virtual network which 
 | 
				
			||||||
    // this CorDapp is operating in.
 | 
					    // this CorDapp operates in. 
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public MemberLookup memberLookup;
 | 
					    public MemberLookup memberLookup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MyFirstFlowResponder() {}
 | 
					    public MyFirstFlowResponder() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Responder flows are invoked when an initiating flow makes a call via a session set up with the Virtual
 | 
					    // Responder flows are invoked when an initiating flow makes a call via a session set up with the virtual
 | 
				
			||||||
    // node hosting the Responder flow. When a responder flow is invoked it's call() method is called.
 | 
					    // node hosting the responder flow. When a responder flow is invoked its call() method is called.
 | 
				
			||||||
    // call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
 | 
					    // Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
 | 
				
			||||||
    // for a response from the other flows and services/
 | 
					    // for a response from the other flows and services.
 | 
				
			||||||
    // The Call method has the flow session passed in as a parameter by Corda so the session is available to
 | 
					    // The Call method has the flow session passed in as a parameter by Corda so the session is available to
 | 
				
			||||||
    // responder flow code, you don't need to inject the FlowMessaging service.
 | 
					    // responder flow code, you don't need to inject the FlowMessaging service.
 | 
				
			||||||
    @Suspendable
 | 
					    @Suspendable
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void call(FlowSession session) {
 | 
					    public void call(FlowSession session) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Useful logging to follow what's happening in the console or logs
 | 
					        // Follow what happens in the console or logs.
 | 
				
			||||||
        log.info("MFF: MyFirstResponderFlow.call() called");
 | 
					        log.info("MFF: MyFirstResponderFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Receive the payload and deserialize it into a Message class
 | 
					        // Receive the payload and deserialize it into a message class.
 | 
				
			||||||
        Message receivedMessage = session.receive(Message.class);
 | 
					        Message receivedMessage = session.receive(Message.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Log the message as a proxy for performing some useful operation on it.
 | 
					        // Log the message as a proxy for performing some useful operation on it.
 | 
				
			||||||
@ -49,7 +49,7 @@ public class MyFirstFlowResponder implements ResponderFlow {
 | 
				
			|||||||
        // Get our identity from the MemberLookup service.
 | 
					        // Get our identity from the MemberLookup service.
 | 
				
			||||||
        MemberX500Name ourIdentity = memberLookup.myInfo().getName();
 | 
					        MemberX500Name ourIdentity = memberLookup.myInfo().getName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Create a response to greet the sender
 | 
					        // Create a message to greet the sender.
 | 
				
			||||||
        Message response = new Message(ourIdentity,
 | 
					        Message response = new Message(ourIdentity,
 | 
				
			||||||
                "Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
 | 
					                "Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					package com.r3.developers.csdetemplate.workflows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.corda.v5.base.types.MemberX500Name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A class to hold the arguments required to start the flow
 | 
				
			||||||
 | 
					public class MyFirstFlowStartArgs {
 | 
				
			||||||
 | 
					    public MemberX500Name otherMember;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public MyFirstFlowStartArgs(MemberX500Name otherMember) {
 | 
				
			||||||
 | 
					        this.otherMember = otherMember;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The JSON Marshalling Service, which handles serialisation, needs this constructor.
 | 
				
			||||||
 | 
					    public MyFirstFlowStartArgs() {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,50 +1,41 @@
 | 
				
			|||||||
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.RequestData;
 | 
				
			||||||
import net.corda.simulator.SimulatedVirtualNode;
 | 
					import net.corda.simulator.SimulatedVirtualNode;
 | 
				
			||||||
import net.corda.simulator.Simulator;
 | 
					import net.corda.simulator.Simulator;
 | 
				
			||||||
import net.corda.v5.base.types.MemberX500Name;
 | 
					import net.corda.v5.base.types.MemberX500Name;
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class MyFirstFlowTest {
 | 
					class MyFirstFlowTest {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Names picked to match the corda network in config/dev-net.json
 | 
					    // 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 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");
 | 
					    private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
 | 
					    @SuppressWarnings("unchecked")
 | 
				
			||||||
    public void test_that_MyFirstFLow_returns_correct_message() {
 | 
					    public void test_that_MyFirstFLow_returns_correct_message() {
 | 
				
			||||||
 | 
					        // Instantiate an instance of the simulator.
 | 
				
			||||||
        // Instantiate an instance of the Simulator
 | 
					 | 
				
			||||||
        Simulator simulator = new Simulator();
 | 
					        Simulator simulator = new Simulator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Create Alice's and Bob HoldingIDs
 | 
					        // Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
 | 
				
			||||||
        HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500);
 | 
					        // Don't assign Bob's virtual node to a value. You don't need it for this particular test.
 | 
				
			||||||
        HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500);
 | 
					        SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
 | 
				
			||||||
 | 
					        simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Create Alice and Bob's virtual nodes, including the Class's of the flows which will be registered on each node.
 | 
					        // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
 | 
				
			||||||
        // We don't assign Bob's virtual node to a val because we don't need it for this particular test.
 | 
					 | 
				
			||||||
        SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class);
 | 
					 | 
				
			||||||
        simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow
 | 
					 | 
				
			||||||
        MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
 | 
					        MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Create a requestData object
 | 
					        // Create a requestData object.
 | 
				
			||||||
        RequestData requestData = RequestData.Companion.create(
 | 
					        RequestData requestData = RequestData.Companion.create(
 | 
				
			||||||
                "request no 1",        // A unique reference for the instance of the flow request
 | 
					                "request no 1",        // A unique reference for the instance of the flow request.
 | 
				
			||||||
                MyFirstFlow.class,        // The name of the flow class which is to be started
 | 
					                MyFirstFlow.class,              // The name of the flow class which is to be started.
 | 
				
			||||||
                myFirstFlowStartArgs            // The object which contains the start arguments of the flow
 | 
					                myFirstFlowStartArgs            // The object which contains the start arguments of the flow.
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Call the Flow on Alice's virtual node and capture the response from the flow
 | 
					        // Call the flow on Alice's virtual node and capture the response.
 | 
				
			||||||
        String flowResponse = aliceVN.callFlow(requestData);
 | 
					        String flowResponse = aliceVN.callFlow(requestData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check that the flow has returned the expected string
 | 
					        // Check that the flow has returned the expected string.
 | 
				
			||||||
        assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
 | 
					        assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user