ported rc02 updates from kotlin

This commit is contained in:
mattbradburyr3 2023-03-16 18:29:57 +00:00
parent d64a21107d
commit 7d65a63f8d
38 changed files with 722 additions and 483 deletions

View File

@ -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.
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:
@ -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.
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)

View File

@ -26,6 +26,9 @@ allprojects {
repositories {
// All dependencies are held in Maven Central
mavenCentral()
maven {
url = "$artifactoryContextUrl/"
}
}
tasks.withType(Test).configureEach {

View File

@ -4,8 +4,16 @@ plugins {
id 'java'
}
def constants = new Properties()
file("$rootDir/../gradle.properties").withInputStream { InputStream input -> constants.load(input) }
def artifactoryContextUrl = constants.getProperty('artifactoryContextUrl')
repositories {
mavenCentral()
mavenLocal()
maven {
url = "$artifactoryContextUrl/"
}
}
dependencies {

View File

@ -1,4 +1,4 @@
jacksonVersion = 2.13.4
unirestVersion=3.13.10
cordaApiVersion=5.0.0.524-Fox1.1
cordaApiVersion=5.0.0.665-Gecko-RC02

View File

@ -19,7 +19,8 @@ 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
import com.r3.csde.VNodesHelper
import com.r3.csde.NetworkConfig
plugins {
id 'java-library'
@ -113,6 +114,8 @@ def projectContext = new ProjectContext(project,
cordaNotaryPluginsVersion
)
def networkConfig = new NetworkConfig("config/static-network-config.json")
def utils = new ProjectUtils()
// Initiate workspace folder
@ -183,7 +186,7 @@ tasks.register('listCPIs') {
// Build CPI tasks
def buildCPIsHelper = new BuildCPIsHelper(projectContext)
def buildCPIsHelper = new BuildCPIsHelper(projectContext, networkConfig)
tasks.register("1-createGroupPolicy") {
group = cordappGroup
@ -224,7 +227,7 @@ tasks.register('3-buildCPIs') {
def deployCPIsHelper = new DeployCPIsHelper(projectContext)
tasks.register("4-deployCPIs") {
tasks.register('4-deployCPIs') {
group = cordappGroup
dependsOn('3-buildCPIs')
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
dependsOn('4-deployCPIs')
doLast {
createAndRegisterVNodesHelper.createAndRegVNodes()
vNodesHelper.vNodesSetup()
}
}
// Empty task, just acts as the Task user entry point task.
tasks.register('quickDeployCordapp') {
group = cordappGroup
dependsOn("5-createAndRegVNodes")
}

View File

@ -2,26 +2,32 @@ package com.r3.csde;
import java.io.*;
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 ProjectContext pc;
public ProjectUtils utils ;
public BuildCPIsHelper(ProjectContext _pc) {
public ProjectUtils utils;
public NetworkConfig config;
public BuildCPIsHelper(ProjectContext _pc, NetworkConfig _config) {
pc = _pc;
utils = new ProjectUtils(pc);
config = _config;
}
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()));
File devnetFile = new File(pc.project.getRootDir() + "/" + config.getConfigFilePath());
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
pc.out.println("createGroupPolicy: Creating a GroupPolicy");
LinkedList<String> configX500Ids = utils.getConfigX500Ids(pc.X500ConfigFile);
List<String> configX500Ids = config.getX500Names();
LinkedList<String> commandList = new LinkedList<>();
commandList.add(String.format("%s/java", pc.javaBinDir));
@ -192,6 +198,8 @@ public class BuildCPIsHelper {
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();

View File

@ -1,5 +1,7 @@
package com.r3.csde;
import kong.unirest.Unirest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -9,7 +11,7 @@ import java.util.Scanner;
/**
* 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 {
ProjectContext pc;
@ -18,15 +20,16 @@ public class CordaLifeCycleHelper {
public CordaLifeCycleHelper(ProjectContext _pc) {
pc = _pc;
utils = new ProjectUtils(pc);
Unirest.config().verifySsl(false);
}
public void startCorda() throws IOException {
PrintStream pidStore = new PrintStream(new FileOutputStream(pc.cordaPidCache));
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(
"docker",
"run", "-d", "--rm",
@ -36,37 +39,33 @@ public class CordaLifeCycleHelper {
"-e", "POSTGRES_USER=postgres",
"-e", "POSTGRES_PASSWORD=password",
"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);
ProcessBuilder procBuild = new ProcessBuilder(pc.javaBinDir + "/java",
"-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",
"-jar",
combinedWorkerJar.toString(),
"--instanceId=0",
"--instance-id=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());
proc.getInputStream().transferTo(pc.out);
// 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
// todo: we should poll for readiness before completing the startCorda task, see https://r3-cev.atlassian.net/browse/CORE-11625
}

View File

@ -6,6 +6,7 @@ import kong.unirest.json.JSONArray;
import kong.unirest.json.JSONObject;
import kong.unirest.HttpResponse;
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
public class CordaStatusQueries {
ProjectContext pc;

View File

@ -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;
}
}

View File

@ -5,15 +5,14 @@ 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;
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
public class DeployCPIsHelper {
public DeployCPIsHelper() {

View 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());
}
}

View File

@ -1,7 +1,6 @@
package com.r3.csde;
import org.gradle.api.Project;
import java.io.PrintStream;
import java.util.Map;
@ -10,12 +9,13 @@ public class ProjectContext {
String baseURL = "https://localhost:8888";
String rpcUser = "admin";
String rpcPasswd = "admin";
String workspaceDir = "workspace";
String workspaceDir = "workspace";
int retryWaitMs = 1000;
PrintStream out = System.out;
String CPIUploadStatusBaseName = "CPIFileStatus.json";
String NotaryCPIUploadBaseName = "CPIFileStatus-NotaryServer.json";
String CPIUploadStatusFName;
String X500ConfigFile = "config/dev-net.json";
String NotaryCPIUploadStatusFName;
String javaBinDir;
String cordaPidCache = "CordaPIDCache.dat";
String dbContainerName;
@ -67,6 +67,7 @@ public class ProjectContext {
dbContainerName = inDbContainerName;
JDBCDir = inJDBCDir;
CPIUploadStatusFName = workspaceDir + "/" + CPIUploadStatusBaseName;
NotaryCPIUploadStatusFName = workspaceDir + "/" + NotaryCPIUploadBaseName;
signingCertAlias = inSigningCertAlias;
signingCertFName = inSigningCertFName;
keystoreAlias = inKeystoreAlias;
@ -79,5 +80,5 @@ public class ProjectContext {
cordaNotaryServiceDir = inCordaNotaryServiceDir;
workflowBuildDir = inWorkflowBuildDir;
cordaNotaryPluginsVersion = inCordaNotaryPluginsVersion;
}
}
}

View File

@ -1,25 +1,19 @@
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;
// todo: This class needs refactoring, see https://r3-cev.atlassian.net/browse/CORE-11624
public class ProjectUtils {
ProjectContext pc;
ProjectUtils(ProjectContext _pc) {
pc = _pc;
}
void rpcWait(int millis) {
try {
sleep(millis);
@ -29,36 +23,7 @@ public class ProjectUtils {
}
}
void rpcWait() {
rpcWait( pc.retryWaitMs);
}
public LinkedList<String> getConfigX500Ids(String configFile) throws IOException {
LinkedList<String> x500Ids = new LinkedList<>();
// com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
ObjectMapper mapper = new ObjectMapper();
FileInputStream in = new FileInputStream(configFile);
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
x500Ids.add(jsonNodeToString(identity));
}
return x500Ids;
}
public String jsonNodeToString(com.fasterxml.jackson.databind.JsonNode jsonNode) {
String jsonString = jsonNode.toString();
return jsonString.substring(1, jsonString.length()-1);
}
public void downloadFile(String url, String targetPath) {
Unirest.get(url)
.asFile(targetPath)
.getBody();
}
public void reportError(@NotNull HttpResponse<JsonNode> response) throws CsdeException {
public void reportError(HttpResponse<JsonNode> response) throws CsdeException {
pc.out.println("*** *** ***");
pc.out.println("Unexpected response from Corda");

View 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; }
}

View 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");
}
}

View File

@ -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; }
}

View File

@ -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; }
}

View 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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View 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; }
}

View File

@ -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
View 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>

View 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"
}
]

View File

@ -13,12 +13,8 @@ plugins {
// 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'
cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
// Corda API specified.
@ -45,8 +41,9 @@ dependencies {
// 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"
// Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
// 3rd party libraries
// Required

View File

@ -4,18 +4,10 @@ import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.ledger.utxo.Command;
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 org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
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 {

View File

@ -2,11 +2,9 @@ package com.r3.developers.csdetemplate.utxoexample.states;
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
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.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
import org.jetbrains.annotations.NotNull;
import java.security.PublicKey;
import java.util.*;

View File

@ -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.524-Fox1.1
cordaApiVersion=5.0.0.665-Gecko-RC02
# Settings For Development Utilities
combinedWorkerVersion=5.0.0.0-Fox1.1
simulatorVersion=5.0.0.0-Fox1.1
combinedWorkerVersion=5.0.0.0-Gecko-RC02
simulatorVersion=5.0.0.0-Gecko-RC02
# 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.1
cordaNotaryPluginsVersion=5.0.0.0-Gecko-RC02
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
cordaPluginsVersion=7.0.1
@ -39,3 +39,6 @@ cordaRpcPasswd=admin
devEnvWorkspace=workspace
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/

View File

@ -3,6 +3,9 @@ pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
maven {
url = "$artifactoryContextUrl/"
}
}
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,

View File

@ -15,12 +15,7 @@ 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'
cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
// Corda API specified.
@ -47,8 +42,9 @@ dependencies {
// 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"
// Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
// 3rd party libraries
// Required

View File

@ -7,7 +7,6 @@ import net.corda.v5.application.messaging.FlowMessaging;
import net.corda.v5.application.messaging.FlowSession;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.types.MemberX500Name;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
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.
@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 {
public class MyFirstFlow implements ClientStartableFlow {
// Log messages from the flows for debugging.
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.
// 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) {
public String call(ClientRequestBody requestBody) {
// Follow what happens in the console or logs.
log.info("MFF: MyFirstFlow.call() called");

View File

@ -28,7 +28,7 @@ import java.util.UUID;
import static java.util.Objects.*;
// 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);
@ -52,7 +52,7 @@ public class CreateNewChatFlow implements RPCStartableFlow {
@Suspendable
@Override
public String call( RPCRequestData requestBody) {
public String call( ClientRequestBody requestBody) {
log.info("CreateNewChatFlow.call() called");
@ -101,10 +101,10 @@ public class CreateNewChatFlow implements RPCStartableFlow {
.addCommand(new ChatContract.Create())
.addSignatories(chatState.getParticipants());
// Convert the transaction builder to a UTXOSignedTransaction and sign with this Vnode's first Ledger key.
// Note, toSignedTransaction() is currently a placeholder method, hence being marked as deprecated.
@SuppressWarnings("DEPRECATION")
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
// the current node.
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
// Call FinalizeChatSubFlow which will finalise the transaction.
// 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",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.CreateNewChatFlow",
"requestData": {
"requestBody": {
"chatName":"Chat with Bob",
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
"message": "Hello Bob"

View File

@ -1,9 +1,9 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
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.RPCRequestData;
import net.corda.v5.application.flows.RPCStartableFlow;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
@ -19,7 +19,7 @@ import static java.util.Objects.*;
import static java.util.stream.Collectors.toList;
// 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);
@ -32,7 +32,7 @@ public class GetChatFlow implements RPCStartableFlow {
@Override
@Suspendable
public String call(RPCRequestData requestBody) {
public String call(ClientRequestBody requestBody) {
// Obtain the deserialized input arguments to the flow from the requestBody.
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
@ -70,7 +70,7 @@ public class GetChatFlow implements RPCStartableFlow {
while (moreBackchain) {
// 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(
ledgerService.findLedgerTransaction(transactionId),
"Transaction " + transactionId + " not found."
@ -110,7 +110,7 @@ RequestBody for triggering the flow via http-rpc:
{
"clientRequestId": "get-1",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow",
"requestData": {
"requestBody": {
"id":"** fill in id **",
"numberOfRecords":"4"
}

View File

@ -1,9 +1,9 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
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.RPCRequestData;
import net.corda.v5.application.flows.RPCStartableFlow;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.ledger.utxo.StateAndRef;
@ -15,7 +15,7 @@ import java.util.*;
import java.util.stream.Collectors;
// 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);
@ -28,7 +28,7 @@ public class ListChatsFlow implements RPCStartableFlow{
@Suspendable
@Override
public String call(RPCRequestData requestBody) {
public String call(ClientRequestBody requestBody) {
log.info("ListChatsFlow.call() called");
@ -53,6 +53,6 @@ RequestBody for triggering the flow via http-rpc:
{
"clientRequestId": "list-1",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow",
"requestData": {}
"requestBody": {}
}
*/

View File

@ -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.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.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.membership.MemberLookup;
import net.corda.v5.base.annotations.Suspendable;
@ -26,7 +26,7 @@ import static java.util.Objects.*;
import static java.util.stream.Collectors.toList;
// 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);
@ -46,7 +46,7 @@ public class UpdateChatFlow implements RPCStartableFlow {
@Suspendable
@Override
public String call(RPCRequestData requestBody) {
public String call(ClientRequestBody requestBody) {
log.info("UpdateNewChatFlow.call() called");
@ -87,10 +87,10 @@ public class UpdateChatFlow implements RPCStartableFlow {
.addCommand(new ChatContract.Update())
.addSignatories(newChatState.getParticipants());
// Convert the transaction builder to a UtxoSignedTransaction and sign with this Vnode's first Ledger key.
// Note, toSignedTransaction() is currently a placeholder method, hence being marked as deprecated.
@SuppressWarnings("DEPRECATION")
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
// the current node.
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
// Call FinalizeChatSubFlow which will finalise the transaction.
// 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",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow",
"requestData": {
"requestBody": {
"id":" ** fill in id **",
"message": "How are you today?"
}

View File

@ -1,41 +1,41 @@
package com.r3.developers.csdetemplate.flowexample.workflows;
import net.corda.simulator.RequestData;
import net.corda.simulator.SimulatedVirtualNode;
import net.corda.simulator.Simulator;
import net.corda.v5.base.types.MemberX500Name;
import org.junit.jupiter.api.Test;
class MyFirstFlowTest {
// Names picked to match the corda network in config/dev-net.json
private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
@Test
@SuppressWarnings("unchecked")
public void test_that_MyFirstFLow_returns_correct_message() {
// Instantiate an instance of the simulator.
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.
// Don't assign Bob's virtual node to a value. You don't need it for this particular test.
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
// Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
// 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.
);
// 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.
assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
}
}
//package com.r3.developers.csdetemplate.flowexample.workflows;
//
//import net.corda.simulator.RequestData;
//import net.corda.simulator.SimulatedVirtualNode;
//import net.corda.simulator.Simulator;
//import net.corda.v5.base.types.MemberX500Name;
//import org.junit.jupiter.api.Test;
//
//class MyFirstFlowTest {
// // Names picked to match the corda network in config/dev-net.json
// private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
// private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
//
// @Test
// @SuppressWarnings("unchecked")
// public void test_that_MyFirstFLow_returns_correct_message() {
// // Instantiate an instance of the simulator.
// 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.
// // Don't assign Bob's virtual node to a value. You don't need it for this particular test.
// SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
// simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
//
// // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
// MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
//
// // 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.
// );
//
// // 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.
// assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
// }
//}