Building, deploying, and running a Corda 5 CorDapp
June 28, 2022
In this blog post we will show how to build and deploy a simple example of a Corda Distributed Application (CorDapp) for Corda 5 using a snapshot of Corda 5 from when it was first open sourced around mid May 2022.
This CorDapp will run a workflow, AKA a “flow”, that calculates the sum of two numbers and returns the result. We will show how to build and deploy the example CorDapp and then execute its calculation flow. Typically, a CorDapp would allow transactions between different parties, maintain a ledger and check transactions with smart contracts. However at the time of writing Corda 5 support of these operations is not yet complete. While most of what is covered here will continue to be broadly applicable, please expect changes to the APIs used and in some of the details of how to build, deploy, and run CorDapps. Our example CorDapp is written in Kotlin but we also support CorDapp development in Java. Please see our overview of the Corda 5 architecture in the wiki for the corda/corda-runtime-os git repository.
Example CorDapp Prerequisites
Hardware
A system meeting the following specification is suitable for developing our CorDapp, and for building and deploying a Corda cluster required to run our CorDapp:
- Intel/AMD CPU with 8 or virtual cores / threads
- 32GiB RAM
- At least 30GiB disk.
These are not minimum specifications. This is what we have found to work for simple CorDapp development with snapshots of Corda 5 from around mid May 2022.
Operating Systems
The CorDapp can be built and run on Windows 10, MacOS Catalina, and Linux (Ubuntu 20.04.04), but it is anticipated that CorDapps can be developed on other versions of Linux, MacOS and Windows.
Third party development tooling
We recommend development using:
To deploy our Corda cluster in this example Kubernetes is used, you will need to have installed:
- Kubernetes (installed locally)
- kubectl
- Helm
Kubernetes Installation
For instructions on how to install a local Kubernetes cluster for the example CorDapp see ”Create a kubernetes cluster” and “Install Helm” from the Local development with Kubernetes page in the corda/corda-runtime-os Wiki up to and including the Install Helm section.
Getting Corda 5
Before we can build and run our CorDapp we must have:
- a Corda 5 cluster to run our CorDapp
- the Corda API to supply the annotations, classes and interfaces used to develop a CorDapp
- the “corda-cli” utility to provide tooling to package up our built CorDapp in a form that can be deployed onto the Corda 5 Cluster
The source code to build what we need is in the following GitHub repositories:
Note: The source in corda/corda-runtime-os has dependencies in the corda/corda-api and corda/corda-cli-plugin-host repositories. To allow corda/corda-runtime-os to build R3 have implemented a “composite build” in its build scripts. In order for that to work the corda/corda-runtime-os, corda/corda-api, and corda/corda-cli-plugin-host repositories must all be immediate children of the same parent directory.
As the Corda 5 code base is undergoing a lot of change a set of commits from the repositories that will work together has been given the tag BlogPost-corda-5-os-cordapp
. We will need to checkout that tag for each repository before we can build anything.
Preparation Steps For The Dependant Repositories
To clone the four repositories:
- Change to a directory where we will clone the four repositories to.
- Run git clone for:
- Then for each of the cloned repository:
- Change direction to the repository (and project) root directory.
- Run
git checkout BlogPost-corda-5-os-cordapp
Note:
Within each repository corda/corda-runtime-os, corda/corda-api, corda/corda-cli-plugin-host there is a Gradle project called corda-runtime-os, corda-api and corda-cli-plugin-host respectively. The terms project and repository will be used interchangeably.
Building Corda
Before attempting to run a build in any Corda 5 repository:
- Make sure that all the corda/corda-runtime-os, corda/corda-api and corda/corda-cli-plugin-host repositories are cloned such they are all the immediate children of the same parent directory.
- For each repository checkout out the tag
BlogPost-corda-5-os-cordapp
.
Build The Corda 5 Cluster Components
A Corda Cluster is composed of a set of services. For this example we will use the services built as a set of Docker images. We will run the publishOSGiImage
task to build the images, setting compositeBuild
to true in order to build the dependencies in the other repositories as required.
To Build The Cluster Services:
- Change directory to the root directory of the corda-runtime-os repository.
- If you are using minikube with docker, as you will if you followed our instructions for installing Kubernetes on Linux, minikube must be started and the shell must be configured to use the correct Docker daemon
a. Start Minikube:
minikube start
b. Configure the shell you use to run the build commands to use the minikube Docker daemon:
eval $(minikube docker-env)
3. Ensure that the docker daemon is running. The method to do this will depend on how Docker was installled and on what OS it was installed on. For a typical installation the daemon is started automatically on booting.
4. Run the build command, for Linux or MacOS:
./gradlew clean publishOSGiImage -PcompositeBuild=true
or on Windows in a PowerShell session:
.\gradlew.bat clean publishOSGiImage -PcompositeBuild=true
Build Corda-CLI
The “corda-cli” utility implements the tooling needed to create a CPI file for our CorDapp which can be deployed on a Corda cluster.
To Build The Corda-Cli Utility:
- Change directory to the root directory of the corda-cli-plugin-host project.
- Run the build command. For Linux or MacOS:
./gradlew clean build
or for Windows in a PowerShell session:
.\gradlew.bat clean build
Build Corda API
We also need to buld and publish the Corda API objects to the maven local repository. These supply the dependencies needed by the CorDapp. This is a repository that resides on the build machine.
To Publish The Corda API Objects:
- Change directory to the root of the corda-api project.
- Run the build command. For Linux or MacOS:
./gradlew clean publishToMavenLocal
For Windows in a PowerShell session:
.\gradlew.bat clean publishToMavenLocal
Our Example CorDapp
This blog post will show CorDapp development using the IntelliJ IDEA IDE. The CorDapp will be developed in Kotlin targetting Java 11. Corda API requires us to use Java 11. We will use the Gradle build automation tool with build scripts written in Groovy.
What follows are fairly general instructions of how to configure the project in Intellij with screenshots taken of IntelliJ IDEA 2021.3.3 (Community Edition).
Create A New Project
First we need to create a new project:
- From the menu select: File->New->Project.
This will open the “New Project” project dialog window. - Select Gradle project on the left hand side panel (see pink 1 in the screenshot below).
- Select Java 11 JDK from Project JDK drop down on the right hand side of the dialog window (see pink 2 in the screenshot below).
- Tick the Kotlin/JVM tick box under “Additional Libraries And Frameworks”. No other tick boxes should be ticked (see pink 3 in the screenshot below).
Set Project Name
- Set project name to
NewCorDapp
(see pink 1 in the screenshot below). - Set directory for the project (see pink 2 in the screenshot below).
Wait for the gradle build process which was triggered after creating the new project to complete. If you find that a src/main/kotlin
directory was not created under the project root directory, you can create it by right clicking on the project name, NewCorDapp
, in the “Project Tool Window” on the left hand side (see pink 1 in the screenshot below) and then select New followed by Directory (see pink 2 in the screenshot below):
Then select src/main/kotlin
in the dialog box that appears and then press enter:
Add A Package To The Project For Our CorDapp Code
Next, we need to create a package for our CorDapp called net.cordapp.example.calculator
. A Kotlin package is equivalent to a Java package. In the project tool window navigate to the Kotlin
directory and right click on it (see pink 1 in the screenshot below) and select New followed by Package (see pink 2 in the screenshot below):
In the dialog box that appears, enter a name for your package, in this case, net.cordapp.example.calculator
, and press enter:
Add “Skeleton” Source Files To The CorDapp Project
Our CorDapp code is composed of four Kotlin classes:
- CalculatorFlow
- InputMessage
- OutputFormattingFlow
- OutputMessage
We will implement these classes in four source files:
- CalculatorFlow.kt
- InputMessage.kt
- OutputFormattingFlow.kt
- OutputMessage.kt
Before any code is written we will use IntelliJ to add empty source files for us to add the class implementations in a later step. The files will be added to the net.cordapp.example.calculator
package in our CorDapp project. To create the CalculatorFlow.kt
file:
- Navigate to
net.cordapp.example.calculator
in the project tool window on the left hand side (see pink 1 in the screenshot below). - Right-click on the package, click New and select Kotlin Class/File (see pink 2 in the screenshot below).
In the dialog box that appears select File (see pink 1 in the screenshot below) and enter CalculatorFlow.kt
(see pink 2 in the screenshot below):
This will create a skeleton CalculatorFlow.kt
file that we will add code to.
- Repeat these steps to create the remaining files:
InputMessage.kt
OutputFormattingFlow.kt
OutputMessage.kt
By following the above instructions we should now have:
- A new IntelliJ project
- Configured to use Gradle with Groovy for our build scripting
- Autogenerated build.gradle, settings.gradle and gradle.properties files
- A new source directory
src/main/kotlin/net/cordapp/example/calculator
containing the following four nearly empty files:- CalculatorFlow.kt
- InputMessage.kt
- OutputFormattingFlow.kt
- OutputMessage.kt
Populate Our Source Files With Code
By this stage will should have four empty files except for the net.cordapp.example.calcultor
package declaration.
Next, we need to edit the four files so each contains the code in the following sections:
CalculatorFlow.kt
package net.cordapp.example.calculator
import net.corda.v5.application.flows.CordaInject
import net.corda.v5.application.flows.Flow
import net.corda.v5.application.flows.FlowEngine
import net.corda.v5.application.flows.StartableByRPC
import net.corda.v5.application.serialization.JsonMarshallingService
import net.corda.v5.application.serialization.parseJson
import net.corda.v5.base.annotations.Suspendable
import net.corda.v5.base.util.contextLogger
@StartableByRPC
class CalculatorFlow(private val jsonArg: String) : Flow<String> {
private companion object {
val log = contextLogger()
}
@CordaInject
lateinit var flowEngine: FlowEngine
@CordaInject
lateinit var jsonMarshallingService: JsonMarshallingService
@Suspendable
override fun call(): String {
log.info("Calculator starting...")
var resultMessage = ""
try {
val inputs = jsonMarshallingService.parseJson<InputMessage>(jsonArg)
val result = (inputs.a ?: 0) + (inputs.b ?: 0)
log.info("Calculated result ${inputs.a} + ${inputs.b} = $result, formatting for response...")
val outputFormatter = OutputFormattingFlow(result)
resultMessage = flowEngine.subFlow(outputFormatter)
log.error("Calculated response: $resultMessage")
} catch (e: Exception) {
log.error(":( could not complete calculation of '$jsonArg' because:'${e.message}'")
}
log.info("Calculation completed.")
return resultMessage
}
}
OutputFormattingFlow.kt
package net.cordapp.example.calculator
import net.corda.v5.application.flows.CordaInject
import net.corda.v5.application.flows.Flow
import net.corda.v5.application.serialization.JsonMarshallingService
import net.corda.v5.base.util.contextLogger
class OutputFormattingFlow(private val result: Int) : Flow<String> {
private companion object {
val log = contextLogger()
}
@CordaInject
lateinit var jsonMarshallingService: JsonMarshallingService
override fun call(): String {
try {
return jsonMarshallingService.formatJson(OutputMessage(result))
} catch (e: Exception) {
log.warn("could not serialise result '$result'")
throw e
}
}
}
InputMessage.kt
package net.cordapp.example.calculator
data class InputMessage(
var a: Int? = null,
var b: Int? = null
)
OutputMessage.kt
package net.cordapp.example.calculator
data class OutputMessage(
val result: Int
)
The above code implements two flows and two data classes to hold input and output data. The main flow, CalculatorFlow
, (implemented in CalculatorFlow.kt
) does the following:
- Parses the input data and extracts two integers.
- Sums the integers together.
- Runs a subflow,
OutputFormattingFlow
(implemented inOutputFormattingFlow.kt
), that turns the integer result into a JSON string. - Returns the JSON string.
OutputFormattingFlow
is not strictly necessary, but demonstrates that flows can run other flows. We call flows executed by another flow, subflows. CorDapp flows implement the Flow<T>
interface. The call()
member function is what is executed when we “run a flow”. Input parameters are passed in as the object constructor parameters and the call()
function returns the result of the flow.
Our CorDapp also uses the following annotations:
@CordaInject
— injects the implementations for theFlowEngine
andJosnMarshallingService
interfaces.@Suspendable
— allows theCalculatorFlow
to be stopped and resumed.@StartableByRPC
— allows the flow to be started via an HTTP API end point.
All of these annotations, as well as the Flow<T>
interface, are provided by the Corda API.
Create The Gradle Build Scripts For The CorDapp
To build a CorDapp that we can deploy, we must build each of its components as a separate CorDapp Package (CPK file). These are bundled together as one CorDapp Package Bundle (CPB file) for the CorDapp. CPK and CPB file formats are built around OSGi bundles. This ensures the isolatation of separate CorDapps when installed and executed on the same Corda 5 cluster.
Complex CorDapps would be split into components composed of related classes implementing different areas of functionaility. The simple example CorDapp shown here is composed of just one component that holds all the classes.
R3 has produced a set of Corda plugins to extend the Gradle DSL and the “build” task so that:
- The dependencies from the Corda API are declared and processed correctly.
- Additional CorDapp metadata can be supplied.
- CPK files are built for each CorDapp component and bundled together to produce a CPB file with a regular gradle build task.
The Corda plugins are available from the Gradle Plugin Portal.
Next, we need to update the build.gradle
, settings.gradle
and gradle.properties
files to contain the code in the following sections:
build.gradle
import static org.gradle.api.JavaVersion.VERSION_11
plugins {
id 'org.jetbrains.kotlin.jvm'
// (1)
id 'net.corda.plugins.cordapp-cpb'
}
group 'org.example'
version '1.0-SNAPSHOT'
def javaVersion = VERSION_11
// (2)
cordapp {
targetPlatformVersion platformVersion as Integer
minimumPlatformVersion platformVersion as Integer
workflow
{
name "Calculator"
versionId 1
vendor "R3"
}
}
// (3)
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions
{
allWarningsAsErrors = true
languageVersion = '1.6'
apiVersion = '1.6'
jvmTarget = javaVersion
javaParameters = true
freeCompilerArgs += [
"-java-parameters",
"-Xjvm-default=all"
]
}
}
// (4)
repositories {
mavenLocal()
mavenCentral()
// Needed for kotlin osgi bundles for corda
maven {
url = "$artifactoryContextUrl/corda-dependencies"
}
}
// (5)
dependencies {
// We need to use kotlin stdlib built as an OSGi Bundle
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
cordaProvided 'net.corda:corda-base'
cordaProvided 'net.corda:corda-application'
cordaProvided 'org.slf4j:slf4j-api'
}
test {
useJUnitPlatform()
}
build.gradle Notes
Some brief explanations on key parts of the above build.gradle
file.
// ...
plugins {
id 'org.jetbrains.kotlin.jvm'
// (1)
id 'net.corda.plugins.cordapp-cpb'
}
// ...
(1) Acorda-cpb
plugin declaration. This will automatically bring in all the corda plugins required to build a Corda Package Bundle (CPB) for the CorDapp and supply the cordapp DSL to specify metadata for our CorDapp.
// ...
// (2)
cordapp {
targetPlatformVersion platformVersion as Integer
minimumPlatformVersion platformVersion as Integer
workflow
{
name "Calculator"
versionId 1
vendor "R3"
}
}
// ...
(2) The cordapp
section is part of the DSL provided by the corda plugins to define metadata for our CorDapp. Each component of the CorDapp would get its own cordapp
section in the build.gradle file for the component’s subproject. The cordapp
section contains either a workflow
or contract
subsection depending on the type of component. A cordapp
section is required by the corda plugins to build the CorDapp. Our example CorDapp has just one component so we can implement everything in the main project and use the top level build.gradle file. The CorDapp only implements flows so its one cordapp
section gets a workflow
subsection. In there we set name
to “Calculator” and set a vender string and a version number. Note that for the time being, targetPlatformVersion
and minimumPlatformVersion
are both set to platformVersion
. This is set to a dummy value in the properties.gradle
file for now.
import static org.gradle.api.JavaVersion.VERSION_11
// ...
def javaVersion = VERSION_11
// ...
// (3)
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions
{
allWarningsAsErrors = true
languageVersion = '1.6'
apiVersion = '1.6'
jvmTarget = javaVersion
javaParameters = true
freeCompilerArgs += [
"-java-parameters",
"-Xjvm-default=all"
]
}
}
// ...
(3) A set of Kotlin compiler options we need to build a CorDapp.
// ...
// (4)
repositories {
mavenLocal()
mavenCentral()
// Needed for kotlin osgi bundles for corda
maven {
url = "$artifactoryContextUrl/corda-dependencies"
}
}
// ...
(4) Declarations of the Maven central and Maven Local repositories. The latter holds the Corda API packages that we built locally. We must also declare the R3 Artifactory repository that has the Kotlin stdlib built as an OSGi bundle, required to build the CorDapp as a CPB package.
// ...
// (5)
dependencies {
// We need to use kotlin stdlib built as an OSGi Bundle
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
cordaProvided 'net.corda:corda-base'
cordaProvided 'net.corda:corda-application'
cordaProvided 'org.slf4j:slf4j-api'
}
// ...
(5) A cordaProvided
declaration is required for anything that we use from the Corda API. We also need to declare a platform
so that we pick up the correct set of dependency versions for the version of the Corda API specified. We provide our own kotlin-stdlib-jdk8-osgi bundle which is Kotlin’s kotlin-stdlib-jdk8 jar which R3 have built into a/an OSGi bundle. Kotlin’s kotlin-osgi-bundle, that R3 might have used otherwise, only includes kotlin-stdlib. This does not contain all the dependencies we need but kotlin-stdlib-jdk8 does. Note that kotlin-stdlib-jdk11 does not exist. However, even though we are targetting Java 11 a Java 11 specific kotlin-stdlib is not required.
settings.gradle
For our example CorDapp, we need to replace the autogenerated settings.gradle
with the following settings.gradle
file:
pluginManagement {
// (1)
repositories
{
mavenLocal()
gradlePluginPortal()
}
// (2)
plugins {
id 'net.corda.plugins.cordapp-cpk' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
}
}
rootProject.name = 'NewCorDapp'
For our example CorDapp, we need to specify the following in the settings.gradle
file:
(1) The repositories where the Corda plugins are found.
(2) The plugin dependencies with versions of the plugins congruent with the specified Corda plugin version, Corda API version, and Kotlin version.
gradle.properties
For our example CorDapp, we need to replace the autogenerated gradle.properties
file with the following gradle.properties
file:
kotlin.code.style=official
# (1)
artifactoryContextUrl=https://software.r3.com/artifactory
# (2)
cordaApiVersion=5.0.0.95-SNAPSHOT
cordaPluginsVersion=6.0.0-DevPreview
# (3)
platformVersion = 999
# (4)
kotlinVersion = 1.6.21
# (5)
kotlin.stdlib.default.dependency=false
For our example CorDapp, we need to specify the following in the gradle.properties
file:
(1) The URL of R3’s artifactory repository that holds Kotlin stdlib built as an OSGi bundle.
(2) The versions of the Corda API and the Corda plugins we need to build.
(3) A temporary dummy value for platformVersion
.
(4) The version of Kotlin required.
(5) Specify that we do not use default dependencies.
Build The CorDapp
Next we build the CordDapp. This means building the CPB file. Details on the CPB format and background on why we use it are in the Corda 5 Architecture Guide.
To build the CorDapp run the following command from the root directory of the CordDapp project, on Linux or MacOS:
./gradlew clean build
Or on Windows:
.\gradlew.bat clean build
These commands will build the CPB file that we need under build/libs directory in the project root directory that is build/libs/NewCorDapp-1.0-SNAPSHOT-package.cpb
.
Make the CorDapp Deployable
In order to use a CorDapp CPB, bundle we need to create a Corda Package Installer (CPI). This a signed CPB with a Group Policy that provides information to Corda on the virtual network that the CorDapp(s) are to be used in. The Corda-cli.sh
(for Linux and MacOS) and corda-cli.cmd
(for Windows) scripts from the corda-cli-plugin-host project can create a Group Policy and CPI files. The scripts should be generated in the build/generatedScripts directory relative to the corda-cli-plugin-host project root directory. See the project documentation for more details.
To create our CPI:
If working on Linux or MacOS make sure that corda-cli.sh
is executable:
- Change directory to the project root directory of corda-cli-plugin-host
- Run this command:
chmod +x build/generatedScripts/corda-cli.sh
- Create a directory called
cordapp-deploy
from the parent directory of the corda-runtime-os, corda-api and corda-cli-plugin-host projects. - Change directory to
cordapp-deploy
- Create a keystore to hold the certificates we will need to create the CPI file from our CorDapp CPB file. Create this with the keytool utility from the JDK. On Linux or MacOS run:
keytool -genkey \
-alias "my signing key" \
-keystore signingkeys.pfx \
-storepass "keystore password" \
-dname "cn=CPI Example - My Signing Key, o=CorpOrgCorp, c=GB" \
-keyalg RSA \
-storetype pkcs12 \
-validity 4000
Or on Windows PowerShell 7 run:
keytool -genkey `
-alias "my signing key" `
-keystore signingkeys.pfx `
-storepass "keystore password" `
-dname "cn=CPI Example - My Signing Key, o=CorpOrgCorp, c=GB" `
-keyalg RSA `
-storetype pkcs12 `
-validity 4000
- We need a time stamping authority to sign the CPI. For example Free Time Stamp Authority. Using a web browser download the Free Time Stamp Authority certificate cacert.pem and move the file to the
cordapp-deploy
directory. - The time stamping authority’s certificate needs to be added to our truststore so that we can use it to sign our CPI. We need to run a the keytool command. When we do it will ask whether to trust cacert.pem certificate. To continue type yes and press return. Then on Linux or MacOS run:
keytool -import \
-alias "freetsa" \
-keystore signingkeys.pfx \
-storepass "keystore password" \
-file cacert.pem
Or on Windows PowerShell 7:
keytool -import `
-alias "freetsa" `
-keystore signingkeys.pfx `
-storepass "keystore password" `
-file cacert.pem
Generate a “Group Policy” file needed for the CPI we will create. On Linux or MacOS :
../corda-cli-plugin-host/build/generatedScripts/corda-cli.sh mgm groupPolicy > MyGroupPolicy.json
Or on Windows PowerShell 7:
..\corda-cli-plugin-host\build\generatedScripts\corda-cli.cmd mgm groupPolicy > MyGroupPolicy.json
- Create a CPI called
NewCorDapp.cpi
using the CPB file for our CorDapp. Where<cordapp-project-root-dir>
is the path to the root directory of our CorDapp project, on Linux or MacOS run:
../corda-cli-plugin-host/build/generatedScripts/corda-cli.sh package create \
--cpb <cordapp-project-root-dir>/build/libs/NewCorDapp-1.0-SNAPSHOT-package.cpb \
--group-policy MyGroupPolicy.json \
--keystore signingkeys.pfx \
--storepass "keystore password" \
--key "my signing key" \
--tsa https://freetsa.org/tsr \
--file NewCorDapp.cpi
Or on Windows PowerShell 7 run:
..\corda-cli-plugin-host\build\generatedScripts\corda-cli.cmd package create `
--cpb <cordapp-project-root-dir>\build\libs\NewCorDapp-1.0-SNAPSHOT-package.cpb `
--group-policy MyGroupPolicy.json `
--keystore signingkeys.pfx `
--storepass "keystore password" `
--key "my signing key" `
--tsa https://freetsa.org/tsr `
--file NewCorDapp.cpi
This will create the CPI file incordapp-deploy
called NewCorDapp.cpi
that we can deploy on our Corda cluster.
Deploy a Corda cluster for the CorDapp
For the example CorDapp we will deploy our Corda cluster on a local Kubernetes cluster. If you do not already have Docker, kubernetes and helm installed on your system follow the instructions from the top of the Local development with Kubernetes page down to and including the Install Helm section. Once we have deployed our Corda Cluster we will then be able upload our CorDapp to it and run its flows.
The commands given below will deploy a cluster for a version of Corda known to with the example CorDapp. For the most up-to-date instructions on bringing up a Corda cluster on Kubernetes see the corda/corda-runtime-os wiki.
To Deploy A Cluster:
- If you are using Windows, you must download and install PowerShell 7.
- If you are using minikube:
- You must have started minikube, you do not need to restart minikube if it is already started
minikube start
- You must configure the shell you use to run the helm commands to use the Docker daemon running in minikube
eval $(minikube docker-env)
- You must have run the build command for corda-runtime-os as descibed above in a shell configured to use the minikube Docker daemon.
- Ensure that the docker daemon is running.
- Create a new kubernetes name space for our Corda cluster to run in called corda. Run:
kubectl create namespace corda
Deploy what we call the corda prerequisites on our Kubernetes cluster. These include a postgres RDBMS and a small kafka cluster that the Corda cluster needs.
- Change directory to root directory of the corda-dev-helm repository.
- Deploy the helm charts for the prerequisites. On Linux or MacOS run:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm dependency build charts/corda-dev
helm upgrade --install prereqs -n corda \
charts/corda-dev \
--set kafka.replicaCount=1,kafka.zookeeper.replicaCount=1 \
--render-subchart-notes \
--timeout 10m \
--wait
Or on Windows PowerShell 7 run:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm dependency build charts/corda-dev
helm upgrade --install prereqs -n corda `
charts/corda-dev `
--set kafka.replicaCount=1,kafka.zookeeper.replicaCount=1 `
--render-subchart-notes `
--timeout 10m `
--wait
- You will have to wait several minutes for the last command to finish.
- Change directory to the root directory of the corda-runtime-os project. This should be ../corda-runtime-os if you carried out the steps above.
- Create a values.yaml file with the contents:
imagePullPolicy: IfNotPresent
image:
registry: corda-os-docker-dev.software.r3.com
tag: latest-local
kafka:
bootstrapServers: prereqs-kafka:9092
db:
cluster:
host: prereqs-postgresql.corda
existingSecret: prereqs-postgresql
- Now start the Corda 5 specific services. Note that this command can timeout sometimes. In that case rerunning should be enough to bring everything up. On Linux or MacOS run:
helm upgrade --install corda -n corda \
charts/corda \
--values values.yaml \
--wait
- Or on Windows PowerShell 7 run:
helm upgrade --install corda -n corda `
charts/corda `
--values values.yaml `
--wait
Deploy and run the CorDapp (CPI)
Now we have our CPI we can deploy it to the Corda cluster and run its flows. In broad terms to do this we will need to:
The details for each step above is given in more detail below. For each step we will need to send commands to the Corda cluster. These are submitted via an HTTP API provided by one of the Corda workers running in the cluster. In the examples given we will issue commands with the command line utility, curl.
Note:
- You must allow some time for the
CPI
to upload to complete in cluster after the upload command given below is complete. - Only the first
CPI
uploaded is accepted for the sameCPI
. Rebuilding theCPI
or creating a CPI with a differentGroupPolicy.json
is not enough. Subsequent uploads seem to ‘succeed’ but do not.
Enable HTTP API Access To Cluster
- Before we can proceed we need to forward a port to the pod providing the HTTP API. This can be done with the following command run on Linux, MacOS or Windows PowerShell 7. You might want to run this command in a separate terminal on Linux or MacOS as it generates output when running on those systems:
kubectl port-forward -n corda deploy/corda-rpc-worker 8888 &
Upload the CorDapp CPI to the cluster
- In order to use a CPI we must first upload to it to the cluster. We set the location of the CPI file to an environment variable for convenience. On Linux or MacOS:
CPI=<path-to-CPI-file-created-earlier>/NewCorDapp.cpi
Or on Windows PowerShell 7:
$CPI="<path-to-CPI-file-created-earlier>\NewCorDapp.cpi"
- We can upload the CPI file with a curl command. On Linux, MacOS and Windows PowerShell 7 run:
curl --insecure -u admin:admin -s -F upload=@$CPI https://localhost:8888/api/v1/cpi/
This should return JSON that looks a bit like this:
{"id":"13bf657f-bf1a-4957-960c-6f99c944e972"}
Keep the request ID, the value for id
from the JSON. On Linux or MacOS:
REQUEST_ID="13bf657f-bf1a-4957-960c-6f99c944e972"
On Window PowerShell 7
$REQUEST_ID="13bf657f-bf1a-4957-960c-6f99c944e972"
If you want to upload the CPI for a second time you will need to delete the kubernetes cluster (kubernetes delete namespace corda
) and recreate following the steps from the “Deploy a Corda cluster for the CorDapp” section again.
- Now wait for around 1 minute for the upload to finish. It is important to wait as at the time of writing there is a bug where attempting subsequent commands will never succeed if attempted too soon. The Corda 5 cluster would have to be deleted and recreated in that circumstance.
- We need to get the CPI hash that we will use to refer to the CPI in future commands. The following command also indicates the status of the upload process (on Linux, MacOS and Windows PowerShell 7):
curl --insecure -u admin:admin https://localhost:8888/api/v1/cpi/status/$REQUEST_ID
We should get something like this:
{"status":"OK","checksum":"C8447000638E"}
The CPI hash is returned as the value for checksum
and allows us to uniquely identify the CPI we uploaded in future commands.
- We will need the value of the checksum for following commands. Store the checksum from the JSON in an environment variable. On Linux or MacOS:
CPI_HASH="C8447000638E"
Or Windows PowerShell 7:
$CPI_HASH="C8447000638E"
Create a virtual node in the cluster
A virtual node is required to run a CorDapp. Creating a virtual node links the CPI with an identity that will run the CorDapp.
- For this example we will use an arbitrary X.500 identity. Store an X.500 identity in an environment variable for use in later commands. On Linux or MacOS:
X500="CN=IRunCorDapps, OU=Application, O=R3, L=London, C=GB"
On Windows PowerShell 7:
$X500="CN=IRunCorDapps, OU=Application, O=R3, L=London, C=GB"
- Now we create the virtual node. On Linux and MacOS:
curl --insecure -u admin:admin -s -d '{ "request": { "cpiFileChecksum": "'"$CPI_HASH"'", "x500Name": "'"$X500"'" } }' https://localhost:8888/api/v1/virtualnode
Or on Windows PowerShell 7:
$PAYLOAD=@{ request = @{ cpiFileChecksum = "$CPI_HASH"; x500Name = "$X500" } }
$PAYLOAD | ConvertTo-Json | curl --insecure -u admin:admin -s -d '@-' https://localhost:8888/api/v1/virtualnode
This returns JSON that looks a bit like this (after I pretty-printed it):
{
"x500Name":"CN=IRunCorDapps, OU=Application, O=R3, L=London, C=GB",
"cpiId":
{
"cpiName":"NewCorDapp",
"cpiVersion":"1.0-SNAPSHOT",
"signerSummaryHash":null
},
"cpiFileChecksum":"C8447000638ECFE24EC3B1DDA4576B186D28014C37B268D12FE3FA8746DF69ED",
"mgmGroupId":"ABC123",
"holdingIdHash":"C5659EC601CE",
"vaultDdlConnectionId":"e2c8568b-40f4-47b5-9daa-a0c9b024b648",
"vaultDmlConnectionId":"265e6a71-c488-4454-93a6-7694fbc8bb42",
"cryptoDdlConnectionId":"29f6e82d-7e00-4b0a-b9a7-f71f1dc968de",
"cryptoDmlConnectionId":"f124a344-646b-41c7-adc2-3d764a9a66b0"
}
We need the value of the holding ID hash that holdingIdHash
has been set to. It will identify the virtual node in following commands. Note that the holdingIdHash will almost certainly be different for each virtual node. On Linux or MacOS:
HOLDING_ID="C5659EC601CE"
Or Windows PowerShell 7:
$HOLDING_ID="C5659EC601CE"
Run the CPI Flow
We can now run a flow on the virtual node we created.
- The following command calculates the sum of the two integers 32 and 10 using
CalculatorFlow
. On Linux or MacOS:
curl --insecure -u admin:admin -X PUT -d '{ "requestBody": "{ \"a\":32, \"b\":10 }" }' https://localhost:8888/api/v1/flow/$HOLDING_ID/r1/net.cordapp.example.calculator.CalculatorFlow
Or on Windows PowerShell 7:
$PAYLOAD=@{ requestBody= "`{ `"a`":32, `"b`":10 `}" }
$PAYLOAD | ConvertTo-Json | curl --insecure -u admin:admin -s -X PUT -d '@-' https://localhost:8888/api/v1/flow/$HOLDING_ID/r1/net.cordapp.example.calculator.CalculatorFlow
The command returns JSON indicating the flow status (again pretty-printed):
{
"isExistingFlow":false,
"flowStatus":
{
"holdingShortId":"C5659EC601CE",
"clientRequestId":"r1",
"flowId":null,
"flowStatus":"START_REQUESTED",
"flowResult":null,
"flowError":null,
"timestamp":"2022-06-17T12:32:20.366724Z"
}
}
- Now wait to give the cluster some time to execute the flow.
- Poll the cluster to get the status of the flow (Linux, MacOS or Windows PowerShell 7):
curl --insecure -u admin:admin https://localhost:8888/api/v1/flow/$HOLDING_ID/r1
You might back get something like this if the flow has not completed:
{
"holdingShortId":"C5659EC601CE",
"clientRequestId":"r1",
"flowId":null,
"flowStatus":"START_REQUESTED",
"flowResult":null,
"flowError":null,
"timestamp":"2022-06-24T00:22:34.216987Z"
}
Eventually the command should return JSON where flowStatus
is set to COMPLETED
as shown below:
{
"holdingShortId":"C5659EC601CE",
"clientRequestId":"r1",
"flowId":"c4b4702d-e1ef-4445-a465-05b09bcb8738",
"flowStatus":"COMPLETED",
"flowResult":"{\"result\":42}",
"flowError":null,
"timestamp":"2022-06-17T12:35:39.174119Z"
}
Our flow returns its result as the value for flowResult
. In the JSON above this is “result :42”. The code that generates the output is in OutputFormatterFlow.kt
line 17 and CalculatorFlow.kt
line 26. Thankfully, the answer returned for the sum of 32 and 10 is 42.
Rerunning The Flow
Each time we start a flow we need to use a unique request number. Looking at the 2 URLs from Run the CPI flow:
https://localhost:8888/api/v1/flow/$HOLDING_ID/r1/net.cordapp.example.calculator.CalculatorFlow
https://localhost:8888/api/v1/flow/$HOLDING_ID/r1
Both have “r1” as part of the path component. To rerun the flow again you need to use r2, r3, r4 and so on. The reason why is because we can run flows concurrently and when we run a flow we make separate HTTP API calls to start flows and poll for results. So when we poll for results we need to tell the corda cluster which flow start request we want the results for.
For example to rerun the flow, on Linux or MacOS: or Windows PowerShell 7):
curl --insecure -u admin:admin -X PUT -d '{ "requestBody": "{ \"a\":15, \"b\":53 }" }' https://localhost:8888/api/v1/flow/$HOLDING_ID/r2/net.cordapp.example.calculator.CalculatorFlow
Or on Windows PowerShell 7
$PAYLOAD=@{ requestBody= "`{ `"a`":15, `"b`":53 `}" }
$PAYLOAD | ConvertTo-Json | curl --insecure -u admin:admin -s -X PUT -d '@-' https://localhost:8888/api/v1/flow/$HOLDING_ID/r2/net.cordapp.example.calculator.CalculatorFlow
- Wait
- Then collect results, run (on Linux, MacOS or Windows PowerShell 7):
curl --insecure -u admin:admin https://localhost:8888/api/v1/flow/$HOLDING_ID/r2
Cluster Clean Up
Deleting The Kubernetes Cluster
You can remove the corda cluster from kubernetes by deleting the corda
namespace we created earlier.
Warning: this will delete everything in the Kubernetes corda namespace. On Linux, MacOS or Windows:
kubectl delete namespace corda
Removing Corda Images
To remove the Corda 5 images once used for the cluster use the docker image rm
command to delete images with names prefixed with “corda-os-docker-dev.software.r3.com/corda-os-
“.
Conclusion
Following the instructions above should give you a working Corda cluster, built and deployed from source, and a simple CorDapp, built and deployed to the cluster with running flows. There are lots of changes coming to Corda 5 but we hope that this article shows what we have so far and how it works. This is just the beginning.