ported rc02 updates from kotlin
This commit is contained in:
parent
d64a21107d
commit
7d65a63f8d
@ -3,7 +3,7 @@
|
|||||||
## Note: This cut of CSDE is work in progress and has not been released yet, hence may not function as expected.
|
## Note: This cut of CSDE is work in progress and has not been released yet, hence may not function as expected.
|
||||||
|
|
||||||
|
|
||||||
To help make the process of prototyping Cordapps on Developer Preview 2 more straight forward we have developed the Cordapp Standard Development Environment (CSDE).
|
To help make the process of prototyping Cordapps on Corda 5 beta releases more straight forward we have developed the Cordapp Standard Development Environment (CSDE).
|
||||||
|
|
||||||
The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local machine. The CSDE provides:
|
The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local machine. The CSDE provides:
|
||||||
|
|
||||||
@ -22,3 +22,5 @@ The CSDE is obtained by cloning this CSDE-Cordapp-Template-java to your local ma
|
|||||||
Note, the CSDE is experimental, we may or may not release it as part of Corda 5.0, in part based on developer feedback using it.
|
Note, the CSDE is experimental, we may or may not release it as part of Corda 5.0, in part based on developer feedback using it.
|
||||||
|
|
||||||
To find out how to use the CSDE please refer to the getting started section in the Corda 5 Developer Preview 2 documentation at https://docs.r3.com/
|
To find out how to use the CSDE please refer to the getting started section in the Corda 5 Developer Preview 2 documentation at https://docs.r3.com/
|
||||||
|
|
||||||
|
(Note, to use the CSDE you must have installed the Corda CLI, make sure the version matches the version of Corda)
|
@ -26,6 +26,9 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
// All dependencies are held in Maven Central
|
// All dependencies are held in Maven Central
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url = "$artifactoryContextUrl/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Test).configureEach {
|
tasks.withType(Test).configureEach {
|
||||||
|
@ -4,8 +4,16 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def constants = new Properties()
|
||||||
|
file("$rootDir/../gradle.properties").withInputStream { InputStream input -> constants.load(input) }
|
||||||
|
def artifactoryContextUrl = constants.getProperty('artifactoryContextUrl')
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
maven {
|
||||||
|
url = "$artifactoryContextUrl/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
jacksonVersion = 2.13.4
|
jacksonVersion = 2.13.4
|
||||||
unirestVersion=3.13.10
|
unirestVersion=3.13.10
|
||||||
|
|
||||||
cordaApiVersion=5.0.0.524-Fox1.1
|
cordaApiVersion=5.0.0.665-Gecko-RC02
|
||||||
|
@ -19,7 +19,8 @@ import com.r3.csde.DeployCPIsHelper
|
|||||||
import com.r3.csde.BuildCPIsHelper
|
import com.r3.csde.BuildCPIsHelper
|
||||||
import com.r3.csde.ProjectUtils
|
import com.r3.csde.ProjectUtils
|
||||||
import com.r3.csde.CordaStatusQueries
|
import com.r3.csde.CordaStatusQueries
|
||||||
import com.r3.csde.CreateAndRegisterVNodesHelper
|
import com.r3.csde.VNodesHelper
|
||||||
|
import com.r3.csde.NetworkConfig
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
@ -113,6 +114,8 @@ def projectContext = new ProjectContext(project,
|
|||||||
cordaNotaryPluginsVersion
|
cordaNotaryPluginsVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def networkConfig = new NetworkConfig("config/static-network-config.json")
|
||||||
|
|
||||||
def utils = new ProjectUtils()
|
def utils = new ProjectUtils()
|
||||||
|
|
||||||
// Initiate workspace folder
|
// Initiate workspace folder
|
||||||
@ -183,7 +186,7 @@ tasks.register('listCPIs') {
|
|||||||
|
|
||||||
// Build CPI tasks
|
// Build CPI tasks
|
||||||
|
|
||||||
def buildCPIsHelper = new BuildCPIsHelper(projectContext)
|
def buildCPIsHelper = new BuildCPIsHelper(projectContext, networkConfig)
|
||||||
|
|
||||||
tasks.register("1-createGroupPolicy") {
|
tasks.register("1-createGroupPolicy") {
|
||||||
group = cordappGroup
|
group = cordappGroup
|
||||||
@ -224,7 +227,7 @@ tasks.register('3-buildCPIs') {
|
|||||||
|
|
||||||
def deployCPIsHelper = new DeployCPIsHelper(projectContext)
|
def deployCPIsHelper = new DeployCPIsHelper(projectContext)
|
||||||
|
|
||||||
tasks.register("4-deployCPIs") {
|
tasks.register('4-deployCPIs') {
|
||||||
group = cordappGroup
|
group = cordappGroup
|
||||||
dependsOn('3-buildCPIs')
|
dependsOn('3-buildCPIs')
|
||||||
doLast {
|
doLast {
|
||||||
@ -232,23 +235,14 @@ tasks.register("4-deployCPIs") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create and register Vnodes Tasks
|
// Setup VNodes tasks
|
||||||
|
|
||||||
def createAndRegisterVNodesHelper = new CreateAndRegisterVNodesHelper(projectContext)
|
def vNodesHelper = new VNodesHelper(projectContext, networkConfig )
|
||||||
|
|
||||||
tasks.register("5-createAndRegVNodes") {
|
tasks.register('5-vNodeSetup') {
|
||||||
group = cordappGroup
|
group = cordappGroup
|
||||||
dependsOn('4-deployCPIs')
|
dependsOn('4-deployCPIs')
|
||||||
doLast {
|
doLast {
|
||||||
createAndRegisterVNodesHelper.createAndRegVNodes()
|
vNodesHelper.vNodesSetup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty task, just acts as the Task user entry point task.
|
|
||||||
tasks.register('quickDeployCordapp') {
|
|
||||||
group = cordappGroup
|
|
||||||
dependsOn("5-createAndRegVNodes")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,26 +2,32 @@ package com.r3.csde;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
|
||||||
public class BuildCPIsHelper {
|
public class BuildCPIsHelper {
|
||||||
|
|
||||||
public ProjectContext pc;
|
public ProjectContext pc;
|
||||||
public ProjectUtils utils ;
|
public ProjectUtils utils;
|
||||||
public BuildCPIsHelper(ProjectContext _pc) {
|
|
||||||
|
public NetworkConfig config;
|
||||||
|
public BuildCPIsHelper(ProjectContext _pc, NetworkConfig _config) {
|
||||||
pc = _pc;
|
pc = _pc;
|
||||||
utils = new ProjectUtils(pc);
|
utils = new ProjectUtils(pc);
|
||||||
|
config = _config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createGroupPolicy() throws IOException {
|
public void createGroupPolicy() throws IOException {
|
||||||
|
|
||||||
File groupPolicyFile = new File(String.format("%s/GroupPolicy.json", pc.devEnvWorkspace));
|
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()));
|
File devnetFile = new File(pc.project.getRootDir() + "/" + config.getConfigFilePath());
|
||||||
|
|
||||||
|
|
||||||
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
|
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
|
||||||
|
|
||||||
pc.out.println("createGroupPolicy: Creating a GroupPolicy");
|
pc.out.println("createGroupPolicy: Creating a GroupPolicy");
|
||||||
|
|
||||||
LinkedList<String> configX500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
|
List<String> configX500Ids = config.getX500Names();
|
||||||
LinkedList<String> commandList = new LinkedList<>();
|
LinkedList<String> commandList = new LinkedList<>();
|
||||||
|
|
||||||
commandList.add(String.format("%s/java", pc.javaBinDir));
|
commandList.add(String.format("%s/java", pc.javaBinDir));
|
||||||
@ -192,6 +198,8 @@ public class BuildCPIsHelper {
|
|||||||
commandList.add("--key");
|
commandList.add("--key");
|
||||||
commandList.add("my-signing-key"); // todo: should be passed as context property
|
commandList.add("my-signing-key"); // todo: should be passed as context property
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(commandList);
|
ProcessBuilder pb = new ProcessBuilder(commandList);
|
||||||
pb.redirectErrorStream(true);
|
pb.redirectErrorStream(true);
|
||||||
Process proc = pb.start();
|
Process proc = pb.start();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.r3.csde;
|
package com.r3.csde;
|
||||||
|
|
||||||
|
import kong.unirest.Unirest;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -9,7 +11,7 @@ import java.util.Scanner;
|
|||||||
/**
|
/**
|
||||||
* Manages Bringing corda up, testing for liveness and taking corda down
|
* Manages Bringing corda up, testing for liveness and taking corda down
|
||||||
*/
|
*/
|
||||||
|
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
|
||||||
public class CordaLifeCycleHelper {
|
public class CordaLifeCycleHelper {
|
||||||
|
|
||||||
ProjectContext pc;
|
ProjectContext pc;
|
||||||
@ -18,15 +20,16 @@ public class CordaLifeCycleHelper {
|
|||||||
public CordaLifeCycleHelper(ProjectContext _pc) {
|
public CordaLifeCycleHelper(ProjectContext _pc) {
|
||||||
pc = _pc;
|
pc = _pc;
|
||||||
utils = new ProjectUtils(pc);
|
utils = new ProjectUtils(pc);
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void startCorda() throws IOException {
|
public void startCorda() throws IOException {
|
||||||
PrintStream pidStore = new PrintStream(new FileOutputStream(pc.cordaPidCache));
|
PrintStream pidStore = new PrintStream(new FileOutputStream(pc.cordaPidCache));
|
||||||
File combinedWorkerJar = pc.project.getConfigurations().getByName("combinedWorker").getSingleFile();
|
File combinedWorkerJar = pc.project.getConfigurations().getByName("combinedWorker").getSingleFile();
|
||||||
|
|
||||||
|
// Manual version of the command to start postgres (for reference):
|
||||||
|
// docker run -d --rm -p5432:5432 --name CSDEpostgresql -e POSTGRES_DB=cordacluster -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password postgres:latest
|
||||||
|
|
||||||
// todo: make consistent with other ProcessBuilder set ups (use cmdArray)
|
|
||||||
new ProcessBuilder(
|
new ProcessBuilder(
|
||||||
"docker",
|
"docker",
|
||||||
"run", "-d", "--rm",
|
"run", "-d", "--rm",
|
||||||
@ -36,37 +39,33 @@ public class CordaLifeCycleHelper {
|
|||||||
"-e", "POSTGRES_USER=postgres",
|
"-e", "POSTGRES_USER=postgres",
|
||||||
"-e", "POSTGRES_PASSWORD=password",
|
"-e", "POSTGRES_PASSWORD=password",
|
||||||
"postgres:latest").start();
|
"postgres:latest").start();
|
||||||
// todo: is there a better way of doing this - ie poll for readiness
|
|
||||||
|
// todo: we should poll for readiness not wait 10 seconds, see https://r3-cev.atlassian.net/browse/CORE-11626
|
||||||
utils.rpcWait(10000);
|
utils.rpcWait(10000);
|
||||||
|
|
||||||
ProcessBuilder procBuild = new ProcessBuilder(pc.javaBinDir + "/java",
|
ProcessBuilder procBuild = new ProcessBuilder(pc.javaBinDir + "/java",
|
||||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
|
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
|
||||||
|
"-DsecurityMangerEnabled=false",
|
||||||
|
"-Dlog4j.configurationFile=" + pc.project.getRootDir() + "/config/log4j2.xml",
|
||||||
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
|
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
|
||||||
"-jar",
|
"-jar",
|
||||||
combinedWorkerJar.toString(),
|
combinedWorkerJar.toString(),
|
||||||
"--instanceId=0",
|
"--instance-id=0",
|
||||||
"-mbus.busType=DATABASE",
|
"-mbus.busType=DATABASE",
|
||||||
"-spassphrase=password",
|
"-spassphrase=password",
|
||||||
"-ssalt=salt",
|
"-ssalt=salt",
|
||||||
"-spassphrase=password",
|
|
||||||
"-ssalt=salt",
|
|
||||||
"-ddatabase.user=user",
|
"-ddatabase.user=user",
|
||||||
"-ddatabase.pass=password",
|
"-ddatabase.pass=password",
|
||||||
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
|
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
|
||||||
"-ddatabase.jdbc.directory="+pc.JDBCDir);
|
"-ddatabase.jdbc.directory="+pc.JDBCDir);
|
||||||
|
|
||||||
|
|
||||||
procBuild.redirectErrorStream(true);
|
procBuild.redirectErrorStream(true);
|
||||||
Process proc = procBuild.start();
|
Process proc = procBuild.start();
|
||||||
pidStore.print(proc.pid());
|
pidStore.print(proc.pid());
|
||||||
pc.out.println("Corda Process-id="+proc.pid());
|
pc.out.println("Corda Process-id="+proc.pid());
|
||||||
|
proc.getInputStream().transferTo(pc.out);
|
||||||
|
|
||||||
// todo: should poll for readiness before returning
|
// todo: we should poll for readiness before completing the startCorda task, see https://r3-cev.atlassian.net/browse/CORE-11625
|
||||||
// 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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import kong.unirest.json.JSONArray;
|
|||||||
import kong.unirest.json.JSONObject;
|
import kong.unirest.json.JSONObject;
|
||||||
import kong.unirest.HttpResponse;
|
import kong.unirest.HttpResponse;
|
||||||
|
|
||||||
|
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
|
||||||
public class CordaStatusQueries {
|
public class CordaStatusQueries {
|
||||||
|
|
||||||
ProjectContext pc;
|
ProjectContext pc;
|
||||||
|
@ -1,289 +0,0 @@
|
|||||||
package com.r3.csde;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import kong.unirest.HttpResponse;
|
|
||||||
import kong.unirest.JsonNode;
|
|
||||||
import kong.unirest.Unirest;
|
|
||||||
import kong.unirest.json.JSONArray;
|
|
||||||
import kong.unirest.json.JSONObject;
|
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import javax.naming.ConfigurationException;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import static java.net.HttpURLConnection.*;
|
|
||||||
|
|
||||||
public class CreateAndRegisterVNodesHelper {
|
|
||||||
|
|
||||||
ProjectContext pc;
|
|
||||||
ProjectUtils utils;
|
|
||||||
CordaStatusQueries queries;
|
|
||||||
|
|
||||||
public CreateAndRegisterVNodesHelper(ProjectContext _pc) {
|
|
||||||
pc = _pc;
|
|
||||||
queries = new CordaStatusQueries(pc);
|
|
||||||
utils = new ProjectUtils(pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createAndRegVNodes() throws IOException, CsdeException, ConfigurationException {
|
|
||||||
Unirest.config().verifySsl(false);
|
|
||||||
String appCpiCheckSum = getLastCPIUploadChkSum( pc.CPIUploadStatusFName );
|
|
||||||
String notaryCpiCheckSum = getLastCPIUploadChkSum( pc.CPIUploadStatusFName, "-NotaryServer" );
|
|
||||||
|
|
||||||
LinkedList<String> x500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
|
|
||||||
|
|
||||||
// For each identity check that it already exists.
|
|
||||||
Set<MemberX500Name> existingX500 = new HashSet<>();
|
|
||||||
HttpResponse<JsonNode> vnodeListResponse = queries.getVNodeInfo();
|
|
||||||
|
|
||||||
JSONArray virtualNodesJson = (JSONArray) vnodeListResponse.getBody().getObject().get("virtualNodes");
|
|
||||||
for(Object o: virtualNodesJson){
|
|
||||||
if(o instanceof JSONObject) {
|
|
||||||
JSONObject idObj = ((JSONObject) o).getJSONObject("holdingIdentity");
|
|
||||||
String x500id = (String) idObj.get("x500Name");
|
|
||||||
existingX500.add(MemberX500Name.parse( x500id) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, CompletableFuture<HttpResponse<JsonNode>>> responses = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
// Create the VNodes
|
|
||||||
for(String x500id: x500Ids) {
|
|
||||||
if(!existingX500.contains(MemberX500Name.parse(x500id) )) {
|
|
||||||
String cpiCheckSum = getNotaryRepresentatives().containsKey(x500id) ? notaryCpiCheckSum : appCpiCheckSum;
|
|
||||||
|
|
||||||
pc.out.println("Creating VNode for x500id=\"" + x500id + "\" cpi checksum=" + cpiCheckSum);
|
|
||||||
responses.put(x500id, Unirest
|
|
||||||
.post(pc.baseURL + "/api/v1/virtualnode")
|
|
||||||
.body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }")
|
|
||||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
|
||||||
.asJsonAsync()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pc.out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.out.println("Waiting for VNode creation results...");
|
|
||||||
|
|
||||||
for (Map.Entry<String, CompletableFuture<HttpResponse<JsonNode>>> response: responses.entrySet()) {
|
|
||||||
try {
|
|
||||||
HttpResponse<JsonNode> jsonNode = response.getValue().get();
|
|
||||||
// need to check this and report errors.
|
|
||||||
// 200/HTTP_OK - OK
|
|
||||||
// 409/HTTP_CONFLICT - Vnode already exists
|
|
||||||
// 500/HTTP_INTERNAL_ERROR
|
|
||||||
// - Can mean that the request timed out.
|
|
||||||
// - However, the cluster may still have created the V-node successfully, so we want to poll later.
|
|
||||||
pc.out.println("Vnode creation end point status:" + jsonNode.getStatus());
|
|
||||||
switch(jsonNode.getStatus()) {
|
|
||||||
case HTTP_OK: break;
|
|
||||||
case HTTP_CONFLICT: break;
|
|
||||||
case HTTP_INTERNAL_ERROR: break;
|
|
||||||
default:
|
|
||||||
utils.reportError(jsonNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
|
||||||
throw new CsdeException("Unexpected exception while waiting for response to " +
|
|
||||||
"membership submission for holding identity" + response.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> OKHoldingX500AndShortIds = pollForVNodeShortHoldingHashIds(x500Ids, 60, 5000);
|
|
||||||
|
|
||||||
// Register the VNodes
|
|
||||||
responses.clear();
|
|
||||||
|
|
||||||
for(String okId: OKHoldingX500AndShortIds.keySet()) {
|
|
||||||
responses.put(okId, Unirest
|
|
||||||
.post(pc.baseURL + "/api/v1/membership/" + OKHoldingX500AndShortIds.get(okId))
|
|
||||||
.body(getMemberRegistrationBody(okId))
|
|
||||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
|
||||||
.asJsonAsync( response ->
|
|
||||||
pc.out.println("Vnode membership submission for \"" + okId + "\"" +
|
|
||||||
System.lineSeparator() + response.getBody().toPrettyString()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.out.println("Vnode membership requests submitted, waiting for acknowledgement from MGM...");
|
|
||||||
|
|
||||||
for (Map.Entry<String, CompletableFuture<HttpResponse<JsonNode>>> response: responses.entrySet()) {
|
|
||||||
try {
|
|
||||||
response.getValue().get();
|
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
|
||||||
throw new CsdeException("Unexpected exception while waiting for response to " +
|
|
||||||
"membership submission for holding identity" + response.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pollForCompleteMembershipRegistration(OKHoldingX500AndShortIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName) throws IOException, NullPointerException {
|
|
||||||
return getLastCPIUploadChkSum(CPIUploadStatusFName, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName,
|
|
||||||
String uploadStatusQualifier) throws IOException, NullPointerException {
|
|
||||||
|
|
||||||
String qualifiedCPIUploadStatusFName =
|
|
||||||
CPIUploadStatusFName.replace(".json", uploadStatusQualifier + ".json");
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
FileInputStream in = new FileInputStream(qualifiedCPIUploadStatusFName);
|
|
||||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
|
||||||
|
|
||||||
String checksum = jsonNode.get("cpiFileChecksum").toString();
|
|
||||||
if(checksum == null || checksum.equals("null")) {
|
|
||||||
throw new NullPointerException("Missing cpiFileChecksum in file " +
|
|
||||||
qualifiedCPIUploadStatusFName + " with contents:" + jsonNode);
|
|
||||||
}
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
// KV pairs of representative x500 name and corresponding notary service x500 name
|
|
||||||
public Map<String, String> getNotaryRepresentatives() throws IOException, ConfigurationException {
|
|
||||||
if (pc.notaryRepresentatives == null) {
|
|
||||||
pc.notaryRepresentatives = new HashMap<>();
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
FileInputStream in = new FileInputStream(pc.X500ConfigFile);
|
|
||||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
|
||||||
|
|
||||||
List<String> identities = utils.getConfigX500Ids(pc.X500ConfigFile);
|
|
||||||
|
|
||||||
for (com.fasterxml.jackson.databind.JsonNode notary : jsonNode.get("notaries")) {
|
|
||||||
|
|
||||||
String svcX500Id = utils.jsonNodeToString(notary.get("serviceX500Name"));
|
|
||||||
|
|
||||||
com.fasterxml.jackson.databind.JsonNode repsForThisService = notary.get("representatives");
|
|
||||||
|
|
||||||
if (repsForThisService.isEmpty()) {
|
|
||||||
throw new ConfigurationException(
|
|
||||||
"Notary service \"" + svcX500Id + "\" must have at least one representative.");
|
|
||||||
} else if (repsForThisService.size() > 1) {
|
|
||||||
// Temporary restriction while the MGM only supports a 1-1 association
|
|
||||||
throw new ConfigurationException(
|
|
||||||
"Notary service \"" + svcX500Id + "\" can only have a single representative at this time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (com.fasterxml.jackson.databind.JsonNode representative : repsForThisService) {
|
|
||||||
|
|
||||||
String repAsString = utils.jsonNodeToString(representative);
|
|
||||||
|
|
||||||
if (identities.contains(repAsString)) {
|
|
||||||
pc.notaryRepresentatives.put(repAsString, svcX500Id);
|
|
||||||
} else {
|
|
||||||
throw new ConfigurationException(
|
|
||||||
"Notary representative \"" + repAsString + "\" is not a valid identity");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pc.notaryRepresentatives;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getMemberRegistrationBody(String memberX500Name) throws ConfigurationException, IOException {
|
|
||||||
Map<String, String> notaryReps = getNotaryRepresentatives();
|
|
||||||
|
|
||||||
String context = "\"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\"" + (
|
|
||||||
notaryReps.containsKey(memberX500Name)
|
|
||||||
? ", \"corda.roles.0\" : \"notary\", " +
|
|
||||||
"\"corda.notary.service.name\" : \"" + notaryReps.get(memberX500Name) + "\", " +
|
|
||||||
// This will need revisiting in the long term when additional protocols are added, and will
|
|
||||||
// need to be specified in config. We will also need to review the hard-coded name once
|
|
||||||
// notary plugin selection logic is re-instated in CORE-7248.
|
|
||||||
"\"corda.notary.service.plugin\" : \"net.corda.notary.NonValidatingNotary\""
|
|
||||||
: ""
|
|
||||||
);
|
|
||||||
|
|
||||||
return "{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { " + context + " } } }";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Map<String, String> pollForVNodeShortHoldingHashIds(List<String> x500Ids, int retryCount, int coolDownMs ) throws CsdeException {
|
|
||||||
HashMap<String, String> x500NameToShortHashes = new HashMap<>();
|
|
||||||
Set<String> vnodesToCheck = new HashSet<String>(x500Ids);
|
|
||||||
while(!vnodesToCheck.isEmpty() && retryCount-- > 0) {
|
|
||||||
utils.rpcWait(coolDownMs);
|
|
||||||
kong.unirest.json.JSONArray virtualNodes = (JSONArray) queries.getVNodeInfo().getBody().getObject().get("virtualNodes");
|
|
||||||
Map<String, String> vnodesMap = new HashMap<String, String>();
|
|
||||||
for (Object virtualNode : virtualNodes) {
|
|
||||||
if (virtualNode instanceof JSONObject) {
|
|
||||||
JSONObject idObj = ((JSONObject) virtualNode).getJSONObject("holdingIdentity");
|
|
||||||
vnodesMap.put(idObj.get("x500Name").toString(), idObj.get("shortHash").toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(String x500Name: vnodesToCheck) {
|
|
||||||
if(vnodesMap.containsKey(x500Name)) {
|
|
||||||
x500NameToShortHashes.put(x500Name, vnodesMap.get(x500Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vnodesMap.keySet().forEach(vnodesToCheck::remove);
|
|
||||||
}
|
|
||||||
if(!vnodesToCheck.isEmpty()) {
|
|
||||||
throw new CsdeException("VNode creation timed out. Not all expected vnodes were reported as created:" + vnodesToCheck.toString());
|
|
||||||
}
|
|
||||||
return x500NameToShortHashes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pollForCompleteMembershipRegistration(Map<String, String> X500ToShortIdHash) throws CsdeException {
|
|
||||||
HashSet<String> vnodesToCheck = new HashSet<String>(X500ToShortIdHash.keySet());
|
|
||||||
LinkedList<String> approved = new LinkedList<String>();
|
|
||||||
while (!vnodesToCheck.isEmpty()) {
|
|
||||||
utils.rpcWait(2000);
|
|
||||||
approved.clear();
|
|
||||||
for (String vnodeX500 : vnodesToCheck) {
|
|
||||||
try {
|
|
||||||
pc.out.println("Checking membership registration progress for v-node '" + vnodeX500 + "':");
|
|
||||||
HttpResponse<JsonNode> statusResponse = Unirest
|
|
||||||
.get(pc.baseURL + "/api/v1/membership/" + X500ToShortIdHash.get(vnodeX500) + "/")
|
|
||||||
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
|
||||||
.asJson();
|
|
||||||
if (isMembershipRegComplete(statusResponse)) {
|
|
||||||
approved.add(vnodeX500);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new CsdeException("Error when registering V-Node '" + vnodeX500 + "'", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
approved.forEach(vnodesToCheck::remove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isMembershipRegComplete(HttpResponse<JsonNode> response) throws CsdeException {
|
|
||||||
if(response.getStatus() == HTTP_OK) {
|
|
||||||
JsonNode responseBody = response.getBody();
|
|
||||||
pc.out.println(responseBody.toPrettyString());
|
|
||||||
if(responseBody.getArray().length() > 0) {
|
|
||||||
JSONObject memRegStatusInfo = (JSONObject) responseBody
|
|
||||||
.getArray()
|
|
||||||
.getJSONObject(0);
|
|
||||||
String memRegStatus = memRegStatusInfo.get("registrationStatus").toString();
|
|
||||||
if (memRegStatus.equals("DECLINED")) {
|
|
||||||
throw new CsdeException("V-Node membership registration declined by Corda");
|
|
||||||
}
|
|
||||||
return memRegStatus.equals("APPROVED");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
utils.reportError(response);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -5,15 +5,14 @@ import kong.unirest.Unirest;
|
|||||||
import kong.unirest.json.JSONArray;
|
import kong.unirest.json.JSONArray;
|
||||||
import kong.unirest.json.JSONObject;
|
import kong.unirest.json.JSONObject;
|
||||||
import kong.unirest.HttpResponse;
|
import kong.unirest.HttpResponse;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
|
||||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||||
import static java.net.HttpURLConnection.HTTP_OK;
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
|
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
|
||||||
public class DeployCPIsHelper {
|
public class DeployCPIsHelper {
|
||||||
|
|
||||||
public DeployCPIsHelper() {
|
public DeployCPIsHelper() {
|
||||||
|
40
buildSrc/src/main/java/com/r3/csde/NetworkConfig.java
Normal file
40
buildSrc/src/main/java/com/r3/csde/NetworkConfig.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package com.r3.csde;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class reads the network config from the json file and makes it available as a list of VNodes.
|
||||||
|
*/
|
||||||
|
public class NetworkConfig {
|
||||||
|
|
||||||
|
private List<VNode> vNodes;
|
||||||
|
private String configFilePath;
|
||||||
|
|
||||||
|
public NetworkConfig(String _configFilePath) throws CsdeException {
|
||||||
|
configFilePath = _configFilePath;
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
FileInputStream in = new FileInputStream(configFilePath);
|
||||||
|
vNodes = mapper.readValue(in, new TypeReference<List<VNode>>() {
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CsdeException("Failed to read static network configuration file, with exception: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getConfigFilePath() { return configFilePath; }
|
||||||
|
|
||||||
|
List<VNode> getVNodes() { return vNodes; }
|
||||||
|
|
||||||
|
List<String> getX500Names() {
|
||||||
|
return vNodes.stream().map(vn -> vn.getX500Name()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package com.r3.csde;
|
package com.r3.csde;
|
||||||
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -14,8 +13,9 @@ public class ProjectContext {
|
|||||||
int retryWaitMs = 1000;
|
int retryWaitMs = 1000;
|
||||||
PrintStream out = System.out;
|
PrintStream out = System.out;
|
||||||
String CPIUploadStatusBaseName = "CPIFileStatus.json";
|
String CPIUploadStatusBaseName = "CPIFileStatus.json";
|
||||||
|
String NotaryCPIUploadBaseName = "CPIFileStatus-NotaryServer.json";
|
||||||
String CPIUploadStatusFName;
|
String CPIUploadStatusFName;
|
||||||
String X500ConfigFile = "config/dev-net.json";
|
String NotaryCPIUploadStatusFName;
|
||||||
String javaBinDir;
|
String javaBinDir;
|
||||||
String cordaPidCache = "CordaPIDCache.dat";
|
String cordaPidCache = "CordaPIDCache.dat";
|
||||||
String dbContainerName;
|
String dbContainerName;
|
||||||
@ -67,6 +67,7 @@ public class ProjectContext {
|
|||||||
dbContainerName = inDbContainerName;
|
dbContainerName = inDbContainerName;
|
||||||
JDBCDir = inJDBCDir;
|
JDBCDir = inJDBCDir;
|
||||||
CPIUploadStatusFName = workspaceDir + "/" + CPIUploadStatusBaseName;
|
CPIUploadStatusFName = workspaceDir + "/" + CPIUploadStatusBaseName;
|
||||||
|
NotaryCPIUploadStatusFName = workspaceDir + "/" + NotaryCPIUploadBaseName;
|
||||||
signingCertAlias = inSigningCertAlias;
|
signingCertAlias = inSigningCertAlias;
|
||||||
signingCertFName = inSigningCertFName;
|
signingCertFName = inSigningCertFName;
|
||||||
keystoreAlias = inKeystoreAlias;
|
keystoreAlias = inKeystoreAlias;
|
||||||
|
@ -1,25 +1,19 @@
|
|||||||
package com.r3.csde;
|
package com.r3.csde;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import kong.unirest.HttpResponse;
|
import kong.unirest.HttpResponse;
|
||||||
import kong.unirest.JsonNode;
|
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;
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
|
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
|
||||||
public class ProjectUtils {
|
public class ProjectUtils {
|
||||||
|
|
||||||
ProjectContext pc;
|
ProjectContext pc;
|
||||||
|
|
||||||
ProjectUtils(ProjectContext _pc) {
|
ProjectUtils(ProjectContext _pc) {
|
||||||
pc = _pc;
|
pc = _pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void rpcWait(int millis) {
|
void rpcWait(int millis) {
|
||||||
try {
|
try {
|
||||||
sleep(millis);
|
sleep(millis);
|
||||||
@ -29,36 +23,7 @@ public class ProjectUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rpcWait() {
|
public void reportError(HttpResponse<JsonNode> response) throws CsdeException {
|
||||||
rpcWait( pc.retryWaitMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkedList<String> getConfigX500Ids(String configFile) throws IOException {
|
|
||||||
LinkedList<String> x500Ids = new LinkedList<>();
|
|
||||||
// com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
|
|
||||||
FileInputStream in = new FileInputStream(configFile);
|
|
||||||
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
|
||||||
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
|
|
||||||
x500Ids.add(jsonNodeToString(identity));
|
|
||||||
}
|
|
||||||
return x500Ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String jsonNodeToString(com.fasterxml.jackson.databind.JsonNode jsonNode) {
|
|
||||||
String jsonString = jsonNode.toString();
|
|
||||||
return jsonString.substring(1, jsonString.length()-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadFile(String url, String targetPath) {
|
|
||||||
Unirest.get(url)
|
|
||||||
.asFile(targetPath)
|
|
||||||
.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reportError(@NotNull HttpResponse<JsonNode> response) throws CsdeException {
|
|
||||||
|
|
||||||
pc.out.println("*** *** ***");
|
pc.out.println("*** *** ***");
|
||||||
pc.out.println("Unexpected response from Corda");
|
pc.out.println("Unexpected response from Corda");
|
||||||
|
25
buildSrc/src/main/java/com/r3/csde/VNode.java
Normal file
25
buildSrc/src/main/java/com/r3/csde/VNode.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package com.r3.csde;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is a representation of a Vnode used to express the vNodes required on the network.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class VNode {
|
||||||
|
private String x500Name;
|
||||||
|
private String cpi;
|
||||||
|
|
||||||
|
private String serviceX500Name;
|
||||||
|
|
||||||
|
public VNode() { }
|
||||||
|
|
||||||
|
public String getX500Name(){ return x500Name; }
|
||||||
|
public void setX500Name(String _x500Name) { x500Name = _x500Name; }
|
||||||
|
|
||||||
|
public String getCpi() { return cpi; }
|
||||||
|
public void setCpi(String _cpi) { cpi = _cpi; }
|
||||||
|
|
||||||
|
public String getServiceX500Name() { return serviceX500Name; }
|
||||||
|
public void setServiceX500Name(String _name) { serviceX500Name = _name; }
|
||||||
|
|
||||||
|
}
|
288
buildSrc/src/main/java/com/r3/csde/VNodesHelper.java
Normal file
288
buildSrc/src/main/java/com/r3/csde/VNodesHelper.java
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
package com.r3.csde;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.r3.csde.dtos.*;
|
||||||
|
import kong.unirest.HttpResponse;
|
||||||
|
import kong.unirest.JsonNode;
|
||||||
|
import kong.unirest.Unirest;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The VNodesHelper class is used to create and register the Vnodes specified in the static-network-config.json file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class VNodesHelper {
|
||||||
|
private ProjectContext pc;
|
||||||
|
private ProjectUtils utils;
|
||||||
|
private NetworkConfig config;
|
||||||
|
private ObjectMapper mapper;
|
||||||
|
|
||||||
|
public VNodesHelper(ProjectContext _pc, NetworkConfig _config) {
|
||||||
|
pc = _pc;
|
||||||
|
utils = new ProjectUtils(pc);
|
||||||
|
config = _config;
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
mapper = new ObjectMapper();
|
||||||
|
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for setting up vnodes, called from the 5-vNodeSetup task in csde.gradle
|
||||||
|
*/
|
||||||
|
public void vNodesSetup() throws CsdeException {
|
||||||
|
List<VNode> requiredNodes = config.getVNodes();
|
||||||
|
createVNodes(requiredNodes);
|
||||||
|
registerVNodes(requiredNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates vnodes specified in the config if they don't already exist.
|
||||||
|
* @param requiredNodes represents the list of VNodes as specified in the network Config json file (static-network-config.json)
|
||||||
|
*/
|
||||||
|
private void createVNodes(List<VNode> requiredNodes) throws CsdeException {
|
||||||
|
|
||||||
|
// Get existing Nodes.
|
||||||
|
List<VirtualNodeInfoDTO> existingVNodes = getExistingNodes();
|
||||||
|
|
||||||
|
// Check if each required vnode already exist, if not create it.
|
||||||
|
for (VNode vn : requiredNodes) {
|
||||||
|
// Match on x500 and cpi name
|
||||||
|
List<VirtualNodeInfoDTO> matches = existingVNodes
|
||||||
|
.stream()
|
||||||
|
.filter(existing ->
|
||||||
|
existing.getHoldingIdentity().getX500Name().equals( vn.getX500Name()) &&
|
||||||
|
existing.getCpiIdentifier().getCpiName().equals(vn.getCpi()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (matches.size() == 0) {
|
||||||
|
createVNode(vn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of the virtual nodes which have already been created.
|
||||||
|
* @return a list of the virtual nodes which have already been created.
|
||||||
|
*/
|
||||||
|
private List<VirtualNodeInfoDTO> getExistingNodes () throws CsdeException {
|
||||||
|
|
||||||
|
HttpResponse<JsonNode> response = Unirest.get(pc.baseURL + "/api/v1/virtualnode")
|
||||||
|
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
if (response.getStatus() != HTTP_OK) {
|
||||||
|
throw new CsdeException("Failed to get Existing vNodes, response status: "+ response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return mapper.readValue(response.getBody().toString(), VirtualNodesDTO.class).getVirtualNodes();
|
||||||
|
} catch (Exception e){
|
||||||
|
throw new CsdeException("Failed to get Existing vNodes with exception: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a vnode on the Corda cluster from the VNode info.
|
||||||
|
* @param vNode represents a virtual node using VNode Class.
|
||||||
|
*/
|
||||||
|
private void createVNode(VNode vNode) throws CsdeException {
|
||||||
|
|
||||||
|
pc.out.println("Creating virtual node for "+ vNode.getX500Name());
|
||||||
|
|
||||||
|
// Reads the current CPIFileChecksum value and checks it has been uploaded.
|
||||||
|
String cpiCheckSum = getCpiCheckSum(vNode);
|
||||||
|
if (!checkCpiUploaded(cpiCheckSum)) throw new CsdeException("Cpi " + cpiCheckSum + " not uploaded.");
|
||||||
|
|
||||||
|
// Creates the vnode on Cluster
|
||||||
|
HttpResponse<JsonNode> response = Unirest.post(pc.baseURL + "/api/v1/virtualnode")
|
||||||
|
.body("{ \"request\" : { \"cpiFileChecksum\": \"" + cpiCheckSum + "\", \"x500Name\": \"" + vNode.getX500Name() + "\" } }")
|
||||||
|
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
if (response.getStatus() != HTTP_OK) {
|
||||||
|
throw new CsdeException("Creation of virtual node failed with response status: " + response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the latest CPI checksums from file.
|
||||||
|
* @param vNode represents a virtual node using VNode Class.
|
||||||
|
*/
|
||||||
|
private String getCpiCheckSum(VNode vNode) throws CsdeException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
String file = (vNode.getServiceX500Name() == null) ? pc.CPIUploadStatusFName : pc.NotaryCPIUploadStatusFName;
|
||||||
|
FileInputStream in = new FileInputStream(file);
|
||||||
|
CPIFileStatusDTO statusDTO = mapper.readValue(in, CPIFileStatusDTO.class);
|
||||||
|
return statusDTO.getCpiFileChecksum().toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CsdeException("Failed to read CPI checksum from file, with error: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCpiUploaded(String cpiCheckSum) throws CsdeException {
|
||||||
|
|
||||||
|
HttpResponse<JsonNode> response = Unirest.get(pc.baseURL + "/api/v1/cpi")
|
||||||
|
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
if (response.getStatus() != HTTP_OK) {
|
||||||
|
throw new CsdeException("Failed to check cpis, response status: " + response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
GetCPIsResponseDTO cpisResponse = mapper.readValue(
|
||||||
|
response.getBody().toString(), GetCPIsResponseDTO.class);
|
||||||
|
|
||||||
|
for (CpiMetadataDTO cpi: cpisResponse.getCpis()) {
|
||||||
|
if (Objects.equals(cpi.getCpiFileChecksum(), cpiCheckSum)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Returns false if no cpis were returned or the cpiCheckSum didnt' match ay results.
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CsdeException("Failed to check cpis with exception: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the required virtual nodes have been registered and if not registers them.
|
||||||
|
* @param requiredNodes represents the list of VNodes as specified in the network Config json file (static-network-config.json)
|
||||||
|
*/
|
||||||
|
private void registerVNodes(List<VNode> requiredNodes) throws CsdeException {
|
||||||
|
|
||||||
|
// There appears to be a delay between the successful post /virtualnodes synchronous call and the
|
||||||
|
// vnodes being returned in the GET /virtualnodes call. Putting a thread wait here as a quick fix
|
||||||
|
// as this will move to async mechanism post beta2.
|
||||||
|
utils.rpcWait(2000);
|
||||||
|
List<VirtualNodeInfoDTO> existingVNodes = getExistingNodes();
|
||||||
|
|
||||||
|
for (VNode vn: requiredNodes) {
|
||||||
|
// Match on x500 and cpi name
|
||||||
|
List<VirtualNodeInfoDTO> matches = existingVNodes
|
||||||
|
.stream()
|
||||||
|
.filter(existing ->
|
||||||
|
existing.getHoldingIdentity().getX500Name().equals( vn.getX500Name()) &&
|
||||||
|
existing.getCpiIdentifier().getCpiName().equals(vn.getCpi()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (matches.size() == 0) {
|
||||||
|
throw new CsdeException("Registration failed because virtual node for '" + vn.getX500Name() + "' not found.");
|
||||||
|
} else if (matches.size() >1 ) {
|
||||||
|
throw new CsdeException(("Registration failed because more than virtual node for '" + vn.getX500Name() + "'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
String shortHash = matches.get(0).getHoldingIdentity().getShortHash();
|
||||||
|
|
||||||
|
if (!checkVNodeIsRegistered(shortHash)) {
|
||||||
|
registerVnode(vn, shortHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a virtual node.
|
||||||
|
* @param vnode represents the vnode to be registered.
|
||||||
|
* @param shortHash the shortHash of the virtual node to register.
|
||||||
|
*/
|
||||||
|
private void registerVnode(VNode vnode, String shortHash) throws CsdeException {
|
||||||
|
|
||||||
|
pc.out.println("Registering vNode: " + vnode.getX500Name() + " with shortHash: " + shortHash);
|
||||||
|
|
||||||
|
// Configure the registration body (notary vs non notary)
|
||||||
|
String registrationBody;
|
||||||
|
if (vnode.getServiceX500Name() == null) {
|
||||||
|
registrationBody =
|
||||||
|
"{ \"action\" : \"requestJoin\"," +
|
||||||
|
" \"context\" : {" +
|
||||||
|
" \"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\" } }";
|
||||||
|
} else {
|
||||||
|
registrationBody =
|
||||||
|
"{ \"action\" : \"requestJoin\"," +
|
||||||
|
" \"context\" : {" +
|
||||||
|
" \"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\", " +
|
||||||
|
" \"corda.roles.0\" : \"notary\", " +
|
||||||
|
" \"corda.notary.service.name\" : \"" + vnode.getServiceX500Name() + "\", " +
|
||||||
|
" \"corda.notary.service.plugin\" : \"net.corda.notary.NonValidatingNotary\" } }";
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse<JsonNode> response = Unirest.post(pc.baseURL + "/api/v1/membership/" + shortHash)
|
||||||
|
.body(registrationBody)
|
||||||
|
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
if (response.getStatus() == HTTP_OK) {
|
||||||
|
pc.out.println("Membership requested for node " + shortHash);
|
||||||
|
} else {
|
||||||
|
throw new CsdeException("Failed to register virtual node " + shortHash + ", response status: " + response.getStatus() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until Vnode registered
|
||||||
|
pollForRegistration(shortHash, 30000, 1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a virtual node with given shortHash has been registered
|
||||||
|
* @param shortHash shortHash of the node which is being checked.
|
||||||
|
* @return returns true if the vnode is registered
|
||||||
|
*/
|
||||||
|
private boolean checkVNodeIsRegistered(String shortHash) throws CsdeException {
|
||||||
|
|
||||||
|
// Queries registration status for vnode.
|
||||||
|
HttpResponse<JsonNode> response = Unirest.get(pc.baseURL + "/api/v1/membership/" + shortHash)
|
||||||
|
.basicAuth(pc.rpcUser, pc.rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
if (response.getStatus() != HTTP_OK)
|
||||||
|
throw new CsdeException("Failed to check registration status for virtual node '" + shortHash +
|
||||||
|
"' response status: " + response.getStatus());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If the response body is not empty check all previous requests for an "APPROVED"
|
||||||
|
if (!response.getBody().getArray().isEmpty()) {
|
||||||
|
List<RegistrationRequestProgressDTO> requests = mapper.readValue(
|
||||||
|
response.getBody().toString(), new TypeReference<>() {
|
||||||
|
});
|
||||||
|
for (RegistrationRequestProgressDTO request : requests) {
|
||||||
|
if (Objects.equals(request.getRegistrationStatus(), "APPROVED")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Returns false if array was empty or "APPROVED" wasn't found
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CsdeException("Failed to check registration status for " + shortHash +
|
||||||
|
" with exception " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls for registration of a vnode
|
||||||
|
* @param shortHash short hash of the node which is being poled for
|
||||||
|
* @param duration the number of milliseconds before the pollign times out
|
||||||
|
* @param cooldown the number of milliseconds in between poll attempts.
|
||||||
|
*/
|
||||||
|
private void pollForRegistration(String shortHash, int duration, int cooldown) throws CsdeException {
|
||||||
|
|
||||||
|
int timer = 0;
|
||||||
|
while (timer < duration ) {
|
||||||
|
if (checkVNodeIsRegistered(shortHash)){
|
||||||
|
pc.out.println("Vnode " + shortHash +" registered.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
utils.rpcWait(cooldown);
|
||||||
|
timer += cooldown;
|
||||||
|
}
|
||||||
|
throw new CsdeException("Vnode " + shortHash + " failed to register in " + duration + " milliseconds");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
public class CPIFileStatusDTO {
|
||||||
|
private String status;
|
||||||
|
private String cpiFileChecksum;
|
||||||
|
|
||||||
|
public CPIFileStatusDTO() {}
|
||||||
|
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
|
||||||
|
public String getCpiFileChecksum() { return cpiFileChecksum; }
|
||||||
|
|
||||||
|
public void setCpiFileChecksum(String cpiFileChecksum) { this.cpiFileChecksum = cpiFileChecksum; }
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
public class CpiIdentifierDTO {
|
||||||
|
|
||||||
|
// Note, these DTOs don't cover all returned values, just the ones required for CSDE
|
||||||
|
private String cpiName;
|
||||||
|
private String cpiVersion;
|
||||||
|
private String signerSummaryHash;
|
||||||
|
|
||||||
|
public CpiIdentifierDTO() { }
|
||||||
|
|
||||||
|
public String getCpiName() { return cpiName; }
|
||||||
|
|
||||||
|
public void setCpiName(String cpiName) { this.cpiName = cpiName; }
|
||||||
|
|
||||||
|
public String getCpiVersion() { return cpiVersion; }
|
||||||
|
|
||||||
|
public void setCpiVersion(String cpiVersion) { this.cpiVersion = cpiVersion; }
|
||||||
|
|
||||||
|
public String getSignerSummaryHash() { return signerSummaryHash; }
|
||||||
|
|
||||||
|
public void setSignerSummaryHash(String signerSummaryHash) { this.signerSummaryHash = signerSummaryHash; }
|
||||||
|
}
|
17
buildSrc/src/main/java/com/r3/csde/dtos/CpiMetadataDTO.java
Normal file
17
buildSrc/src/main/java/com/r3/csde/dtos/CpiMetadataDTO.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
public class CpiMetadataDTO {
|
||||||
|
// Note, these DTOs don't cover all returned values, just the ones required for CSDE.
|
||||||
|
private String cpiFileChecksum;
|
||||||
|
private CpiIdentifierDTO id;
|
||||||
|
|
||||||
|
public CpiMetadataDTO() {}
|
||||||
|
|
||||||
|
public String getCpiFileChecksum() { return cpiFileChecksum; }
|
||||||
|
|
||||||
|
public void setCpiFileChecksum(String cpiFileChecksum) { this.cpiFileChecksum = cpiFileChecksum; }
|
||||||
|
|
||||||
|
public CpiIdentifierDTO getId() { return id; }
|
||||||
|
|
||||||
|
public void setId(CpiIdentifierDTO id) { this.id = id; }
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetCPIsResponseDTO {
|
||||||
|
|
||||||
|
private List<CpiMetadataDTO> cpis;
|
||||||
|
|
||||||
|
public GetCPIsResponseDTO() {}
|
||||||
|
|
||||||
|
public List<CpiMetadataDTO> getCpis() { return cpis; }
|
||||||
|
|
||||||
|
public void setCpis(List<CpiMetadataDTO> cpis) { this.cpis = cpis; }
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
public class HoldingIdentityDTO {
|
||||||
|
// Note, these DTOs don't cover all returned values, just the ones required for CSDE.
|
||||||
|
private String fullHash;
|
||||||
|
private String groupId;
|
||||||
|
private String shortHash;
|
||||||
|
private String x500Name;
|
||||||
|
|
||||||
|
public HoldingIdentityDTO() {}
|
||||||
|
|
||||||
|
public String getFullHash() { return fullHash; }
|
||||||
|
|
||||||
|
public void setFullHash(String fullHash) { this.fullHash = fullHash; }
|
||||||
|
|
||||||
|
public String getGroupId() { return groupId; }
|
||||||
|
|
||||||
|
public void setGroupID(String groupID) { this.groupId = groupId; }
|
||||||
|
|
||||||
|
public String getShortHash() { return shortHash; }
|
||||||
|
|
||||||
|
public void setShortHash(String shortHash) { this.shortHash = shortHash; }
|
||||||
|
|
||||||
|
public String getX500Name() { return x500Name; }
|
||||||
|
|
||||||
|
public void setX500Name(String x500Name) { this.x500Name = x500Name; }
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
public class RegistrationRequestProgressDTO {
|
||||||
|
// Note, these DTOs don't cover all returned values, just the ones required for CSDE.
|
||||||
|
private String registrationStatus;
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
public RegistrationRequestProgressDTO() {}
|
||||||
|
|
||||||
|
public String getRegistrationStatus() { return registrationStatus; }
|
||||||
|
|
||||||
|
public void setRegistrationStatus(String registrationStatus) { this.registrationStatus = registrationStatus; }
|
||||||
|
|
||||||
|
public String getReason() { return reason; }
|
||||||
|
|
||||||
|
public void setReason(String reason) { this.reason = reason; }
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
public class VirtualNodeInfoDTO {
|
||||||
|
// Note, these DTOs don't cover all returned values, just the ones required for CSDE.
|
||||||
|
private HoldingIdentityDTO holdingIdentity;
|
||||||
|
private CpiIdentifierDTO cpiIdentifier;
|
||||||
|
|
||||||
|
public VirtualNodeInfoDTO() {}
|
||||||
|
|
||||||
|
public HoldingIdentityDTO getHoldingIdentity() { return holdingIdentity; }
|
||||||
|
|
||||||
|
public void setHoldingIdentity(HoldingIdentityDTO holdingIdentity) { this.holdingIdentity = holdingIdentity; }
|
||||||
|
|
||||||
|
public CpiIdentifierDTO getCpiIdentifier() { return cpiIdentifier; }
|
||||||
|
|
||||||
|
public void setCpiIdentifier(CpiIdentifierDTO cpiIdentifier) { this.cpiIdentifier = cpiIdentifier; }
|
||||||
|
}
|
14
buildSrc/src/main/java/com/r3/csde/dtos/VirtualNodesDTO.java
Normal file
14
buildSrc/src/main/java/com/r3/csde/dtos/VirtualNodesDTO.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.r3.csde.dtos;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VirtualNodesDTO {
|
||||||
|
|
||||||
|
private List<VirtualNodeInfoDTO> virtualNodes;
|
||||||
|
|
||||||
|
public VirtualNodesDTO() {}
|
||||||
|
|
||||||
|
public List<VirtualNodeInfoDTO> getVirtualNodes() { return virtualNodes; }
|
||||||
|
|
||||||
|
public void setVirtualNodes(List<VirtualNodeInfoDTO> virtualNodes) { this.virtualNodes = virtualNodes; }
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"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"]
|
|
||||||
}]
|
|
||||||
}
|
|
51
config/log4j2.xml
Normal file
51
config/log4j2.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="INFO">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} %X - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
|
||||||
|
<RollingFile name="App"
|
||||||
|
fileName="logs/corda.log"
|
||||||
|
filePattern="logs/corda.%d{MM-dd-yyyy}.%i.log"
|
||||||
|
ignoreExceptions="false">
|
||||||
|
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} %X - %msg%n"/>
|
||||||
|
<Policies>
|
||||||
|
<OnStartupTriggeringPolicy />
|
||||||
|
<TimeBasedTriggeringPolicy />
|
||||||
|
<SizeBasedTriggeringPolicy size="10 MB" />
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy>
|
||||||
|
<Delete basePath="logs/">
|
||||||
|
<IfFileName glob="logs/corda.*.log">
|
||||||
|
<IfAny>
|
||||||
|
<IfAccumulatedFileSize exceeds="500 MB" />
|
||||||
|
<IfAccumulatedFileCount exceeds="10" />
|
||||||
|
</IfAny>
|
||||||
|
</IfFileName>
|
||||||
|
<IfLastModified age="7d" />
|
||||||
|
</Delete>
|
||||||
|
</DefaultRolloverStrategy>
|
||||||
|
</RollingFile>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<logger name="Console">
|
||||||
|
<AppenderRef ref="Console" level="info"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- log warn only for these 3rd party libs -->
|
||||||
|
<Logger name="com.zaxxer.hikari" level="warn" />
|
||||||
|
<Logger name="io.javalin.Javalin" level="warn" />
|
||||||
|
<Logger name="org.apache.aries.spifly" level="warn" />
|
||||||
|
<Logger name="org.apache.kafka" level="warn" />
|
||||||
|
<Logger name="org.eclipse.jetty" level="warn" />
|
||||||
|
<Logger name="org.hibernate" level="warn" />
|
||||||
|
|
||||||
|
<!-- default to warn only for OSGi logging -->
|
||||||
|
<Logger name="net.corda.osgi.framework.OSGiFrameworkWrap" level="warn" />
|
||||||
|
|
||||||
|
<root level="debug">
|
||||||
|
<AppenderRef ref="App" level="info"/>
|
||||||
|
</root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
23
config/static-network-config.json
Normal file
23
config/static-network-config.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"x500Name" : "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"cpi" : "cpi name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x500Name" : "CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"cpi" : "cpi name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x500Name" : "CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"cpi" : "cpi name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x500Name" : "CN=Dave, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"cpi" : "cpi name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x500Name" : "CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"cpi" : "CSDE Notary Server CPI",
|
||||||
|
"serviceX500Name": "CN=NotaryService, OU=Test Dept, O=R3, L=London, C=GB"
|
||||||
|
}
|
||||||
|
]
|
@ -13,12 +13,8 @@ plugins {
|
|||||||
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
|
// 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 built on.
|
||||||
dependencies {
|
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.
|
cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
|
||||||
// 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
|
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
|
||||||
// Corda API specified.
|
// Corda API specified.
|
||||||
@ -45,8 +41,9 @@ dependencies {
|
|||||||
|
|
||||||
// This are shared so should be here.
|
// This are shared so should be here.
|
||||||
// Dependencies Required By Test Tooling
|
// Dependencies Required By Test Tooling
|
||||||
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
// Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
|
||||||
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
||||||
|
// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
||||||
|
|
||||||
// 3rd party libraries
|
// 3rd party libraries
|
||||||
// Required
|
// Required
|
||||||
|
@ -4,18 +4,10 @@ import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
|||||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||||
import net.corda.v5.ledger.utxo.Command;
|
import net.corda.v5.ledger.utxo.Command;
|
||||||
import net.corda.v5.ledger.utxo.Contract;
|
import net.corda.v5.ledger.utxo.Contract;
|
||||||
import net.corda.v5.ledger.utxo.ContractState;
|
|
||||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
|
||||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static java.util.Objects.*;
|
|
||||||
|
|
||||||
public class ChatContract implements Contract {
|
public class ChatContract implements Contract {
|
||||||
|
|
||||||
|
@ -2,11 +2,9 @@ package com.r3.developers.csdetemplate.utxoexample.states;
|
|||||||
|
|
||||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||||
import net.corda.v5.base.annotations.CordaSerializable;
|
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import net.corda.v5.ledger.utxo.BelongsToContract;
|
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||||
import net.corda.v5.ledger.utxo.ContractState;
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -2,15 +2,15 @@ kotlin.code.style=official
|
|||||||
|
|
||||||
# Specify the version of the Corda-API to use.
|
# Specify the version of the Corda-API to use.
|
||||||
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
|
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
|
||||||
cordaApiVersion=5.0.0.524-Fox1.1
|
cordaApiVersion=5.0.0.665-Gecko-RC02
|
||||||
|
|
||||||
# Settings For Development Utilities
|
# Settings For Development Utilities
|
||||||
combinedWorkerVersion=5.0.0.0-Fox1.1
|
combinedWorkerVersion=5.0.0.0-Gecko-RC02
|
||||||
simulatorVersion=5.0.0.0-Fox1.1
|
simulatorVersion=5.0.0.0-Gecko-RC02
|
||||||
|
|
||||||
# Specify the version of the notary plugins to use.
|
# 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.
|
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
|
||||||
cordaNotaryPluginsVersion=5.0.0.0-Fox1.1
|
cordaNotaryPluginsVersion=5.0.0.0-Gecko-RC02
|
||||||
|
|
||||||
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
|
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
|
||||||
cordaPluginsVersion=7.0.1
|
cordaPluginsVersion=7.0.1
|
||||||
@ -39,3 +39,6 @@ cordaRpcPasswd=admin
|
|||||||
devEnvWorkspace=workspace
|
devEnvWorkspace=workspace
|
||||||
dbContainerName=CSDEpostgresql
|
dbContainerName=CSDEpostgresql
|
||||||
|
|
||||||
|
# R3 internal repository
|
||||||
|
#S3 version for HC and RC versions published by CD/CD team
|
||||||
|
artifactoryContextUrl=https://staging.download.corda.net/maven/20ede3c6-29c0-11ed-966d-b7c36748b9f6-Gecko-RC02/
|
@ -3,6 +3,9 @@ pluginManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url = "$artifactoryContextUrl/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
|
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
|
||||||
|
@ -15,12 +15,7 @@ dependencies {
|
|||||||
// From other subprojects:
|
// From other subprojects:
|
||||||
cordapp project(':contracts')
|
cordapp project(':contracts')
|
||||||
|
|
||||||
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
|
cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
|
||||||
// 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
|
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
|
||||||
// Corda API specified.
|
// Corda API specified.
|
||||||
@ -47,8 +42,9 @@ dependencies {
|
|||||||
|
|
||||||
// This are shared so should be here.
|
// This are shared so should be here.
|
||||||
// Dependencies Required By Test Tooling
|
// Dependencies Required By Test Tooling
|
||||||
testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
// Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
|
||||||
testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
|
||||||
|
// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
|
||||||
|
|
||||||
// 3rd party libraries
|
// 3rd party libraries
|
||||||
// Required
|
// Required
|
||||||
|
@ -7,7 +7,6 @@ import net.corda.v5.application.messaging.FlowMessaging;
|
|||||||
import net.corda.v5.application.messaging.FlowSession;
|
import net.corda.v5.application.messaging.FlowSession;
|
||||||
import net.corda.v5.base.annotations.Suspendable;
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -16,7 +15,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
// to link the two sides of the flow together they need to have the same protocol.
|
// to link the two sides of the flow together they need to have the same protocol.
|
||||||
@InitiatingFlow(protocol = "my-first-flow")
|
@InitiatingFlow(protocol = "my-first-flow")
|
||||||
// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
|
// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
|
||||||
public class MyFirstFlow implements RPCStartableFlow {
|
public class MyFirstFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
// Log messages from the flows for debugging.
|
// Log messages from the flows for debugging.
|
||||||
private final static Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
private final static Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||||
@ -43,10 +42,10 @@ public class MyFirstFlow implements RPCStartableFlow {
|
|||||||
// When a flow is invoked its call() method is called.
|
// When a flow is invoked its call() method is called.
|
||||||
// Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
// Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||||
// for a response from the other flows and services.
|
// for a response from the other flows and services.
|
||||||
@NotNull
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public String call(RPCRequestData requestBody) {
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
|
||||||
// Follow what happens in the console or logs.
|
// Follow what happens in the console or logs.
|
||||||
log.info("MFF: MyFirstFlow.call() called");
|
log.info("MFF: MyFirstFlow.call() called");
|
||||||
|
@ -28,7 +28,7 @@ import java.util.UUID;
|
|||||||
import static java.util.Objects.*;
|
import static java.util.Objects.*;
|
||||||
|
|
||||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||||
public class CreateNewChatFlow implements RPCStartableFlow {
|
public class CreateNewChatFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
|
private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class CreateNewChatFlow implements RPCStartableFlow {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public String call( RPCRequestData requestBody) {
|
public String call( ClientRequestBody requestBody) {
|
||||||
|
|
||||||
log.info("CreateNewChatFlow.call() called");
|
log.info("CreateNewChatFlow.call() called");
|
||||||
|
|
||||||
@ -101,10 +101,10 @@ public class CreateNewChatFlow implements RPCStartableFlow {
|
|||||||
.addCommand(new ChatContract.Create())
|
.addCommand(new ChatContract.Create())
|
||||||
.addSignatories(chatState.getParticipants());
|
.addSignatories(chatState.getParticipants());
|
||||||
|
|
||||||
// Convert the transaction builder to a UTXOSignedTransaction and sign with this Vnode's first Ledger key.
|
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
|
||||||
// Note, toSignedTransaction() is currently a placeholder method, hence being marked as deprecated.
|
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
|
||||||
@SuppressWarnings("DEPRECATION")
|
// the current node.
|
||||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
|
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
|
||||||
|
|
||||||
// Call FinalizeChatSubFlow which will finalise the transaction.
|
// Call FinalizeChatSubFlow which will finalise the transaction.
|
||||||
// If successful the flow will return a String of the created transaction id,
|
// If successful the flow will return a String of the created transaction id,
|
||||||
@ -124,7 +124,7 @@ RequestBody for triggering the flow via http-rpc:
|
|||||||
{
|
{
|
||||||
"clientRequestId": "create-1",
|
"clientRequestId": "create-1",
|
||||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.CreateNewChatFlow",
|
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.CreateNewChatFlow",
|
||||||
"requestData": {
|
"requestBody": {
|
||||||
"chatName":"Chat with Bob",
|
"chatName":"Chat with Bob",
|
||||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
"message": "Hello Bob"
|
"message": "Hello Bob"
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||||
|
|
||||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||||
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
import net.corda.v5.application.flows.RPCRequestData;
|
|
||||||
import net.corda.v5.application.flows.RPCStartableFlow;
|
|
||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||||
import net.corda.v5.base.annotations.Suspendable;
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||||
@ -19,7 +19,7 @@ import static java.util.Objects.*;
|
|||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||||
public class GetChatFlow implements RPCStartableFlow {
|
public class GetChatFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
|
private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ public class GetChatFlow implements RPCStartableFlow {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Suspendable
|
@Suspendable
|
||||||
public String call(RPCRequestData requestBody) {
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
|
||||||
// Obtain the deserialized input arguments to the flow from the requestBody.
|
// Obtain the deserialized input arguments to the flow from the requestBody.
|
||||||
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
|
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
|
||||||
@ -70,7 +70,7 @@ public class GetChatFlow implements RPCStartableFlow {
|
|||||||
while (moreBackchain) {
|
while (moreBackchain) {
|
||||||
|
|
||||||
// Obtain the transaction id from the current StateAndRef and fetch the transaction from the vault.
|
// Obtain the transaction id from the current StateAndRef and fetch the transaction from the vault.
|
||||||
SecureHash transactionId = currentStateAndRef.getRef().getTransactionHash();
|
SecureHash transactionId = currentStateAndRef.getRef().getTransactionId();
|
||||||
UtxoLedgerTransaction transaction = requireNonNull(
|
UtxoLedgerTransaction transaction = requireNonNull(
|
||||||
ledgerService.findLedgerTransaction(transactionId),
|
ledgerService.findLedgerTransaction(transactionId),
|
||||||
"Transaction " + transactionId + " not found."
|
"Transaction " + transactionId + " not found."
|
||||||
@ -110,7 +110,7 @@ RequestBody for triggering the flow via http-rpc:
|
|||||||
{
|
{
|
||||||
"clientRequestId": "get-1",
|
"clientRequestId": "get-1",
|
||||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow",
|
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow",
|
||||||
"requestData": {
|
"requestBody": {
|
||||||
"id":"** fill in id **",
|
"id":"** fill in id **",
|
||||||
"numberOfRecords":"4"
|
"numberOfRecords":"4"
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||||
|
|
||||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||||
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
import net.corda.v5.application.flows.RPCRequestData;
|
|
||||||
import net.corda.v5.application.flows.RPCStartableFlow;
|
|
||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||||
import net.corda.v5.base.annotations.Suspendable;
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||||
@ -15,7 +15,7 @@ import java.util.*;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||||
public class ListChatsFlow implements RPCStartableFlow{
|
public class ListChatsFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
|
private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ public class ListChatsFlow implements RPCStartableFlow{
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public String call(RPCRequestData requestBody) {
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
|
||||||
log.info("ListChatsFlow.call() called");
|
log.info("ListChatsFlow.call() called");
|
||||||
|
|
||||||
@ -53,6 +53,6 @@ RequestBody for triggering the flow via http-rpc:
|
|||||||
{
|
{
|
||||||
"clientRequestId": "list-1",
|
"clientRequestId": "list-1",
|
||||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow",
|
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow",
|
||||||
"requestData": {}
|
"requestBody": {}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
@ -2,10 +2,10 @@ package com.r3.developers.csdetemplate.utxoexample.workflows;
|
|||||||
|
|
||||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||||
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
import net.corda.v5.application.flows.FlowEngine;
|
import net.corda.v5.application.flows.FlowEngine;
|
||||||
import net.corda.v5.application.flows.RPCRequestData;
|
|
||||||
import net.corda.v5.application.flows.RPCStartableFlow;
|
|
||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||||
import net.corda.v5.application.membership.MemberLookup;
|
import net.corda.v5.application.membership.MemberLookup;
|
||||||
import net.corda.v5.base.annotations.Suspendable;
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
@ -26,7 +26,7 @@ import static java.util.Objects.*;
|
|||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||||
public class UpdateChatFlow implements RPCStartableFlow {
|
public class UpdateChatFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
|
private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ public class UpdateChatFlow implements RPCStartableFlow {
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Override
|
@Override
|
||||||
public String call(RPCRequestData requestBody) {
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
|
||||||
log.info("UpdateNewChatFlow.call() called");
|
log.info("UpdateNewChatFlow.call() called");
|
||||||
|
|
||||||
@ -87,10 +87,10 @@ public class UpdateChatFlow implements RPCStartableFlow {
|
|||||||
.addCommand(new ChatContract.Update())
|
.addCommand(new ChatContract.Update())
|
||||||
.addSignatories(newChatState.getParticipants());
|
.addSignatories(newChatState.getParticipants());
|
||||||
|
|
||||||
// Convert the transaction builder to a UtxoSignedTransaction and sign with this Vnode's first Ledger key.
|
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
|
||||||
// Note, toSignedTransaction() is currently a placeholder method, hence being marked as deprecated.
|
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
|
||||||
@SuppressWarnings("DEPRECATION")
|
// the current node.
|
||||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
|
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
|
||||||
|
|
||||||
// Call FinalizeChatSubFlow which will finalise the transaction.
|
// Call FinalizeChatSubFlow which will finalise the transaction.
|
||||||
// If successful the flow will return a String of the created transaction id,
|
// If successful the flow will return a String of the created transaction id,
|
||||||
@ -112,7 +112,7 @@ RequestBody for triggering the flow via http-rpc:
|
|||||||
{
|
{
|
||||||
"clientRequestId": "update-1",
|
"clientRequestId": "update-1",
|
||||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow",
|
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow",
|
||||||
"requestData": {
|
"requestBody": {
|
||||||
"id":" ** fill in id **",
|
"id":" ** fill in id **",
|
||||||
"message": "How are you today?"
|
"message": "How are you today?"
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
//package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||||
|
//
|
||||||
import net.corda.simulator.RequestData;
|
//import net.corda.simulator.RequestData;
|
||||||
import net.corda.simulator.SimulatedVirtualNode;
|
//import net.corda.simulator.SimulatedVirtualNode;
|
||||||
import net.corda.simulator.Simulator;
|
//import net.corda.simulator.Simulator;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
//import net.corda.v5.base.types.MemberX500Name;
|
||||||
import org.junit.jupiter.api.Test;
|
//import org.junit.jupiter.api.Test;
|
||||||
|
//
|
||||||
class MyFirstFlowTest {
|
//class MyFirstFlowTest {
|
||||||
// Names picked to match the corda network in config/dev-net.json
|
// // Names picked to match the corda network in config/dev-net.json
|
||||||
private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
|
// private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
|
||||||
private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
|
// private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@SuppressWarnings("unchecked")
|
// @SuppressWarnings("unchecked")
|
||||||
public void test_that_MyFirstFLow_returns_correct_message() {
|
// public void test_that_MyFirstFLow_returns_correct_message() {
|
||||||
// Instantiate an instance of the simulator.
|
// // Instantiate an instance of the simulator.
|
||||||
Simulator simulator = new Simulator();
|
// Simulator simulator = new Simulator();
|
||||||
|
//
|
||||||
// Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
|
// // 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.
|
// // Don't assign Bob's virtual node to a value. You don't need it for this particular test.
|
||||||
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
|
// SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
|
||||||
simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
|
// simulator.createVirtualNode(bobX500, 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);
|
// MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
|
||||||
|
//
|
||||||
// Create a requestData object.
|
// // Create a requestData object.
|
||||||
RequestData requestData = RequestData.Companion.create(
|
// RequestData requestData = RequestData.Companion.create(
|
||||||
"request no 1", // A unique reference for the instance of the flow request.
|
// "request no 1", // A unique reference for the instance of the flow request.
|
||||||
MyFirstFlow.class, // The name of the flow class which is to be started.
|
// MyFirstFlow.class, // The name of the flow class which is to be started.
|
||||||
myFirstFlowStartArgs // The object which contains the start arguments of the flow.
|
// myFirstFlowStartArgs // The object which contains the start arguments of the flow.
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
// Call the flow on Alice's virtual node and capture the response.
|
// // Call the flow on Alice's virtual node and capture the response.
|
||||||
String flowResponse = aliceVN.callFlow(requestData);
|
// String flowResponse = aliceVN.callFlow(requestData);
|
||||||
|
//
|
||||||
// Check that the flow has returned the expected string.
|
// // Check that the flow has returned the expected string.
|
||||||
assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
|
// assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
Loading…
Reference in New Issue
Block a user