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
					
				
							
								
								
									
										4
									
								
								.ci/Jenkinsfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.ci/Jenkinsfile
									
									
									
									
										vendored
									
									
								
							@ -1,5 +1,5 @@
 | 
			
		||||
@Library('corda-shared-build-pipeline-steps@chrisbarratt/CORE-8075-create-pipeline-for-running-the-autotester') _
 | 
			
		||||
 | 
			
		||||
@Library('corda-shared-build-pipeline-steps@5.0') _
 | 
			
		||||
 | 
			
		||||
cordaPipeline(
 | 
			
		||||
    nexusAppId: 'com.corda.CSDE-Java.5.0',
 | 
			
		||||
    publishRepoPrefix: '',
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,8 @@
 | 
			
		||||
# 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). 
 | 
			
		||||
 | 
			
		||||
The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local machine. The CSDE provides:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										122
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								build.gradle
									
									
									
									
									
								
							@ -3,11 +3,6 @@ import static org.gradle.api.JavaVersion.VERSION_11
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'org.jetbrains.kotlin.jvm'
 | 
			
		||||
 | 
			
		||||
    // Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
 | 
			
		||||
    // These extend existing build environment so that CPB and CPK files can be built.
 | 
			
		||||
    // This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
 | 
			
		||||
    // required by Corda.
 | 
			
		||||
    id 'net.corda.plugins.cordapp-cpb2'
 | 
			
		||||
    id 'net.corda.cordapp.cordapp-configuration'
 | 
			
		||||
 | 
			
		||||
    id 'org.jetbrains.kotlin.plugin.jpa'
 | 
			
		||||
@ -18,110 +13,39 @@ plugins {
 | 
			
		||||
    id 'csde'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
group 'com.r3.hellocorda'
 | 
			
		||||
version '1.0-SNAPSHOT'
 | 
			
		||||
allprojects {
 | 
			
		||||
    group 'com.r3.hellocorda'
 | 
			
		||||
    version '1.0-SNAPSHOT'
 | 
			
		||||
 | 
			
		||||
def javaVersion = VERSION_11
 | 
			
		||||
    def javaVersion = VERSION_11
 | 
			
		||||
 | 
			
		||||
// The CordApp section.
 | 
			
		||||
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
 | 
			
		||||
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the 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 Java compiler options we need to build a CorDapp.
 | 
			
		||||
    tasks.withType(JavaCompile) {
 | 
			
		||||
        // -parameters - Needed for reflection and serialization to work correctly.
 | 
			
		||||
        options.compilerArgs += [
 | 
			
		||||
                "-parameters"
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Declare the set of Kotlin compiler options we need to build a CorDapp.
 | 
			
		||||
tasks.withType(JavaCompile) {
 | 
			
		||||
 | 
			
		||||
    // -parameters - Needed for reflection and serialization to work correctly.
 | 
			
		||||
    options.compilerArgs += [
 | 
			
		||||
            "-parameters"
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    // All dependencies are held in Maven Central
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
    maven {
 | 
			
		||||
        url = "$artifactoryContextUrl/"
 | 
			
		||||
    repositories {
 | 
			
		||||
        // All dependencies are held in Maven Central
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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'
 | 
			
		||||
    tasks.withType(Test).configureEach {
 | 
			
		||||
        useJUnitPlatform()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
publishing {
 | 
			
		||||
    publications {
 | 
			
		||||
            maven(MavenPublication) {
 | 
			
		||||
                artifactId "corda-CSDE-java-sample"
 | 
			
		||||
                groupId project.group
 | 
			
		||||
                artifact jar
 | 
			
		||||
            }
 | 
			
		||||
        maven(MavenPublication) {
 | 
			
		||||
            artifactId "corda-CSDE-java-sample"
 | 
			
		||||
            groupId project.group
 | 
			
		||||
            artifact jar
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,6 @@ plugins {
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
    maven {
 | 
			
		||||
        url = "$artifactoryContextUrl/"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,4 @@
 | 
			
		||||
jacksonVersion = 2.13.3
 | 
			
		||||
jacksonVersion = 2.13.4
 | 
			
		||||
unirestVersion=3.13.10
 | 
			
		||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
 | 
			
		||||
 | 
			
		||||
# R3 internal repository
 | 
			
		||||
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00
 | 
			
		||||
cordaApiVersion=5.0.0.523-Fox1.0
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,24 @@
 | 
			
		||||
import com.r3.csde.CsdeRpcInterface
 | 
			
		||||
// Note, IntelliJ does not recognise the imported Java Classes, hence they are
 | 
			
		||||
// highlighted in Red. However, they are recognised in the gradle compilation.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// todo: look at the declaration of the script variables, can they be combined with the declaration of the Project Context
 | 
			
		||||
// todo: investigate adding corda-cli to the class path then executing it directly - might not work as gradle has to set up the jar file, so its not their when you start.
 | 
			
		||||
// Todo: write a test flow runner helper function??
 | 
			
		||||
// todo: rename deployCPIsHelper
 | 
			
		||||
// todo: add proper logging, rather than reading Stdout
 | 
			
		||||
// todo: add test corda running/live task
 | 
			
		||||
// todo: add a test to check docker is running and display error if not + halt start corda
 | 
			
		||||
// todo: add a clean corda task.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import com.r3.csde.CordaLifeCycleHelper
 | 
			
		||||
import com.r3.csde.ProjectContext
 | 
			
		||||
import com.r3.csde.DeployCPIsHelper
 | 
			
		||||
import com.r3.csde.BuildCPIsHelper
 | 
			
		||||
import com.r3.csde.ProjectUtils
 | 
			
		||||
import com.r3.csde.CordaStatusQueries
 | 
			
		||||
import com.r3.csde.CreateAndRegisterVNodesHelper
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'java-library'
 | 
			
		||||
@ -18,21 +38,37 @@ configurations {
 | 
			
		||||
        canBeResolved = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    notaryServerCPB {
 | 
			
		||||
        canBeConsumed = false
 | 
			
		||||
        canBeResolved = true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dependencies for supporting tools
 | 
			
		||||
dependencies {
 | 
			
		||||
    combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
 | 
			
		||||
    myPostgresJDBC 'org.postgresql:postgresql:42.4.1'
 | 
			
		||||
    notaryServerCPB("com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-server:$cordaNotaryPluginsVersion") {
 | 
			
		||||
        artifact {
 | 
			
		||||
            classifier = 'package'
 | 
			
		||||
            extension = 'cpb'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    implementation "org.codehaus.groovy:groovy-json:3.0.9"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// task groupings
 | 
			
		||||
def cordaGroup = 'csde-corda'       // corda lifecycle tasks
 | 
			
		||||
def cordappGroup = 'csde-cordapp'   // tasks to build and deploy corDapps
 | 
			
		||||
def queriesGroup = 'csde-queries'   // tasks which query corda status
 | 
			
		||||
def supportingGroup = 'supporting'  // tasks which should be hidden from the csde user
 | 
			
		||||
 | 
			
		||||
def pluginGroupName = "CSDE"
 | 
			
		||||
def pluginImplGroupName = "other"
 | 
			
		||||
def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5"
 | 
			
		||||
def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli"
 | 
			
		||||
 | 
			
		||||
def cordaBinDir = System.getenv("CSDE_CORDA_BIN") ?: System.getProperty('user.home') + "/.corda/corda5"
 | 
			
		||||
def cordaCliBinDir =  System.getenv("CSDE_CORDA_CLI") ?:System.getProperty('user.home') + "/.corda/cli"
 | 
			
		||||
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
 | 
			
		||||
def cordaNotaryServerDir = cordaBinDir + "/notaryserver"
 | 
			
		||||
def signingCertAlias="gradle-plugin-default-key"
 | 
			
		||||
// Get error if this is not a autotyped object
 | 
			
		||||
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
 | 
			
		||||
@ -41,25 +77,75 @@ def keystoreAlias = "my-signing-key"
 | 
			
		||||
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
 | 
			
		||||
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
 | 
			
		||||
def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat"
 | 
			
		||||
// todo: can we rely on the build directory always being /workflow/build? aslo, is the
 | 
			
		||||
//  workflow directory the correct place to build the cpb to. shoudl it be the main build directory.
 | 
			
		||||
def workflowBuildDir = rootDir.toString() + "/workflows/build"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Need to read things from cordapp plugin
 | 
			
		||||
def cpiName =  'cpi name'
 | 
			
		||||
// todo: Need to read things from cordapp plugin - the cordapp names will be changed by the user
 | 
			
		||||
def appCpiName =  'cpi name'
 | 
			
		||||
def notaryCpiName =  'CSDE Notary Server CPI'
 | 
			
		||||
 | 
			
		||||
def csdeHelper = new CsdeRpcInterface(project,
 | 
			
		||||
 | 
			
		||||
// todo: there should be a better way to set up these project context variables.
 | 
			
		||||
def projectContext = new ProjectContext(project,
 | 
			
		||||
        cordaClusterURL.toString(),
 | 
			
		||||
        cordaRpcUser,
 | 
			
		||||
        cordaRpcPasswd,
 | 
			
		||||
        devEnvWorkspace,
 | 
			
		||||
        // todo: why is this not obtained in the groovy def's abouve - its inconsistent.
 | 
			
		||||
        new String("${System.getProperty("java.home")}/bin"),
 | 
			
		||||
        dbContainerName,
 | 
			
		||||
        cordaJDBCDir,
 | 
			
		||||
        combiWorkerPidCacheFile
 | 
			
		||||
        combiWorkerPidCacheFile,
 | 
			
		||||
        signingCertAlias,
 | 
			
		||||
        signingCertFName,
 | 
			
		||||
        keystoreAlias,
 | 
			
		||||
        keystoreFName,
 | 
			
		||||
        keystoreCertFName,
 | 
			
		||||
        appCpiName,
 | 
			
		||||
        notaryCpiName,
 | 
			
		||||
        devEnvWorkspace,
 | 
			
		||||
        cordaCliBinDir,
 | 
			
		||||
        cordaNotaryServerDir,
 | 
			
		||||
        workflowBuildDir,
 | 
			
		||||
        cordaNotaryPluginsVersion
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
def utils = new ProjectUtils()
 | 
			
		||||
 | 
			
		||||
// Initiate workspace folder
 | 
			
		||||
 | 
			
		||||
tasks.register('projInit') {
 | 
			
		||||
    group = supportingGroup
 | 
			
		||||
    doLast {
 | 
			
		||||
        mkdir devEnvWorkspace
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// CordaLifeCycle tasks
 | 
			
		||||
 | 
			
		||||
def cordaLifeCycle = new CordaLifeCycleHelper(projectContext)
 | 
			
		||||
 | 
			
		||||
tasks.register("startCorda") {
 | 
			
		||||
    group = cordaGroup
 | 
			
		||||
    dependsOn('getDevCordaLite', 'getPostgresJDBC')
 | 
			
		||||
    doLast {
 | 
			
		||||
        mkdir devEnvWorkspace
 | 
			
		||||
        cordaLifeCycle.startCorda()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("stopCorda") {
 | 
			
		||||
    group = cordaGroup
 | 
			
		||||
    doLast {
 | 
			
		||||
        cordaLifeCycle.stopCorda()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("getPostgresJDBC") {
 | 
			
		||||
    group = pluginImplGroupName
 | 
			
		||||
    group = supportingGroup
 | 
			
		||||
    doLast {
 | 
			
		||||
        copy {
 | 
			
		||||
            from configurations.myPostgresJDBC
 | 
			
		||||
@ -68,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) {
 | 
			
		||||
    group = pluginImplGroupName
 | 
			
		||||
    group = supportingGroup
 | 
			
		||||
    from configurations.combinedWorker
 | 
			
		||||
    into cordaBinDir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register('createKeystore') {
 | 
			
		||||
    group = pluginImplGroupName
 | 
			
		||||
    dependsOn('projInit')
 | 
			
		||||
    doLast {
 | 
			
		||||
        File keystoreFile = new File(keystoreFName)
 | 
			
		||||
        if(!keystoreFile.exists()) {
 | 
			
		||||
            println('createKeystore: Create a keystore')
 | 
			
		||||
            exec {
 | 
			
		||||
                commandLine "${System.getProperty("java.home")}/bin/keytool", "-genkeypair",
 | 
			
		||||
                        "-alias", keystoreAlias,
 | 
			
		||||
                        "-keystore", keystoreFName,
 | 
			
		||||
                        "-storepass", "keystore password",
 | 
			
		||||
                        "-dname", "CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB",
 | 
			
		||||
                        "-keyalg", "RSA",
 | 
			
		||||
                        "-storetype", "pkcs12",
 | 
			
		||||
                        "-validity", "4000"
 | 
			
		||||
            }
 | 
			
		||||
            // 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') {
 | 
			
		||||
    group = pluginGroupName
 | 
			
		||||
    group = queriesGroup
 | 
			
		||||
    doLast {
 | 
			
		||||
        csdeHelper.listVNodes()
 | 
			
		||||
        cordaStatusQueries.listVNodes()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register('listCPIs') {
 | 
			
		||||
    group = pluginImplGroupName
 | 
			
		||||
    group = queriesGroup
 | 
			
		||||
    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.
 | 
			
		||||
tasks.register('deployCordapp') {
 | 
			
		||||
    group = pluginGroupName
 | 
			
		||||
    dependsOn("createAndRegVNodes")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("startCorda") {
 | 
			
		||||
    group = pluginGroupName
 | 
			
		||||
    dependsOn('getDevCordaLite', 'getPostgresJDBC')
 | 
			
		||||
    doLast {
 | 
			
		||||
        mkdir devEnvWorkspace
 | 
			
		||||
        csdeHelper.startCorda()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("stopCorda") {
 | 
			
		||||
    group = pluginGroupName
 | 
			
		||||
    doLast {
 | 
			
		||||
        csdeHelper.stopCorda()
 | 
			
		||||
    }
 | 
			
		||||
tasks.register('quickDeployCordapp') {
 | 
			
		||||
    group = cordappGroup
 | 
			
		||||
    dependsOn("5-createAndRegVNodes")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
 | 
			
		||||
public class CsdeException extends Exception {
 | 
			
		||||
    public CsdeException(String message, Throwable cause) {
 | 
			
		||||
        super(message, cause);
 | 
			
		||||
    }
 | 
			
		||||
    public CsdeException(String 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 {
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
"identities" : [
 | 
			
		||||
 "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
 | 
			
		||||
 "CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
 | 
			
		||||
 "CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB"
 | 
			
		||||
 ]
 | 
			
		||||
 "identities" : [
 | 
			
		||||
  "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
 | 
			
		||||
  "CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
 | 
			
		||||
  "CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
 | 
			
		||||
  "CN=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.
 | 
			
		||||
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
 | 
			
		||||
# cordaApiVersion=5.0.0.190-DevPreview-2
 | 
			
		||||
cordaApiVersion=5.0.0.505-Beta1.0-HC00
 | 
			
		||||
cordaApiVersion=5.0.0.523-Fox1.0
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
cordaPluginsVersion=7.0.0
 | 
			
		||||
cordaPluginsVersion=7.0.1
 | 
			
		||||
 | 
			
		||||
# For the time being this just needs to be set to a dummy value.
 | 
			
		||||
platformVersion = 999
 | 
			
		||||
@ -24,15 +31,9 @@ mockitoKotlinVersion=4.0.0
 | 
			
		||||
mockitoVersion=4.6.1
 | 
			
		||||
hamcrestVersion=2.2
 | 
			
		||||
 | 
			
		||||
# Settings For Development Utilities
 | 
			
		||||
combinedWorkerVersion=5.0.0.0-Beta1.0-HC00
 | 
			
		||||
simulatorVersion=5.0.0.0-Beta1.0-HC00
 | 
			
		||||
 | 
			
		||||
cordaClusterURL=https://localhost:8888
 | 
			
		||||
cordaRpcUser=admin
 | 
			
		||||
cordaRpcPasswd=admin
 | 
			
		||||
devEnvWorkspace=workspace
 | 
			
		||||
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.
 | 
			
		||||
    repositories {
 | 
			
		||||
        gradlePluginPortal()
 | 
			
		||||
        maven {
 | 
			
		||||
            url = "$artifactoryContextUrl/"
 | 
			
		||||
        }
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //  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.
 | 
			
		||||
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.types.MemberX500Name;
 | 
			
		||||
 | 
			
		||||
// A class which will contain a message, It must be marked with @CordaSerializable for Corda
 | 
			
		||||
// to be able to send from one virtual node to another.
 | 
			
		||||
// Where a class contains a message, mark it with @CordaSerializable to enable Corda to 
 | 
			
		||||
// send it from one virtual node to another.
 | 
			
		||||
@CordaSerializable
 | 
			
		||||
public class Message {
 | 
			
		||||
    // public Message() {}
 | 
			
		||||
    public Message(MemberX500Name sender, String message) {
 | 
			
		||||
        this.sender = sender;
 | 
			
		||||
        this.message = message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MemberX500Name getSender() {
 | 
			
		||||
        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.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)
 | 
			
		||||
// 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
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
    // 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.
 | 
			
		||||
 | 
			
		||||
    // JsonMarshallingService provides a Service for manipulating json
 | 
			
		||||
    // JsonMarshallingService provides a service for manipulating JSON.
 | 
			
		||||
    @CordaInject
 | 
			
		||||
    public JsonMarshallingService jsonMarshallingService;
 | 
			
		||||
 | 
			
		||||
    // FlowMessaging provides a service for establishing flow sessions between Virtual Nodes and
 | 
			
		||||
    // sending and receiving payloads between them
 | 
			
		||||
    // FlowMessaging provides a service that establishes flow sessions between virtual nodes 
 | 
			
		||||
    // that send and receive payloads between them.
 | 
			
		||||
    @CordaInject
 | 
			
		||||
    public FlowMessaging flowMessaging;
 | 
			
		||||
 | 
			
		||||
    // MemberLookup provides a service for looking up information about members of the Virtual Network which
 | 
			
		||||
    // this CorDapp is operating in.
 | 
			
		||||
     // MemberLookup provides a service for looking up information about members of the virtual network which
 | 
			
		||||
     // this CorDapp operates in.
 | 
			
		||||
    @CordaInject
 | 
			
		||||
    public MemberLookup memberLookup;
 | 
			
		||||
 | 
			
		||||
    public MyFirstFlow() {}
 | 
			
		||||
 | 
			
		||||
    // When a flow is invoked it's call() method is called.
 | 
			
		||||
    // 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
 | 
			
		||||
    // 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
 | 
			
		||||
    // for a response from the other flows and services.
 | 
			
		||||
    @NotNull
 | 
			
		||||
    @Suspendable
 | 
			
		||||
    @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");
 | 
			
		||||
 | 
			
		||||
        // 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());
 | 
			
		||||
 | 
			
		||||
        // 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);
 | 
			
		||||
 | 
			
		||||
        // Obtain the MemberX500Name of counterparty
 | 
			
		||||
        // Obtain the MemberX500Name of the counterparty.
 | 
			
		||||
        MemberX500Name otherMember = flowArgs.otherMember;
 | 
			
		||||
 | 
			
		||||
        // Get our identity from the MemberLookup service.
 | 
			
		||||
@ -69,19 +69,19 @@ public class MyFirstFlow implements RPCStartableFlow {
 | 
			
		||||
        // Log the message to be sent.
 | 
			
		||||
        log.info("MFF: message.message: " + message.message);
 | 
			
		||||
 | 
			
		||||
        // Start a flow session with the otherMember using the FlowMessaging service
 | 
			
		||||
        // The otherMember's Virtual Node will run the corresponding MyFirstFlowResponder responder flow
 | 
			
		||||
        // Start a flow session with the otherMember using the FlowMessaging service.
 | 
			
		||||
        // The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow.
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        // Receive a response from the Responder flow
 | 
			
		||||
        // Receive a response from the responder flow.
 | 
			
		||||
        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
 | 
			
		||||
        // value from the flow when testing using the Simulator
 | 
			
		||||
        // value from the flow when testing using the simulator.
 | 
			
		||||
        return response.message;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -91,9 +91,9 @@ public class MyFirstFlow implements RPCStartableFlow {
 | 
			
		||||
RequestBody for triggering the flow via http-rpc:
 | 
			
		||||
{
 | 
			
		||||
    "clientRequestId": "r1",
 | 
			
		||||
    "flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
 | 
			
		||||
    "flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow",
 | 
			
		||||
    "requestData": {
 | 
			
		||||
        "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 */
 | 
			
		||||
 */
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package com.r3.developers.csdetemplate;
 | 
			
		||||
package com.r3.developers.csdetemplate.workflows;
 | 
			
		||||
 | 
			
		||||
import net.corda.v5.application.flows.CordaInject;
 | 
			
		||||
import net.corda.v5.application.flows.InitiatedBy;
 | 
			
		||||
@ -11,36 +11,36 @@ import org.slf4j.Logger;
 | 
			
		||||
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.
 | 
			
		||||
@InitiatedBy(protocol = "another-flow")
 | 
			
		||||
@InitiatedBy(protocol = "my-first-flow")
 | 
			
		||||
// Responder flows must inherit from 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);
 | 
			
		||||
 | 
			
		||||
    // MemberLookup provides a service for looking up information about members of the Virtual Network which
 | 
			
		||||
    // this CorDapp is operating in.
 | 
			
		||||
    // MemberLookup looks for information about members of the virtual network which 
 | 
			
		||||
    // this CorDapp operates in. 
 | 
			
		||||
    @CordaInject
 | 
			
		||||
    public MemberLookup memberLookup;
 | 
			
		||||
 | 
			
		||||
    public MyFirstFlowResponder() {}
 | 
			
		||||
 | 
			
		||||
    // 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.
 | 
			
		||||
    // 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/
 | 
			
		||||
    // 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 its call() method is called.
 | 
			
		||||
    // 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.
 | 
			
		||||
    // 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.
 | 
			
		||||
    @Suspendable
 | 
			
		||||
    @Override
 | 
			
		||||
    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");
 | 
			
		||||
 | 
			
		||||
        // 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);
 | 
			
		||||
 | 
			
		||||
        // 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.
 | 
			
		||||
        MemberX500Name ourIdentity = memberLookup.myInfo().getName();
 | 
			
		||||
 | 
			
		||||
        // Create a response to greet the sender
 | 
			
		||||
        // Create a message to greet the sender.
 | 
			
		||||
        Message response = new Message(ourIdentity,
 | 
			
		||||
                "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.SimulatedVirtualNode;
 | 
			
		||||
import net.corda.simulator.Simulator;
 | 
			
		||||
import net.corda.v5.base.types.MemberX500Name;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyFirstFlowTest {
 | 
			
		||||
 | 
			
		||||
    // Names picked to match the corda network in config/dev-net.json
 | 
			
		||||
    private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
 | 
			
		||||
    private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    public void test_that_MyFirstFLow_returns_correct_message() {
 | 
			
		||||
 | 
			
		||||
        // Instantiate an instance of the Simulator
 | 
			
		||||
        // Instantiate an instance of the simulator.
 | 
			
		||||
        Simulator simulator = new Simulator();
 | 
			
		||||
 | 
			
		||||
        // Create Alice's and Bob HoldingIDs
 | 
			
		||||
        HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500);
 | 
			
		||||
        HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500);
 | 
			
		||||
        // Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
 | 
			
		||||
        // Don't assign Bob's virtual node to a value. You don't need it for this particular test.
 | 
			
		||||
        SimulatedVirtualNode aliceVN = simulator.createVirtualNode(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.
 | 
			
		||||
        // 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
 | 
			
		||||
        // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
 | 
			
		||||
        MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
 | 
			
		||||
 | 
			
		||||
        // Create a requestData object
 | 
			
		||||
        // Create a requestData object.
 | 
			
		||||
        RequestData requestData = RequestData.Companion.create(
 | 
			
		||||
                "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
 | 
			
		||||
                myFirstFlowStartArgs            // The object which contains the start arguments of the flow
 | 
			
		||||
                "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.
 | 
			
		||||
                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);
 | 
			
		||||
 | 
			
		||||
        // 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"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user