Tech Stack
CorDapps, consisting of flows Communication between participants in an application network is peer-to-peer using flows. , and optionally states An immutable object representing a fact known by one or more participants at a specific point in time. You can use states to represent any type of data, and any kind of fact. and contracts, are “pieces of code” hosted by the Corda runtime. This code can be written in a JVM compatible language. Java and Kotlin are officially supported. Currently, Corda 5 supports CorDapps compiled using Azul Zulu JDK 17.
CorDapps are simply code, written in a JVM compatible language, compiled into a special type of JAR called a CPK Corda Package. A signed ZIP/JAR library of Java code packaged to be portable with all of its dependencies and version information contained within it. . These CPKs are compiled using the Gradle plugin. See the Corda 5 Samples repository for an example of a typical CorDapp.
A CorDapp must be installed in a Corda cluster to make it available to virtual nodes The combination of the context of a user and the ephemeral compute instances created to progress a transaction on that identity's behalf. . To enable this, the CorDapp must be packaged up into a CPB Corda Package Bundle. A signed ZIP/JAR collection of CPKs that forms a complete application suite and contains all the code that a virtual node must operate, minus the specific network details. , which comprises of all CPKs necessary for a CorDapp to be complete, and their dependencies. The CPB must then be combined with network metadata into a CPI Corda Package Installer. A signed ZIP/JAR combination of a CPB and a Group Policy File that defines not only the application code that a virtual node will run, but also the details of the MGM with which to register, and the details of network PKI requirements. before it can be installed in the cluster. For more information, see Packaging.
While CPBs and CPIs use the .cpb
and .cpi
file extension, the files conform to the JAR file specification, and therefore JAR tools can be used to inspect them. For example:
❯ jar tf iou-app.cpb
META-INF/MANIFEST.MF
META-INF/CORDAPP.SF
META-INF/CORDAPP.EC
META-INF/
workflows-1.0-SNAPSHOT.jar
contracts-1.0-SNAPSHOT.jar
Corda Programming Model
Using a JVM compatible language for creating workflows, states, and contracts is very powerful. As a CorDapp developer, you have very effective languages such as Java and Kotlin at your disposal, as well as the rich JVM ecosystem.
Corda enhances the programming experience not just by offering access to a familiar tech stack and tooling, but also by introducing a familiar programming model to distributed application development.
Consider the previous IOU issue flow example. The following diagram describes this scenario:
The following is the code for the initiating flow:
...
// Create the IOUState from the input arguments and member information.
val iou = IOUState(
amount = flowArgs.amount.toInt(),
lender = lenderInfo.name,
borrower = myInfo.name,
paid = 0,
linearId = UUID.randomUUID(),
listOf(myInfo.ledgerKeys[0],lenderInfo.ledgerKeys[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.
val signedTransaction = txBuilder.toSignedTransaction()
...
// Calls the Corda provided finalise() function which gathers signatures from the counterparty,
// notarises the transaction and persists the transaction to each party's vault.
// On success returns the ID of the transaction created.
val finalizedSignedTransaction = ledgerService.finalize(
signedTransaction,
sessions
)
...
Some detail has been omitted for simplicity, but the above code highlights three important stages of the initiating flow:
- Creating the state
- Signing the transaction A transaction is a proposal to update the ledger.
- Requesting the peer(s) to review the transaction and sign it
The responder flow looks something like this:
// Calls receiveFinality() function which provides the responder to the finalise() function
// in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether
// the responder should sign the transaction.
val finalizedSignedTransaction = ledgerService.receiveFinality(session) { ledgerTransaction ->
val state = ledgerTransaction.getOutputStates(IOUState::class.java).singleOrNull() ?:
throw CordaRuntimeException("Failed verification - transaction did not have exactly one output IOUState.")
... // decide whether to "accept" this transaction
}
Continuance
As shown in the previous diagram, what seem like simple function calls in a CorDapp often represent complex “out-of-process” operations. The most obvious one being the request to another party to respond. This involves a peer-to-peer session to initiate the responder flow at the other party’s Corda runtime. In the best case scenario, the other party responds quickly, but as with any distributed application, this cannot be guaranteed. The other party may, for example, suffer from an infrastructure failure and be unavailable for a short period of time, or the responder flow may simply be slow, maybe it integrates with another downstream system as part of the responder flow, with each “hop” potentially contributing to a slow response. This unpredictability poses a challenge on the initiating side. Firstly, “waiting” can be expensive and resources should be made available as soon as possible so they can be reused. Secondly, a long wait increases the probability of being interrupted.
In order to avoid these long waits, and also to facilitate building fault tolerant workflows, Corda uses a suspend/resume model. In practice, every time one of these “out-of-process” operations happens, such as, signing, persisting, or peer-to-peer communications, the flow should suspend and a checkpoint created.
When the out-of-process operation completes, the checkpoint can be restored and the flow resumes from this checkpoint.
Returning to the IOU example, the following diagram shows suspension and resumption points in the flow:
Corda manages suspending and resuming. However, it may be necessary for a CorDapp Developer to inform Corda of certain suspension points. This is achieved by adding the @Suspendable
annotation to functions.
Workflow Versus Contract
CorDapps typically consist of flows, states, and contracts, all written in a JVM compatible language, and deployed as JARs. Corda, however, makes a distinction between workflow and contract CPKs (JARs). This is identified in the Gradle CorDapp configuration.
Workflow CPK:
cordapp {
workflow {
name "WorkflowsModuleNameHere"
versionId 1
vendor "VendorNameHere"
}
}
Contract CPK:
cordapp {
contract {
name "ContractsModuleNameHere"
versionId 1
vendor "VendorNameHere"
}
}
The following rules identify which CPK type is applicable:
- Flow code (anything implementing the Flow interface) can only exist in a workflow CPK.
- Contracts and states (implementing
Contract
andContractState
) can only exist in a contract CPK. -  Entity
An organization or individual that participates in one or more application networks that can provide attestation that they are whom they claim to be.
classes used in the persistence API (annotated with
@Entity
), along with their database migration scripts, can only exist in a contract CPK. - Workflow CPKs can reference contract CPKs, but never the other way around.
As a CorDapp Developer, it is usually sufficient to remember the above rules. However, to understand a little bit more about why there are different CPKs, you must understand where and when the code in these CPKs is executed. The distributed architecture of Corda is based on worker JVM processes that run in a cluster and perform a specific task. The processes required to form a cluster depend on the deployment topology. Workers increase or scale back their capacity depending on the number of available tasks. processes. There are different types of workers that each have their own operational responsibility and, because it is possible for each type of worker to scale horizontally, they are all stateless.
Two of these worker types, the flow worker A worker that runs the CorDapp application code and translates flow API calls into function requests to the relevant workers. The flow workers are designed to share work between themselves and to record checkpoints at each stage of the application's progress, so that in the event of worker failure, the operations can be retried. and the database worker A worker that connects to, manages, and operates upon the database(s) used by the Corda cluster. This includes the cluster-level database schemas needed to store configuration data for the cluster, but also the separate databases/schemas used by each virtual node. , are special because they host CorDapp code. They act as an application server for the code in the CPKs that are part of the CorDapp. This code runs inside a Corda sandbox An execution environment within a JVM process that provides isolation for a CorDapp. It shields it from outside threats but it also restricts what it can do so that running potentially dangerous code cannot harm others. . There are three different types of sandboxes:
- Flow - the flow engine host. This exists in the flow worker and is responsible for executing all flow code.
- Persistence - hosted by the persistence worker. This takes instructions from the flow engine to persist states or custom objects. For this reason, it needs to parse custom entity classes that are part of the CorDapp.
- Verify - this is hosted by the flow worker but is exclusively responsible for contract verification.
This relates to the separation of contract and workflow CPKs, as follows:
- Flow sandbox — requires both workflow and contract CPKs in order to execute an initiating or responder flow.
- Persistence sandbox — has special privileges as it is allowed to interact with the virtual node The combination of the context of a user and the ephemeral compute instances created to progress a transaction on that identity's behalf. ’s databases. However, it only requires the custom entities and states that are part of the contract CPK. Workflow CPKs are never loaded into the persistence sandbox.
- Verify sandbox — exclusively used for verifying the contract. Therefore, it only needs the contract CPK. Workflow CPKs are never loaded into the verify sandbox. During backchain verification, it is sometimes necessary to verify “old” states that require the previous version of the contract to verify. This is another reason why it is wise to separate contract CPKs from workflow CPKs as their version lifecycle may be different.
Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.