From b86a3c3d2c5a1c7c0c3af174b192f25038d9fe5b Mon Sep 17 00:00:00 2001 From: Khoi Nguyen Date: Fri, 14 Oct 2022 14:17:36 +0100 Subject: [PATCH 01/26] CORE-7135: Setup Jenkins Project * Added new .ci folder which contains Jenkins file for multibranch pipeline project --- .ci/Jenkinsfile | 8 ++++++++ .ci/nightly/JenkinsfileSnykScan | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 .ci/Jenkinsfile create mode 100644 .ci/nightly/JenkinsfileSnykScan diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile new file mode 100644 index 0000000..8e0651c --- /dev/null +++ b/.ci/Jenkinsfile @@ -0,0 +1,8 @@ +@Library('corda-shared-build-pipeline-steps@5.0') _ + +cordaPipeline( + publishRepoPrefix: '', + slimBuild: true, + runUnitTests: false, + dedicatedJobForSnykDelta: false + ) diff --git a/.ci/nightly/JenkinsfileSnykScan b/.ci/nightly/JenkinsfileSnykScan new file mode 100644 index 0000000..c07efb7 --- /dev/null +++ b/.ci/nightly/JenkinsfileSnykScan @@ -0,0 +1,6 @@ +@Library('corda-shared-build-pipeline-steps@5.0') _ + +cordaSnykScanPipeline ( + snykTokenId: 'r3-snyk-corda5', + snykAdditionalCommands: "--all-sub-projects -d" +) From 8267ea597c1a7ebad4fe3908bb9126b7544011fb Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Mon, 17 Oct 2022 11:04:37 +0100 Subject: [PATCH 02/26] Set service members public for DP2 simulator --- .../java/com/r3/developers/csdetemplate/MyFirstFlow.java | 6 +++--- .../r3/developers/csdetemplate/MyFirstFlowResponder.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index 50d183a..0fcc95e 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -26,17 +26,17 @@ public class MyFirstFlow implements RPCStartableFlow { // JsonMarshallingService provides a Service for manipulating json @CordaInject - JsonMarshallingService jsonMarshallingService; + public JsonMarshallingService jsonMarshallingService; // FlowMessaging provides a service for establishing flow sessions between Virtual Nodes and // sending and receiving payloads between them @CordaInject - FlowMessaging flowMessaging; + public FlowMessaging flowMessaging; // MemberLookup provides a service for looking up information about members of the Virtual Network which // this CorDapp is operating in. @CordaInject - MemberLookup memberLookup; + public MemberLookup memberLookup; public MyFirstFlow() {} diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java index 4e49f3b..de27380 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java @@ -23,7 +23,7 @@ public class MyFirstFlowResponder implements ResponderFlow { // MemberLookup provides a service for looking up information about members of the Virtual Network which // this CorDapp is operating in. @CordaInject - MemberLookup memberLookup; + public MemberLookup memberLookup; public MyFirstFlowResponder() {} From fc34e1bd5f45e101b8d0f9acb07a18a6f60cbae8 Mon Sep 17 00:00:00 2001 From: Khoi Nguyen Date: Mon, 17 Oct 2022 13:16:02 +0100 Subject: [PATCH 03/26] CORE-7135: Setup Jenkins pipeline * Added new .ci folder which contains Jenkins file for multibranch pipeline project --- .ci/Jenkinsfile | 8 ++++++++ .ci/nightly/JenkinsfileSnykScan | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 .ci/Jenkinsfile create mode 100644 .ci/nightly/JenkinsfileSnykScan diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile new file mode 100644 index 0000000..8e0651c --- /dev/null +++ b/.ci/Jenkinsfile @@ -0,0 +1,8 @@ +@Library('corda-shared-build-pipeline-steps@5.0') _ + +cordaPipeline( + publishRepoPrefix: '', + slimBuild: true, + runUnitTests: false, + dedicatedJobForSnykDelta: false + ) diff --git a/.ci/nightly/JenkinsfileSnykScan b/.ci/nightly/JenkinsfileSnykScan new file mode 100644 index 0000000..c07efb7 --- /dev/null +++ b/.ci/nightly/JenkinsfileSnykScan @@ -0,0 +1,6 @@ +@Library('corda-shared-build-pipeline-steps@5.0') _ + +cordaSnykScanPipeline ( + snykTokenId: 'r3-snyk-corda5', + snykAdditionalCommands: "--all-sub-projects -d" +) From 8e6081875b6294b3a288be3a4bb152cab2a3099a Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Mon, 17 Oct 2022 17:01:27 +0100 Subject: [PATCH 04/26] Final message classes, removal of unneeded deps, initial pass over docs --- build.gradle | 41 ------------------- .../r3/developers/csdetemplate/Message.java | 6 +-- .../developers/csdetemplate/MyFirstFlow.java | 4 +- .../csdetemplate/MyFirstFlowResponder.java | 8 ++-- .../csdetemplate/MyFirstFlowStartArgs.java | 10 +---- .../csdetemplate/MyFirstFlowTest.java | 1 - 6 files changed, 11 insertions(+), 59 deletions(-) diff --git a/build.gradle b/build.gradle index bd29e7b..5b50cbd 100644 --- a/build.gradle +++ b/build.gradle @@ -45,36 +45,6 @@ cordapp { } } -/* -// Declare the set of Kotlin compiler options we need to build a CorDapp. -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions { - allWarningsAsErrors = false - - // Specify the version of Kotlin that we are that we will be developing. - languageVersion = '1.7' - // Specify the Kotlin libraries that code is compatible with - apiVersion = '1.7' - // Note that we Need to use a version of Kotlin that will be compatible with the Corda API. - // Currently that is developed in Kotlin 1.7 so picking the same version ensures compatibility with that. - - // Specify the version of Java to target. - jvmTarget = javaVersion - - // Needed for reflection to work correctly. - javaParameters = true - - // -Xjvm-default determines how Kotlin supports default methods. - // JetBrains currently recommends developers use -Xjvm-default=all - // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-default/ - freeCompilerArgs += [ - "-Xjvm-default=all" - ] - } -} - - */ - // Declare the set of Kotlin compiler options we need to build a CorDapp. tasks.withType(JavaCompile) { @@ -84,8 +54,6 @@ tasks.withType(JavaCompile) { ] } - - repositories { // All dependencies are held in Maven Central mavenCentral() @@ -95,13 +63,6 @@ repositories { // 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") @@ -135,9 +96,7 @@ dependencies { // 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" - } diff --git a/src/main/java/com/r3/developers/csdetemplate/Message.java b/src/main/java/com/r3/developers/csdetemplate/Message.java index a7109ef..4ca6092 100644 --- a/src/main/java/com/r3/developers/csdetemplate/Message.java +++ b/src/main/java/com/r3/developers/csdetemplate/Message.java @@ -3,15 +3,15 @@ package com.r3.developers.csdetemplate; 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. +// 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. @CordaSerializable public class Message { - // public Message() {} public Message(MemberX500Name sender, String message) { this.sender = sender; this.message = message; } + public MemberX500Name getSender() { return sender; } diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index 0fcc95e..ffcc434 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -14,7 +14,7 @@ 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 { @@ -40,7 +40,7 @@ public class MyFirstFlow implements RPCStartableFlow { public MyFirstFlow() {} - // When a flow is invoked it's call() method is called. + // When a flow is invoked its call() method is called. // call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait // for a response from the other flows and services @NotNull diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java index de27380..43f6075 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java @@ -11,9 +11,9 @@ 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, it's 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 { @@ -28,9 +28,9 @@ public class MyFirstFlowResponder implements ResponderFlow { 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. + // 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/ + // 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 diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java index 1867875..2229518 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java @@ -2,20 +2,14 @@ package com.r3.developers.csdetemplate; import net.corda.v5.base.types.MemberX500Name; -// // A class to hold the arguments required to start the flow -//class MyFirstFlowStartArgs(val otherMember: 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]" + // The JSON Marshalling Service, that handles serialisation, needs this constructor. public MyFirstFlowStartArgs() {} } diff --git a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java index 1aa828f..31305a0 100644 --- a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java +++ b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java @@ -1,6 +1,5 @@ package com.r3.developers.csdetemplate; - import net.corda.simulator.HoldingIdentity; import net.corda.simulator.RequestData; import net.corda.simulator.SimulatedVirtualNode; From 7062445a2ee95a65d719bd94331395a0a59cd981 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Tue, 18 Oct 2022 09:44:25 +0100 Subject: [PATCH 05/26] Finished tweaking comments --- build.gradle | 4 ++-- .../com/r3/developers/csdetemplate/MyFirstFlowResponder.java | 2 +- .../com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java | 2 +- .../java/com/r3/developers/csdetemplate/MyFirstFlowTest.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 5b50cbd..ff011d9 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ 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. + // These extend the existing build environment so that CPB and CPK files can be built. // This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp // required by Corda. id 'net.corda.plugins.cordapp-cpb2' @@ -67,7 +67,7 @@ dependencies { // Corda API specified. cordaProvided platform("net.corda:corda-api:$cordaApiVersion") - // If using transistive dependencies this will provide most of Corda-API: + // If using transistive dependencies this will provide most of the Corda-API: // cordaProvided 'net.corda:corda-application' // Alternatively we can explicitly specify all our Corda-API dependencies: diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java index 43f6075..d7fba74 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java @@ -11,7 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -// MyFirstFlowResponder is a responder flow, it's corresponding initiating flow is called MyFirstFlow (defined in MyFirstFlow.java) +// 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 = "my-first-flow") // Responder flows must inherit from ResponderFlow diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java index 2229518..4c5f2ff 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java @@ -10,6 +10,6 @@ public class MyFirstFlowStartArgs { this.otherMember = otherMember; } - // The JSON Marshalling Service, that handles serialisation, needs this constructor. + // The JSON Marshalling Service, which handles serialisation, needs this constructor. public MyFirstFlowStartArgs() {} } diff --git a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java index 31305a0..925ae5c 100644 --- a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java +++ b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java @@ -25,7 +25,7 @@ class MyFirstFlowTest { HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500); // 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. + // We don't assign Bob's virtual node to a variable because we don't need it for this particular test. SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class); simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class); @@ -35,7 +35,7 @@ class MyFirstFlowTest { // 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 + MyFirstFlow.class, // The name of the flow class which is to be started myFirstFlowStartArgs // The object which contains the start arguments of the flow ); From 2bcc9d6fb5cd7430e041014604a4eda287ec4acf Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Wed, 19 Oct 2022 01:14:28 +0100 Subject: [PATCH 06/26] Suppress varargs unchecked warning, remove unnecessary NotNull annotation --- src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java | 2 +- .../java/com/r3/developers/csdetemplate/MyFirstFlowTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index ffcc434..21321c6 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -46,7 +46,7 @@ public class MyFirstFlow implements RPCStartableFlow { @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 log.info("MFF: MyFirstFlow.call() called"); diff --git a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java index 925ae5c..e343318 100644 --- a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java +++ b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java @@ -15,8 +15,8 @@ class MyFirstFlowTest { 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 Simulator simulator = new Simulator(); From 9d532633bfc18cebbe6d14926073ec49f495a05a Mon Sep 17 00:00:00 2001 From: Khoi Nguyen Date: Tue, 18 Oct 2022 15:53:43 +0100 Subject: [PATCH 07/26] CORE-7176: Setup Template Project Notification * Updated Jenkinsfile to use #corda-corda5-developer-experience as the default slack channel destination. --- .ci/Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 8e0651c..2e1e13d 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -4,5 +4,6 @@ cordaPipeline( publishRepoPrefix: '', slimBuild: true, runUnitTests: false, - dedicatedJobForSnykDelta: false + dedicatedJobForSnykDelta: false, + slackChannel: '#corda-corda5-developer-experience' ) From c4b8f4fa1b6068ad8cf10d6008072a306d2a8b40 Mon Sep 17 00:00:00 2001 From: Khoi Nguyen Date: Thu, 20 Oct 2022 10:41:50 +0100 Subject: [PATCH 08/26] CORE-7176: Setup Template Project Notification * Updated slackChannel in .ci/Jenkinsfile to send notification to '#corda-corda5-dev-ex-build-notifications' --- .ci/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 2e1e13d..330a1e5 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -5,5 +5,5 @@ cordaPipeline( slimBuild: true, runUnitTests: false, dedicatedJobForSnykDelta: false, - slackChannel: '#corda-corda5-developer-experience' + slackChannel: '#corda-corda5-dev-ex-build-notifications' ) From 0428adac4cca4545523ec958fb6c3f58ac76d83c Mon Sep 17 00:00:00 2001 From: Ronan Browne Date: Fri, 21 Oct 2022 09:04:09 +0100 Subject: [PATCH 09/26] CORE-7135: send notifications to correct slack channel --- .ci/Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 8e0651c..330a1e5 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -4,5 +4,6 @@ cordaPipeline( publishRepoPrefix: '', slimBuild: true, runUnitTests: false, - dedicatedJobForSnykDelta: false + dedicatedJobForSnykDelta: false, + slackChannel: '#corda-corda5-dev-ex-build-notifications' ) From bbdb1b41cae4c83af5f61b4e8565f168a6798315 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Wed, 26 Oct 2022 15:55:50 +0100 Subject: [PATCH 10/26] Use published artifacts --- build.gradle | 47 ++--------------------------------------------- gradle.properties | 9 ++------- settings.gradle | 31 ++----------------------------- 3 files changed, 6 insertions(+), 81 deletions(-) diff --git a/build.gradle b/build.gradle index 733d3bf..ff011d9 100644 --- a/build.gradle +++ b/build.gradle @@ -55,51 +55,8 @@ tasks.withType(JavaCompile) { } repositories { - mavenLocal() // All dependencies are held in Maven Central mavenCentral() - - // R3 Internal repositories - // Repository the provides kotlin-stdlib-jdk8-osgi created by R3. - // Final location to be decided. - maven { - url = "$artifactoryContextUrl/corda-dependencies" - } - // Repository provides Corda 5 binaries that implement Corda-API. - // These will be made publicly available. - // Final location to be decided. - // Repository subject to change - maven { - url = "$artifactoryContextUrl/corda-os-maven" - credentials { - username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } - // Provides the combined-worker Jars - // These will be made publicly available. - // Final location to be decided. - maven { - url = "$artifactoryContextUrl/corda-ent-maven-unstable-local" - credentials { - username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } - maven { - url = "$artifactoryContextUrl/corda-ent-maven" - credentials { - username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } - maven { - url = "$artifactoryContextUrl/engineering-tools-maven-unstable" - credentials { - username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } } // Declare dependencies for the modules we will use. @@ -128,8 +85,8 @@ dependencies { cordaProvided 'org.slf4j:slf4j-api' // Dependencies Required By Test Tooling - testImplementation "net.corda:corda-simulator-api:$simulatorVersion" - testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion" + testImplementation "net.corda:corda-simulator-api:$combinedWorkerVersion" + testRuntimeOnly "net.corda:corda-simulator-runtime:$combinedWorkerVersion" // 3rd party libraries // Required diff --git a/gradle.properties b/gradle.properties index 1581641..c6aac50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,7 @@ 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.414-beta+ +cordaApiVersion=5.0.0.190-DevPreview-2 # Specify the version of the cordapp-cpb and cordapp-cpk plugins cordaPluginsVersion=7.0.0-DevPreview-2 @@ -26,8 +25,7 @@ hamcrestVersion=2.2 # Settings For Development Utilities testUtilsVersion=5.0.0.0-DevPreview-2 -#combinedWorkerVersion=5.0.0.0-DevPreview-2 -combinedWorkerVersion=5.0.0.0-beta+ +combinedWorkerVersion=5.0.0.0-DevPreview-2 cordaClusterURL=https://localhost:8888 cordaRpcUser=admin @@ -35,6 +33,3 @@ cordaRpcPasswd=admin devEnvWorkspace=workspace dbContainerName=CSDEpostgresql -# R3 internal repository -artifactoryContextUrl=https://software.r3.com/artifactory -simulatorVersion=5.0.0.0-SNAPSHOT \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 8fb0cb1..655a3be 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,41 +1,12 @@ pluginManagement { // Declare the repositories where plugins are stored. repositories { - mavenLocal() gradlePluginPortal() mavenCentral { content { includeGroupByRegex 'net\\.corda(\\..*)?' } } - // R3 internal repositories - maven { - url "${artifactoryContextUrl}/corda-dev" - content { - includeGroupByRegex 'net\\.corda\\.plugins(\\..*)?' - } - } - maven { - url = "$artifactoryContextUrl/corda-os-maven-unstable" - credentials { - username = settings.ext.find('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = settings.ext.find('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } - maven { - url = "$artifactoryContextUrl/corda-os-maven-unstable-local" - credentials { - username = settings.ext.find('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = settings.ext.find('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } - maven { - url = "$artifactoryContextUrl/corda-dev" - credentials { - username = settings.ext.find('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') - password = settings.ext.find('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - } } // The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version, @@ -54,3 +25,5 @@ pluginManagement { rootProject.name = 'csde-cordapp-template-java' + + From 39c21c38a67b820ca8d87c26a0796261a68ad493 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Wed, 26 Oct 2022 16:30:17 +0100 Subject: [PATCH 11/26] Fix Jenkinsfile bug --- .ci/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 1e80e28..4e8e34a 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -5,6 +5,6 @@ cordaPipeline( slimBuild: true, runUnitTests: false, dedicatedJobForSnykDelta: false, - slackChannel: '#corda-corda5-dev-ex-build-notifications' + slackChannel: '#corda-corda5-dev-ex-build-notifications', dedicatedJobForSnykDelta: false ) From 805fb0d9ad853fcb3efd9d598c8f4be48165b930 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Wed, 26 Oct 2022 16:31:16 +0100 Subject: [PATCH 12/26] Fix Jenkinsfile bug --- .ci/Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 4e8e34a..330a1e5 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -5,6 +5,5 @@ cordaPipeline( slimBuild: true, runUnitTests: false, dedicatedJobForSnykDelta: false, - slackChannel: '#corda-corda5-dev-ex-build-notifications', - dedicatedJobForSnykDelta: false + slackChannel: '#corda-corda5-dev-ex-build-notifications' ) From fe9ab2cda1108aff44ac0cda5509d0f1d71e23ed Mon Sep 17 00:00:00 2001 From: Niamh25 Date: Thu, 17 Nov 2022 16:27:11 +0000 Subject: [PATCH 13/26] DOC-4345: updated edited Java CSDE template code comments --- build.gradle | 4 +-- buildSrc/src/main/groovy/csde.gradle | 10 +++--- .../java/com/r3/csde/CsdeRpcInterface.java | 16 ++++----- .../r3/developers/csdetemplate/Message.java | 4 +-- .../developers/csdetemplate/MyFirstFlow.java | 36 +++++++++---------- .../csdetemplate/MyFirstFlowResponder.java | 18 +++++----- .../csdetemplate/MyFirstFlowTest.java | 22 ++++++------ 7 files changed, 55 insertions(+), 55 deletions(-) diff --git a/build.gradle b/build.gradle index ff011d9..e1a62dc 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ def javaVersion = VERSION_11 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. + // Enforced versioning has not been implemented yet, so enter a dummy value for now. // The platform version will correspond to and be roughly equivalent to the Corda API version. targetPlatformVersion platformVersion.toInteger() minimumPlatformVersion platformVersion.toInteger() @@ -61,7 +61,7 @@ repositories { // 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. +// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are then built upon. dependencies { // Declare a "platform" so that we use the correct set of dependency versions for the version of the // Corda API specified. diff --git a/buildSrc/src/main/groovy/csde.gradle b/buildSrc/src/main/groovy/csde.gradle index f2fe6a4..6efb924 100644 --- a/buildSrc/src/main/groovy/csde.gradle +++ b/buildSrc/src/main/groovy/csde.gradle @@ -34,8 +34,8 @@ def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5" def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli" def cordaJDBCDir = cordaBinDir + "/jdbcDrivers" def signingCertAlias="gradle-plugin-default-key" -// Get error if this is not a autotyped object -// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem" +// You will receive an error if this is not a autotyped object. +// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem". def signingCertFName = rootDir.toString() + "/config/gradle-plugin-default-key.pem" def keystoreAlias = "my-signing-key" def keystoreFName = devEnvWorkspace + "/signingkeys.pfx" @@ -136,7 +136,7 @@ tasks.register('createKeystore') { "-storetype", "pkcs12", "-validity", "4000" } - // Need to add the default signing key to the keystore + // Add the default signing key to the keystore. exec { commandLine "${System.getProperty("java.home")}/bin/keytool", "-importcert", "-keystore", keystoreFName, @@ -172,7 +172,7 @@ tasks.register('buildCPI') { File srcDir srcDir = file('build/libs') - // Create a file collection using a closure + // Create a file collection using a closure. def collection = layout.files { srcDir.listFiles() } def cpbs = collection.filter { it.getName().endsWith(".cpb") } @@ -225,7 +225,7 @@ tasks.register('listCPIs') { } } -// Empty task, just acts as the Task user entry point task. +// Empty task, this acts as the user entry point task. tasks.register('deployCordapp') { group = pluginGroupName dependsOn("createAndRegVNodes") diff --git a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java index eef8c7e..3776d84 100644 --- a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java +++ b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java @@ -202,9 +202,9 @@ public class CsdeRpcInterface { private boolean uploadStatusRetry(kong.unirest.HttpResponse response) { int status = response.getStatus(); kong.unirest.JsonNode body = response.getBody(); - // Do not retry on success + // Do not retry if successful. if(status == 200) { - // Keep retrying until we get "OK" may move through "Validateing upload", "Persisting CPI" + // Retry until you get an "OK", it may move to "Validating upload", "Persisting CPI". return !(body.getObject().get("status").equals("OK")); } else if (status == 400){ @@ -269,7 +269,7 @@ public class CsdeRpcInterface { out.println("Upload Status:" + status); out.println("Pretty print the body\n" + body.toPrettyString()); - // We expect the id field to be a string. + // Expect the ID field to be a string. if (status == 200) { String id = (String) body.getObject().get("id"); out.println("get id:\n" + id); @@ -299,7 +299,7 @@ public class CsdeRpcInterface { LinkedList x500Ids = getConfigX500Ids(); LinkedList OKHoldingShortIds = new LinkedList<>(); - // For each identity check that it already exists. + // Check that each identity already exists. Set existingX500 = new HashSet<>(); kong.unirest.HttpResponse vnodeListResponse = getVNodeInfo(); @@ -322,9 +322,9 @@ public class CsdeRpcInterface { .asJson(); // Logging. - // need to check this and report errors. - // 200 - OK - // 409 - Vnode already exists + // Check this and report errors. + // 200 - OK. + // 409 - Vnode already exists. if (jsonNode.getStatus() != 409) { if (jsonNode.getStatus() != 200) { reportError(jsonNode); @@ -340,7 +340,7 @@ public class CsdeRpcInterface { } } - // Register the VNodes + // Register the VNodes. for(String shortHoldingIdHash: OKHoldingShortIds) { kong.unirest.HttpResponse vnodeResponse = Unirest.post(baseURL + "/api/v1/membership/" + shortHoldingIdHash) .body("{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { \"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\" } } }") diff --git a/src/main/java/com/r3/developers/csdetemplate/Message.java b/src/main/java/com/r3/developers/csdetemplate/Message.java index 4ca6092..58dc5b8 100644 --- a/src/main/java/com/r3/developers/csdetemplate/Message.java +++ b/src/main/java/com/r3/developers/csdetemplate/Message.java @@ -3,8 +3,8 @@ package com.r3.developers.csdetemplate; 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(MemberX500Name sender, String message) { diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index 21321c6..985a25e 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -18,46 +18,46 @@ import org.slf4j.LoggerFactory; // 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 establishes flow sessions between virtual nodes + // that sends and receives 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 looks for information about members of the virtual network + // which this CorDapp operates in. @CordaInject public MemberLookup memberLookup; public MyFirstFlow() {} // 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 + // 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(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 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 string will pass // 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; } } diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java index d7fba74..504e0b0 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java @@ -17,19 +17,19 @@ import org.slf4j.LoggerFactory; // 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 its call() method is called. - // call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait + // 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. @@ -37,10 +37,10 @@ public class MyFirstFlowResponder implements ResponderFlow { @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()); diff --git a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java index e343318..9b45d53 100644 --- a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java +++ b/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java @@ -17,32 +17,32 @@ class MyFirstFlowTest { @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 + // Create Alice's and Bob HoldingIDs. HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500); HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500); - // 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 variable because we don't need it for this particular test. + // Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node. + // Don't assign Bob's virtual node to a value. You don't need it for this particular test. SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class); simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class); - // 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")); } } From 370ea175e987fa8d7521bff90b22a992c7b3ed82 Mon Sep 17 00:00:00 2001 From: Niamh25 Date: Thu, 17 Nov 2022 16:34:16 +0000 Subject: [PATCH 14/26] DOC-4345: updated edited Java CSDE template code comments --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e1a62dc..b7737ee 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - // Optional but used by exmaple tests. + // Optional but used by example tests. testImplementation "org.mockito:mockito-core:$mockitoVersion" testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion" } From fa52064d125354653ca6c822965c1dab54e45018 Mon Sep 17 00:00:00 2001 From: Niamh25 <115650292+Niamh25@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:02:19 +0000 Subject: [PATCH 15/26] Update buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java Co-authored-by: Paul Moloney <112477620+paulmoloneyr3@users.noreply.github.com> --- buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java index 3776d84..89d2891 100644 --- a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java +++ b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java @@ -204,7 +204,7 @@ public class CsdeRpcInterface { kong.unirest.JsonNode body = response.getBody(); // Do not retry if successful. if(status == 200) { - // Retry until you get an "OK", it may move to "Validating upload", "Persisting CPI". + // Retry until you get an "OK"; it may move to "Validating upload", "Persisting CPI". return !(body.getObject().get("status").equals("OK")); } else if (status == 400){ From f2efd50a4c68b2e1d9a87925c69c9b67a04d1166 Mon Sep 17 00:00:00 2001 From: Niamh25 <115650292+Niamh25@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:02:45 +0000 Subject: [PATCH 16/26] Update src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java Co-authored-by: Paul Moloney <112477620+paulmoloneyr3@users.noreply.github.com> --- src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index 985a25e..c2e6aea 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -29,7 +29,7 @@ public class MyFirstFlow implements RPCStartableFlow { public JsonMarshallingService jsonMarshallingService; // FlowMessaging establishes flow sessions between virtual nodes - // that sends and receives payloads between them. + // that send and receive payloads between them. @CordaInject public FlowMessaging flowMessaging; From 573c4c194980b3172eed6b063a2e5fe111b3fc1b Mon Sep 17 00:00:00 2001 From: Niamh25 <115650292+Niamh25@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:02:57 +0000 Subject: [PATCH 17/26] Update src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java Co-authored-by: Paul Moloney <112477620+paulmoloneyr3@users.noreply.github.com> --- src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index c2e6aea..ebbc43c 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -57,7 +57,7 @@ public class MyFirstFlow implements RPCStartableFlow { // 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. From 3af97d9cbf254513dc3c1f730ffea2a4eecdd01a Mon Sep 17 00:00:00 2001 From: Niamh25 <115650292+Niamh25@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:57:55 +0000 Subject: [PATCH 18/26] Update CsdeRpcInterface.java --- buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java index 89d2891..39cdc5b 100644 --- a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java +++ b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java @@ -204,7 +204,7 @@ public class CsdeRpcInterface { kong.unirest.JsonNode body = response.getBody(); // Do not retry if successful. if(status == 200) { - // Retry until you get an "OK"; it may move to "Validating upload", "Persisting CPI". + // Retry until you get an "OK"; we may see several other responses before the "OK" response. return !(body.getObject().get("status").equals("OK")); } else if (status == 400){ @@ -299,7 +299,7 @@ public class CsdeRpcInterface { LinkedList x500Ids = getConfigX500Ids(); LinkedList OKHoldingShortIds = new LinkedList<>(); - // Check that each identity already exists. + // Create a list of X500 IDs we will not need to create VNodes for. Set existingX500 = new HashSet<>(); kong.unirest.HttpResponse vnodeListResponse = getVNodeInfo(); From d4c4f8d007b69aad8accfa7b6970ba3497b150a2 Mon Sep 17 00:00:00 2001 From: Niamh25 <115650292+Niamh25@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:01:06 +0000 Subject: [PATCH 19/26] Update build.gradle --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b7737ee..b202de6 100644 --- a/build.gradle +++ b/build.gradle @@ -63,8 +63,8 @@ repositories { // A cordaProvided declaration is required for anything that we use that the Corda API provides. // This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are then built upon. dependencies { - // Declare a "platform" so that we use the correct set of dependency versions for the version of the - // Corda API specified. + // Declare a "platform" to use the correct set of dependency versions for the version that the + // Corda API specifies. cordaProvided platform("net.corda:corda-api:$cordaApiVersion") // If using transistive dependencies this will provide most of the Corda-API: From 4e6c74989f2f7a3d3c9e2399ce1390ec4c4f7eaa Mon Sep 17 00:00:00 2001 From: Niamh25 <115650292+Niamh25@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:09:54 +0000 Subject: [PATCH 20/26] Update MyFirstFlow.java --- .../com/r3/developers/csdetemplate/MyFirstFlow.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java index ebbc43c..b0e5233 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java @@ -28,13 +28,13 @@ public class MyFirstFlow implements RPCStartableFlow { @CordaInject public JsonMarshallingService jsonMarshallingService; - // FlowMessaging establishes flow sessions between virtual nodes + // FlowMessaging provides a service that establishes flow sessions between virtual nodes // that send and receive payloads between them. @CordaInject public FlowMessaging flowMessaging; - // MemberLookup looks for information about members of the virtual network - // which this CorDapp operates in. + // MemberLookup provides a service for looking up information about members of the virtual network which + // this CorDapp operates in. @CordaInject public MemberLookup memberLookup; @@ -79,7 +79,7 @@ public class MyFirstFlow implements RPCStartableFlow { // 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 pass + // 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. return response.message; @@ -96,4 +96,4 @@ RequestBody for triggering the flow via http-rpc: "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB" } } - */ \ No newline at end of file + */ From 2dffb3858799265d9d2fd3a8e8a69eca79dfc516 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Thu, 5 Jan 2023 14:15:40 +0000 Subject: [PATCH 21/26] Fix build.gradle so that publishToMavenLocal works --- build.gradle | 115 ++--- buildSrc/gradle.properties | 7 +- buildSrc/src/main/groovy/csde.gradle | 328 +++++++------- .../java/com/r3/csde/BuildCPIsHelper.java | 270 ++++++++++++ .../com/r3/csde/CordaLifeCycleHelper.java | 94 ++++ .../java/com/r3/csde/CordaStatusQueries.java | 63 +++ .../csde/CreateAndRegisterVNodesHelper.java | 289 ++++++++++++ .../main/java/com/r3/csde/CsdeException.java | 3 + .../java/com/r3/csde/CsdeRpcInterface.java | 415 ------------------ .../java/com/r3/csde/DeployCPIsHelper.java | 188 ++++++++ .../src/main/java/com/r3/csde/NoPidFile.java | 7 - .../main/java/com/r3/csde/ProjectContext.java | 83 ++++ .../main/java/com/r3/csde/ProjectUtils.java | 71 +++ .../src/test/java/CsdeRpcInterfaceTests.java | 4 - config/dev-net.json | 15 +- contracts/build.gradle | 91 ++++ gradle.properties | 20 +- settings.gradle | 2 + workflows/build.gradle | 94 ++++ .../csdetemplate/workflows}/Message.java | 2 +- .../csdetemplate/workflows}/MyFirstFlow.java | 4 +- .../workflows}/MyFirstFlowResponder.java | 2 +- .../workflows}/MyFirstFlowStartArgs.java | 2 +- .../workflows}/MyFirstFlowTest.java | 14 +- 24 files changed, 1472 insertions(+), 711 deletions(-) create mode 100644 buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java create mode 100644 buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java create mode 100644 buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java create mode 100644 buildSrc/src/main/java/com/r3/csde/CreateAndRegisterVNodesHelper.java delete mode 100644 buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java create mode 100644 buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java delete mode 100644 buildSrc/src/main/java/com/r3/csde/NoPidFile.java create mode 100644 buildSrc/src/main/java/com/r3/csde/ProjectContext.java create mode 100644 buildSrc/src/main/java/com/r3/csde/ProjectUtils.java delete mode 100644 buildSrc/src/test/java/CsdeRpcInterfaceTests.java create mode 100644 contracts/build.gradle create mode 100644 workflows/build.gradle rename {src/main/java/com/r3/developers/csdetemplate => workflows/src/main/java/com/r3/developers/csdetemplate/workflows}/Message.java (92%) rename {src/main/java/com/r3/developers/csdetemplate => workflows/src/main/java/com/r3/developers/csdetemplate/workflows}/MyFirstFlow.java (97%) rename {src/main/java/com/r3/developers/csdetemplate => workflows/src/main/java/com/r3/developers/csdetemplate/workflows}/MyFirstFlowResponder.java (98%) rename {src/main/java/com/r3/developers/csdetemplate => workflows/src/main/java/com/r3/developers/csdetemplate/workflows}/MyFirstFlowStartArgs.java (89%) rename {src/test/java/com/r3/developers/csdetemplate => workflows/src/test/java/com/r3/developers/csdetemplate/workflows}/MyFirstFlowTest.java (82%) diff --git a/build.gradle b/build.gradle index c1354a4..aa344c7 100644 --- a/build.gradle +++ b/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 the existing build environment so that CPB and CPK files can be built. - // This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp - // required by Corda. - id 'net.corda.plugins.cordapp-cpb2' id 'net.corda.cordapp.cordapp-configuration' id 'org.jetbrains.kotlin.plugin.jpa' @@ -18,101 +13,41 @@ 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 been implemented yet, so enter a dummy value for now. - // The platform version will correspond to and be roughly equivalent to the Corda API version. - targetPlatformVersion platformVersion.toInteger() - minimumPlatformVersion platformVersion.toInteger() - - // The cordapp section contains either a workflow or contract subsection depending on the type of component. - // Declares the type and metadata of the CPK (this CPB has one CPK). - workflow { - name "ModuleNameHere" - versionId 1 - vendor "VendorNameHere" + // Declare the set of 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() + maven { + url = "$artifactoryContextUrl/" + } } -} -// Declare dependencies for the modules we will use. -// A cordaProvided declaration is required for anything that we use that the Corda API provides. -// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are then built upon. -dependencies { - // Declare a "platform" to use the correct set of dependency versions for the version that the - // Corda API specifies. - cordaProvided platform("net.corda:corda-api:$cordaApiVersion") + tasks.withType(Test).configureEach { + useJUnitPlatform() + } - // If using transistive dependencies this will provide most of the Corda-API: - // cordaProvided 'net.corda:corda-application' - - // Alternatively we can explicitly specify all our Corda-API dependencies: - cordaProvided 'net.corda:corda-base' - cordaProvided 'net.corda:corda-application' - cordaProvided 'net.corda:corda-crypto' - cordaProvided 'net.corda:corda-membership' - // cordaProvided 'net.corda:corda-persistence' - cordaProvided 'net.corda:corda-serialization' - - // Not yet fully implemented: - // cordaProvided 'net.corda:corda-ledger' - - // The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration. - cordaProvided 'org.slf4j:slf4j-api' - - // Dependencies Required By Test Tooling - testImplementation "net.corda:corda-simulator-api:$combinedWorkerVersion" - testRuntimeOnly "net.corda:corda-simulator-runtime:$combinedWorkerVersion" - - // 3rd party libraries - // Required - testImplementation "org.slf4j:slf4j-simple:2.0.0" - testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - - // Optional but used by example tests. - testImplementation "org.mockito:mockito-core:$mockitoVersion" - testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion" -} - - -test { - 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 + } } } + diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index 3636833..a8865c8 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -1,6 +1,7 @@ -jacksonVersion = 2.13.3 +jacksonVersion = 2.13.4 unirestVersion=3.13.10 -cordaApiVersion=5.0.0.505-Beta1.0-HC00 + +cordaApiVersion=5.0.0.523-Fox1.0-RC03 # R3 internal repository -artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00 +artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0-RC03 diff --git a/buildSrc/src/main/groovy/csde.gradle b/buildSrc/src/main/groovy/csde.gradle index 6efb924..9e6c525 100644 --- a/buildSrc/src/main/groovy/csde.gradle +++ b/buildSrc/src/main/groovy/csde.gradle @@ -1,4 +1,24 @@ -import com.r3.csde.CsdeRpcInterface +// Note, IntelliJ does not recognise the imported Java Classes, hence they are +// highlighted in Red. However, they are recognised in the gradle compilation. + + +// todo: look at the declaration of the script variables, can they be combined with the declaration of the Project Context +// todo: investigate adding corda-cli to the class path then executing it directly - might not work as gradle has to set up the jar file, so its not their when you start. +// Todo: write a test flow runner helper function?? +// todo: rename deployCPIsHelper +// todo: add proper logging, rather than reading Stdout +// todo: add test corda running/live task +// todo: add a test to check docker is running and display error if not + halt start corda +// todo: add a clean corda task. + + +import com.r3.csde.CordaLifeCycleHelper +import com.r3.csde.ProjectContext +import com.r3.csde.DeployCPIsHelper +import com.r3.csde.BuildCPIsHelper +import com.r3.csde.ProjectUtils +import com.r3.csde.CordaStatusQueries +import com.r3.csde.CreateAndRegisterVNodesHelper plugins { id 'java-library' @@ -18,48 +38,114 @@ configurations { canBeResolved = true } + notaryServerCPB { + canBeConsumed = false + canBeResolved = true + } } // Dependencies for supporting tools dependencies { combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion" myPostgresJDBC 'org.postgresql:postgresql:42.4.1' + notaryServerCPB("com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-server:$cordaNotaryPluginsVersion") { + artifact { + classifier = 'package' + extension = 'cpb' + } + } + implementation "org.codehaus.groovy:groovy-json:3.0.9" } +// task groupings +def cordaGroup = 'csde-corda' // corda lifecycle tasks +def cordappGroup = 'csde-cordapp' // tasks to build and deploy corDapps +def queriesGroup = 'csde-queries' // tasks which query corda status +def supportingGroup = 'supporting' // tasks which should be hidden from the csde user -def pluginGroupName = "CSDE" -def pluginImplGroupName = "other" -def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5" -def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli" + +def cordaBinDir = System.getenv("CSDE_CORDA_BIN") ?: System.getProperty('user.home') + "/.corda/corda5" +def cordaCliBinDir = System.getenv("CSDE_CORDA_CLI") ?:System.getProperty('user.home') + "/.corda/cli" def cordaJDBCDir = cordaBinDir + "/jdbcDrivers" +def cordaNotaryServerDir = cordaBinDir + "/notaryserver" def signingCertAlias="gradle-plugin-default-key" -// You will receive an error if this is not a autotyped object. -// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem". +// Get error if this is not a autotyped object +// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem" def signingCertFName = rootDir.toString() + "/config/gradle-plugin-default-key.pem" def keystoreAlias = "my-signing-key" def keystoreFName = devEnvWorkspace + "/signingkeys.pfx" def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem" def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat" +// todo: can we rely on the build directory always being /workflow/build? aslo, is the +// workflow directory the correct place to build the cpb to. shoudl it be the main build directory. +def workflowBuildDir = rootDir.toString() + "/workflows/build" -// Need to read things from cordapp plugin -def cpiName = 'cpi name' +// todo: Need to read things from cordapp plugin - the cordapp names will be changed by the user +def appCpiName = 'cpi name' +def notaryCpiName = 'CSDE Notary Server CPI' -def csdeHelper = new CsdeRpcInterface(project, + +// todo: there should be a better way to set up these project context variables. +def projectContext = new ProjectContext(project, cordaClusterURL.toString(), cordaRpcUser, cordaRpcPasswd, devEnvWorkspace, + // todo: why is this not obtained in the groovy def's abouve - its inconsistent. new String("${System.getProperty("java.home")}/bin"), dbContainerName, cordaJDBCDir, - combiWorkerPidCacheFile + combiWorkerPidCacheFile, + signingCertAlias, + signingCertFName, + keystoreAlias, + keystoreFName, + keystoreCertFName, + appCpiName, + notaryCpiName, + devEnvWorkspace, + cordaCliBinDir, + cordaNotaryServerDir, + workflowBuildDir, + cordaNotaryPluginsVersion ) +def utils = new ProjectUtils() + +// Initiate workspace folder + +tasks.register('projInit') { + group = supportingGroup + doLast { + mkdir devEnvWorkspace + } +} + + +// CordaLifeCycle tasks + +def cordaLifeCycle = new CordaLifeCycleHelper(projectContext) + +tasks.register("startCorda") { + group = cordaGroup + dependsOn('getDevCordaLite', 'getPostgresJDBC') + doLast { + mkdir devEnvWorkspace + cordaLifeCycle.startCorda() + } +} + +tasks.register("stopCorda") { + group = cordaGroup + doLast { + cordaLifeCycle.stopCorda() + } +} tasks.register("getPostgresJDBC") { - group = pluginImplGroupName + group = supportingGroup doLast { copy { from configurations.myPostgresJDBC @@ -68,184 +154,100 @@ tasks.register("getPostgresJDBC") { } } -tasks.register('projInit') { - group = pluginImplGroupName - doLast { - mkdir devEnvWorkspace - } -} - -tasks.register("createGroupPolicy") { - group = pluginImplGroupName - dependsOn('projInit') - doLast { - - def groupPolicyFName = new String("${devEnvWorkspace}/GroupPolicy.json") - def devnetFName = new String("$rootDir/config/dev-net.json") - File groupPolicyFile = new File(groupPolicyFName) - File devnetFile = new File(devnetFName) - if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) { - def configX500Ids = csdeHelper.getConfigX500Ids() - - println("createGroupPolicy: Creating a GroupPolicy") - - javaexec { - classpath = files("$cordaCliBinDir/corda-cli.jar") - jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"] - standardOutput = new FileOutputStream(groupPolicyFName) - LinkedList myArgs = new LinkedList() - myArgs.add("mgm") - myArgs.add("groupPolicy") - configX500Ids.forEach { - myArgs.add("--name") - myArgs.add("$it") - } - - myArgs.add("--endpoint-protocol=1") - myArgs.add("--endpoint=http://localhost:1080") - args = myArgs - } - - } else { - println("createPolicyTask: everything up to date; nothing to do.") - } - - } -} - tasks.register("getDevCordaLite", Copy) { - group = pluginImplGroupName + group = supportingGroup from configurations.combinedWorker into cordaBinDir } -tasks.register('createKeystore') { - group = pluginImplGroupName - dependsOn('projInit') - doLast { - File keystoreFile = new File(keystoreFName) - if(!keystoreFile.exists()) { - println('createKeystore: Create a keystore') - exec { - commandLine "${System.getProperty("java.home")}/bin/keytool", "-genkeypair", - "-alias", keystoreAlias, - "-keystore", keystoreFName, - "-storepass", "keystore password", - "-dname", "CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB", - "-keyalg", "RSA", - "-storetype", "pkcs12", - "-validity", "4000" - } - // Add the default signing key to the keystore. - exec { - commandLine "${System.getProperty("java.home")}/bin/keytool", "-importcert", - "-keystore", keystoreFName, - "-storepass", "keystore password", - "-noprompt", - "-alias", signingCertAlias, - "-file", signingCertFName - } - // keytool -exportcert -rfc -alias "signing key 1" -keystore signingkeys.pfx -storepass "keystore password" -file signingkey1.pem - exec { - commandLine "${System.getProperty("java.home")}/bin/keytool", - "-exportcert", "-rfc", "-alias", keystoreAlias, - "-keystore", keystoreFName, - "-storepass", "keystore password", - "-file", keystoreCertFName - } - } - else { - println('createKeystore: keystore already created; nothing to do.') - } - } -} +// Corda status queries +def cordaStatusQueries = new CordaStatusQueries(projectContext) -tasks.register('buildCPI') { - group = pluginGroupName - dependsOn('build', 'createGroupPolicy', 'createKeystore') - - doLast{ - def cpiFile= buildDir.toString() + "/" + project.archivesBaseName + "-" + project.version + ".cpi" - delete { delete cpiFile } - File srcDir - srcDir = file('build/libs') - - // Create a file collection using a closure. - def collection = layout.files { srcDir.listFiles() } - def cpbs = collection.filter { it.getName().endsWith(".cpb") } - - javaexec { - classpath = files("$cordaCliBinDir/corda-cli.jar") - jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"] - args = ['package', 'create-cpi', - '--cpb', cpbs.singleFile.absolutePath, - '--group-policy', "${devEnvWorkspace}/GroupPolicy.json", - '--cpi-name', cpiName, - '--cpi-version', project.version, - '--file', cpiFile, - '--keystore', "${devEnvWorkspace}/signingkeys.pfx", - '--storepass', 'keystore password', - '--key', 'my-signing-key' ] - } - } - -} - -tasks.register("deployCPI") { - group = pluginImplGroupName - dependsOn('buildCPI') - doLast { - csdeHelper.uploadCertificate(signingCertAlias, signingCertFName) - csdeHelper.uploadCertificate(keystoreAlias, keystoreCertFName) - csdeHelper.deployCPI("${buildDir}/${project.archivesBaseName}-${project.version}.cpi", cpiName, project.version) - } -} - -tasks.register("createAndRegVNodes") { - group = pluginImplGroupName - dependsOn('deployCPI') - doLast { - csdeHelper.createAndRegVNodes() - } -} tasks.register('listVNodes') { - group = pluginGroupName + group = queriesGroup doLast { - csdeHelper.listVNodes() + cordaStatusQueries.listVNodes() } } tasks.register('listCPIs') { - group = pluginImplGroupName + group = queriesGroup doLast { - csdeHelper.listCPIs() + cordaStatusQueries.listCPIs() } } -// Empty task, this acts as the user entry point task. -tasks.register('deployCordapp') { - group = pluginGroupName - dependsOn("createAndRegVNodes") -} +// Build CPI tasks -tasks.register("startCorda") { - group = pluginGroupName - dependsOn('getDevCordaLite', 'getPostgresJDBC') +def buildCPIsHelper = new BuildCPIsHelper(projectContext) + +tasks.register("1-createGroupPolicy") { + group = cordappGroup + dependsOn('projInit') doLast { - mkdir devEnvWorkspace - csdeHelper.startCorda() + buildCPIsHelper.createGroupPolicy() } } -tasks.register("stopCorda") { - group = pluginGroupName +tasks.register("getNotaryServerCPB", Copy) { + group = supportingGroup + from configurations.notaryServerCPB + into cordaNotaryServerDir +} + +tasks.register('2-createKeystore') { + group = cordappGroup + dependsOn('projInit') doLast { - csdeHelper.stopCorda() + buildCPIsHelper.createKeyStore() + } +} + +tasks.register('3-buildCPIs') { + group = cordappGroup + def dependsOnTasks = subprojects.collect {it.tasks.findByName("build") } + dependsOnTasks.add('1-createGroupPolicy') + dependsOnTasks.add('2-createKeystore') + dependsOnTasks.add('getNotaryServerCPB') + dependsOn dependsOnTasks + doLast{ + buildCPIsHelper.buildCPIs() } } +// deploy CPI tasks + +def deployCPIsHelper = new DeployCPIsHelper(projectContext) + +tasks.register("4-deployCPIs") { + group = cordappGroup + dependsOn('3-buildCPIs') + doLast { + deployCPIsHelper.deployCPIs() + } +} + +// create and register Vnodes Tasks + +def createAndRegisterVNodesHelper = new CreateAndRegisterVNodesHelper(projectContext) + +tasks.register("5-createAndRegVNodes") { + group = cordappGroup + dependsOn('4-deployCPIs') + doLast { + createAndRegisterVNodesHelper.createAndRegVNodes() + } +} + +// Empty task, just acts as the Task user entry point task. +tasks.register('quickDeployCordapp') { + group = cordappGroup + dependsOn("5-createAndRegVNodes") +} + + diff --git a/buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java b/buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java new file mode 100644 index 0000000..7b20764 --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/BuildCPIsHelper.java @@ -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 configX500Ids = utils.getConfigX500Ids(pc.X500ConfigFile); + LinkedList 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 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 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 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 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 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 cmdArray) { + for (int i = 0; i < cmdArray.size(); i++) { + pc.out.print(cmdArray.get(i) + " "); + } + } + +} diff --git a/buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java b/buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java new file mode 100644 index 0000000..12a63dd --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/CordaLifeCycleHelper.java @@ -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?"); + } + } +} diff --git a/buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java b/buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java new file mode 100644 index 0000000..a3ef066 --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/CordaStatusQueries.java @@ -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 getVNodeInfo() { + Unirest.config().verifySsl(false); + return Unirest.get(pc.baseURL + "/api/v1/virtualnode/") + .basicAuth(pc.rpcUser, pc.rpcPasswd) + .asJson(); + } + public void listVNodesVerbose() { + HttpResponse vnodeResponse = getVNodeInfo(); + pc.out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString()); + } + + // X500Name, shorthash, cpiname + public void listVNodes() { + HttpResponse 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 getCpiInfo() { + Unirest.config().verifySsl(false); + return Unirest.get(pc.baseURL + "/api/v1/cpi/") + .basicAuth(pc.rpcUser, pc.rpcPasswd) + .asJson(); + } + + public void listCPIs() { + HttpResponse 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")); + } + } + } + +} diff --git a/buildSrc/src/main/java/com/r3/csde/CreateAndRegisterVNodesHelper.java b/buildSrc/src/main/java/com/r3/csde/CreateAndRegisterVNodesHelper.java new file mode 100644 index 0000000..31b2292 --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/CreateAndRegisterVNodesHelper.java @@ -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 x500Ids = utils.getConfigX500Ids(pc.X500ConfigFile); + + // For each identity check that it already exists. + Set existingX500 = new HashSet<>(); + HttpResponse 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>> 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>> response: responses.entrySet()) { + try { + HttpResponse 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 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>> 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 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 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 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 pollForVNodeShortHoldingHashIds(List x500Ids, int retryCount, int coolDownMs ) throws CsdeException { + HashMap x500NameToShortHashes = new HashMap<>(); + Set vnodesToCheck = new HashSet(x500Ids); + while(!vnodesToCheck.isEmpty() && retryCount-- > 0) { + utils.rpcWait(coolDownMs); + kong.unirest.json.JSONArray virtualNodes = (JSONArray) queries.getVNodeInfo().getBody().getObject().get("virtualNodes"); + Map vnodesMap = new HashMap(); + 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 X500ToShortIdHash) throws CsdeException { + HashSet vnodesToCheck = new HashSet(X500ToShortIdHash.keySet()); + LinkedList approved = new LinkedList(); + 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 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 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; + } + + +} diff --git a/buildSrc/src/main/java/com/r3/csde/CsdeException.java b/buildSrc/src/main/java/com/r3/csde/CsdeException.java index 163d49b..72f8fea 100644 --- a/buildSrc/src/main/java/com/r3/csde/CsdeException.java +++ b/buildSrc/src/main/java/com/r3/csde/CsdeException.java @@ -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); } diff --git a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java b/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java deleted file mode 100644 index dbb00c4..0000000 --- a/buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java +++ /dev/null @@ -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 getConfigX500Ids() throws IOException { - LinkedList 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 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 getVNodeInfo() { - Unirest.config().verifySsl(false); - return Unirest.get(baseURL + "/api/v1/virtualnode/") - .basicAuth(rpcUser, rpcPasswd) - .asJson(); - } - - public void listVNodesVerbose() { - kong.unirest.HttpResponse vnodeResponse = getVNodeInfo(); - out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString()); - } - - // X500Name, cpiname, shorthash, - public void listVNodes() { - kong.unirest.HttpResponse 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 getCpiInfo() { - Unirest.config().verifySsl(false); - return Unirest.get(baseURL + "/api/v1/cpi/") - .basicAuth(rpcUser, rpcPasswd) - .asJson(); - - } - - public void listCPIs() { - kong.unirest.HttpResponse 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 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 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 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 response) { - int status = response.getStatus(); - kong.unirest.JsonNode body = response.getBody(); - // Do not retry if successful. - if(status == 200) { - // Retry until you get an "OK"; we may see several other responses before the "OK" response. - return !(body.getObject().get("status").equals("OK")); - } - else if (status == 400){ - JSONObject details = response.getBody().getObject().getJSONObject("details"); - if( details != null ){ - String code = (String) details.getString("code"); - return !code.equals("BAD_REQUEST"); - } - else { - // 400 otherwise means some transient problem - return true; - } - } - return false; - } - - public kong.unirest.HttpResponse uploadStatus(String requestId) { - kong.unirest.HttpResponse 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 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 uploadResponse = Unirest.post(baseURL + "/api/v1/cpi/") - .field("upload", new File(cpiFName)) - .basicAuth(rpcUser, rpcPasswd) - .asJson(); - - kong.unirest.JsonNode body = uploadResponse.getBody(); - - int status = uploadResponse.getStatus(); - - out.println("Upload Status:" + status); - out.println("Pretty print the body\n" + body.toPrettyString()); - - // Expect the ID field to be a string. - if (status == 200) { - String id = (String) body.getObject().get("id"); - out.println("get id:\n" + id); - - kong.unirest.HttpResponse 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 x500Ids = getConfigX500Ids(); - LinkedList OKHoldingShortIds = new LinkedList<>(); - - // Create a list of X500 IDs we will not need to create VNodes for. - Set existingX500 = new HashSet<>(); - kong.unirest.HttpResponse 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 jsonNode = Unirest.post(baseURL + "/api/v1/virtualnode") - .body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }") - .basicAuth(rpcUser, rpcPasswd) - .asJson(); - // Logging. - - // Check this and report errors. - // 200 - OK. - // 409 - Vnode already exists. - if (jsonNode.getStatus() != 409) { - if (jsonNode.getStatus() != 200) { - reportError(jsonNode); - } else { - JSONObject thing = jsonNode.getBody().getObject().getJSONObject("holdingIdentity"); - String shortHash = (String) thing.get("shortHash"); - OKHoldingShortIds.add(shortHash); - } - } - } - else { - out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists."); - } - } - - // Register the VNodes. - for(String shortHoldingIdHash: OKHoldingShortIds) { - kong.unirest.HttpResponse 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?"); - } - } - -} diff --git a/buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java b/buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java new file mode 100644 index 0000000..d8b6d3f --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/DeployCPIsHelper.java @@ -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 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 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 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 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 uploadStatus(String requestId) { + HttpResponse 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 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 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 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); + } + } + +} diff --git a/buildSrc/src/main/java/com/r3/csde/NoPidFile.java b/buildSrc/src/main/java/com/r3/csde/NoPidFile.java deleted file mode 100644 index d7bb3dc..0000000 --- a/buildSrc/src/main/java/com/r3/csde/NoPidFile.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.r3.csde; - -public class NoPidFile extends Exception { - public NoPidFile(String message){ - super(message); - } -} \ No newline at end of file diff --git a/buildSrc/src/main/java/com/r3/csde/ProjectContext.java b/buildSrc/src/main/java/com/r3/csde/ProjectContext.java new file mode 100644 index 0000000..63e1fac --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/ProjectContext.java @@ -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 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; + } +} diff --git a/buildSrc/src/main/java/com/r3/csde/ProjectUtils.java b/buildSrc/src/main/java/com/r3/csde/ProjectUtils.java new file mode 100644 index 0000000..0b8b85d --- /dev/null +++ b/buildSrc/src/main/java/com/r3/csde/ProjectUtils.java @@ -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 getConfigX500Ids(String configFile) throws IOException { + LinkedList 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 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."); + } +} diff --git a/buildSrc/src/test/java/CsdeRpcInterfaceTests.java b/buildSrc/src/test/java/CsdeRpcInterfaceTests.java deleted file mode 100644 index dcf57d3..0000000 --- a/buildSrc/src/test/java/CsdeRpcInterfaceTests.java +++ /dev/null @@ -1,4 +0,0 @@ -import com.r3.csde.CsdeRpcInterface; - -public class CsdeRpcInterfaceTests { -} diff --git a/config/dev-net.json b/config/dev-net.json index 0d5aa28..db8a299 100644 --- a/config/dev-net.json +++ b/config/dev-net.json @@ -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"] + }] } diff --git a/contracts/build.gradle b/contracts/build.gradle new file mode 100644 index 0000000..2921da5 --- /dev/null +++ b/contracts/build.gradle @@ -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 + } + } +} diff --git a/gradle.properties b/gradle.properties index c63fb39..9538733 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,11 +2,19 @@ kotlin.code.style=official # Specify the version of the Corda-API to use. # This needs to match the version supported by the Corda Cluster the CorDapp will run on. -# cordaApiVersion=5.0.0.190-DevPreview-2 -cordaApiVersion=5.0.0.505-Beta1.0-HC00 +cordaApiVersion=5.0.0.523-Fox1.0-RC03 + +# Settings For Development Utilities +combinedWorkerVersion=5.0.0.0-Fox1.0-RC03 +simulatorVersion=5.0.0.0-Fox1.0-RC03 + +# Specify the version of the notary plugins to use. +# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version. +cordaNotaryPluginsVersion=5.0.0.0-Fox1.0-RC03 + # Specify the version of the cordapp-cpb and cordapp-cpk plugins -cordaPluginsVersion=7.0.0 +cordaPluginsVersion=7.0.1-Fox1.0-RC03 # For the time being this just needs to be set to a dummy value. platformVersion = 999 @@ -24,10 +32,6 @@ mockitoKotlinVersion=4.0.0 mockitoVersion=4.6.1 hamcrestVersion=2.2 -# Settings For Development Utilities -combinedWorkerVersion=5.0.0.0-Beta1.0-HC00 -simulatorVersion=5.0.0.0-Beta1.0-HC00 - cordaClusterURL=https://localhost:8888 cordaRpcUser=admin cordaRpcPasswd=admin @@ -35,4 +39,4 @@ devEnvWorkspace=workspace dbContainerName=CSDEpostgresql # R3 internal repository -artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Beta1.0-HC00 +artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0-RC03 diff --git a/settings.gradle b/settings.gradle index b0a936a..e9765c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,6 +21,8 @@ pluginManagement { // Root project name, used in naming the project as a whole and used in naming objects built by the project. rootProject.name = 'csde-cordapp-template-java' +include ':workflows' +include ':contracts' diff --git a/workflows/build.gradle b/workflows/build.gradle new file mode 100644 index 0000000..5ad0c76 --- /dev/null +++ b/workflows/build.gradle @@ -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 + } + } +} diff --git a/src/main/java/com/r3/developers/csdetemplate/Message.java b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/Message.java similarity index 92% rename from src/main/java/com/r3/developers/csdetemplate/Message.java rename to workflows/src/main/java/com/r3/developers/csdetemplate/workflows/Message.java index 58dc5b8..c19960d 100644 --- a/src/main/java/com/r3/developers/csdetemplate/Message.java +++ b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/Message.java @@ -1,4 +1,4 @@ -package com.r3.developers.csdetemplate; +package com.r3.developers.csdetemplate.workflows; import net.corda.v5.base.annotations.CordaSerializable; import net.corda.v5.base.types.MemberX500Name; diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlow.java similarity index 97% rename from src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java rename to workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlow.java index b0e5233..0263bc1 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlow.java +++ b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlow.java @@ -1,4 +1,4 @@ -package com.r3.developers.csdetemplate; +package com.r3.developers.csdetemplate.workflows; import net.corda.v5.application.flows.*; import net.corda.v5.application.marshalling.JsonMarshallingService; @@ -91,7 +91,7 @@ public class MyFirstFlow implements RPCStartableFlow { RequestBody for triggering the flow via http-rpc: { "clientRequestId": "r1", - "flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow", + "flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow", "requestData": { "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB" } diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowResponder.java similarity index 98% rename from src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java rename to workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowResponder.java index 504e0b0..e7a1ff2 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowResponder.java +++ b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowResponder.java @@ -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; diff --git a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowStartArgs.java similarity index 89% rename from src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java rename to workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowStartArgs.java index 4c5f2ff..51a83c6 100644 --- a/src/main/java/com/r3/developers/csdetemplate/MyFirstFlowStartArgs.java +++ b/workflows/src/main/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowStartArgs.java @@ -1,4 +1,4 @@ -package com.r3.developers.csdetemplate; +package com.r3.developers.csdetemplate.workflows; import net.corda.v5.base.types.MemberX500Name; diff --git a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java b/workflows/src/test/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowTest.java similarity index 82% rename from src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java rename to workflows/src/test/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowTest.java index 9b45d53..af2a32d 100644 --- a/src/test/java/com/r3/developers/csdetemplate/MyFirstFlowTest.java +++ b/workflows/src/test/java/com/r3/developers/csdetemplate/workflows/MyFirstFlowTest.java @@ -1,15 +1,12 @@ -package com.r3.developers.csdetemplate; +package com.r3.developers.csdetemplate.workflows; -import net.corda.simulator.HoldingIdentity; import net.corda.simulator.RequestData; import net.corda.simulator.SimulatedVirtualNode; import net.corda.simulator.Simulator; import net.corda.v5.base.types.MemberX500Name; import org.junit.jupiter.api.Test; - class MyFirstFlowTest { - // Names picked to match the corda network in config/dev-net.json private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB"); private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"); @@ -20,14 +17,10 @@ class MyFirstFlowTest { // Instantiate an instance of the simulator. Simulator simulator = new Simulator(); - // Create Alice's and Bob HoldingIDs. - HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500); - HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500); - // Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node. // Don't assign Bob's virtual node to a value. You don't need it for this particular test. - SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class); - simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class); + SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class); + simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class); // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow. MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500); @@ -46,4 +39,3 @@ class MyFirstFlowTest { assert(flowResponse.equals("Hello Alice, best wishes from Bob")); } } - From 7dd7382b3762d4d00d08d82a7ea3e5b70fda6f59 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Tue, 10 Jan 2023 09:57:15 +0000 Subject: [PATCH 22/26] Switch to Corda 5 Beta 1 and fix publishToMavenlocal --- build.gradle | 3 ++- buildSrc/gradle.properties | 4 ++-- gradle.properties | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index aa344c7..0f0a98e 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,8 @@ publishing { artifactId "corda-CSDE-java-sample" groupId project.group artifact jar - } } + + } } diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index a8865c8..18a3ef5 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -1,7 +1,7 @@ jacksonVersion = 2.13.4 unirestVersion=3.13.10 -cordaApiVersion=5.0.0.523-Fox1.0-RC03 +cordaApiVersion=5.0.0.523-Fox1.0 # R3 internal repository -artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0-RC03 +artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0 diff --git a/gradle.properties b/gradle.properties index 9538733..d5c8807 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,15 +2,15 @@ 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.523-Fox1.0-RC03 +cordaApiVersion=5.0.0.523-Fox1.0 # Settings For Development Utilities -combinedWorkerVersion=5.0.0.0-Fox1.0-RC03 -simulatorVersion=5.0.0.0-Fox1.0-RC03 +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-RC03 +cordaNotaryPluginsVersion=5.0.0.0-Fox1.0 # Specify the version of the cordapp-cpb and cordapp-cpk plugins @@ -39,4 +39,4 @@ devEnvWorkspace=workspace dbContainerName=CSDEpostgresql # R3 internal repository -artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0-RC03 +artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0 From 8c6e86875d02c82d4a19ff1ed0b2b022deb24741 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Tue, 10 Jan 2023 10:11:11 +0000 Subject: [PATCH 23/26] Fix Corda Plugins version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d5c8807..39afe47 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ cordaNotaryPluginsVersion=5.0.0.0-Fox1.0 # Specify the version of the cordapp-cpb and cordapp-cpk plugins -cordaPluginsVersion=7.0.1-Fox1.0-RC03 +cordaPluginsVersion=7.0.1-Fox # For the time being this just needs to be set to a dummy value. platformVersion = 999 From 64eb588844ef84f4e4d65cd52458e2df261f8d20 Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Tue, 10 Jan 2023 10:36:46 +0000 Subject: [PATCH 24/26] Fix Corda Plugins Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 39afe47..df4f589 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ cordaNotaryPluginsVersion=5.0.0.0-Fox1.0 # Specify the version of the cordapp-cpb and cordapp-cpk plugins -cordaPluginsVersion=7.0.1-Fox +cordaPluginsVersion=7.0.1 # For the time being this just needs to be set to a dummy value. platformVersion = 999 From e9c5f19027007cca0634948c39110ba5ef9f4c0e Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Tue, 10 Jan 2023 13:14:51 +0000 Subject: [PATCH 25/26] README.md change only, add message to warn that this is a beta release --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 88c9652..d7ef618 100644 --- a/README.md +++ b/README.md @@ -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: From 460dc0560754e732f7605ae53cb18d748ec6ea9c Mon Sep 17 00:00:00 2001 From: Chris Barratt Date: Wed, 11 Jan 2023 11:30:17 +0000 Subject: [PATCH 26/26] Just use external repositories --- build.gradle | 3 --- buildSrc/build.gradle | 3 --- buildSrc/gradle.properties | 3 --- gradle.properties | 3 --- settings.gradle | 4 +--- 5 files changed, 1 insertion(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 0f0a98e..9cb78fe 100644 --- a/build.gradle +++ b/build.gradle @@ -30,9 +30,6 @@ allprojects { repositories { // All dependencies are held in Maven Central mavenCentral() - maven { - url = "$artifactoryContextUrl/" - } } tasks.withType(Test).configureEach { diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 49f7a1f..750b4c5 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -6,9 +6,6 @@ plugins { repositories { mavenCentral() - maven { - url = "$artifactoryContextUrl/" - } } dependencies { diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index 18a3ef5..a0d6994 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -2,6 +2,3 @@ jacksonVersion = 2.13.4 unirestVersion=3.13.10 cordaApiVersion=5.0.0.523-Fox1.0 - -# R3 internal repository -artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0 diff --git a/gradle.properties b/gradle.properties index df4f589..156d05f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,6 @@ simulatorVersion=5.0.0.0-Fox1.0 # 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.1 @@ -38,5 +37,3 @@ cordaRpcPasswd=admin devEnvWorkspace=workspace dbContainerName=CSDEpostgresql -# R3 internal repository -artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Fox1.0 diff --git a/settings.gradle b/settings.gradle index e9765c8..fb1520b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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,