Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Developing With Corda: States and Contracts

DZone 's Guide to

Developing With Corda: States and Contracts

Want to learn more about developing with Corda? Check out this post to learn more about using Corda to develop contracts and states!

· Security Zone ·
Free Resource

In my last post, I gave an overview of what Corda is trying to achieve and the design decisions that were made to do so. That information is great to know, so we can get some perspective of the platform, but it’s not going to help us in writing a system that can leverage Corda. To do that, we need to know how the components of Corda work and fit together, only then can we start writing an application that actually does something and works correctly from the perspective of Corda. I see this as the same as learning any other framework, but we need the voice in the back of our heads to remind us every now and then that we are, in fact, designing upon a Distributed Ledger Technology platform to make sure that the applications we create are properly designed.

In this post, we will look at writing a very simple Corda application.

Corda is compatible with any JVM language. I know you're thinking it, but no, it’s not written in Java. It is actually written in Kotlin. This might require a little bit of extra learning to get to grips with Kotlin if you normally work with Java (like I do), but it shouldn’t take you long to be comfortable with it. Due to this, I personally suggest writing your own code in Kotlin to keep the whole stack in the same language, so when you need to dig down into Corda’s own code, it won’t look an alien compared to what you were just writing. Obviously, that is just my suggestion, and you could instead use Clojure, but you're going to find it hard to get a hold of any existing examples without first converting them into their Clojure equivalents.

It is a bit hard to dive straight into the code without first understanding the key concepts of Corda. Rather than going through these myself, I personally believe the documentation Corda provides on this subject is very helpful and should get you most of the way there.

For the purpose of this post, I think it is best to leave out the configuration required to actually setup Corda nodes, and instead, we should fully focus on writing a Corda application. In a later post, I will go over the configuration needed to set up nodes so that they can interact with each other. For now, you're just going to have to trust me that this stuff works.

Overview of the Application

Before we can begin writing any code, we need to have an understanding of what the application is trying to achieve. Rather than coming up with my own example, I will lean upon r3’s training materials, as the example is reasonably simple to understand. This example is the “IOU” process (I owe you), where someone requests for some money that will be paid back at a later date. In this post, we will just focus on the issuing of an IOU.

Below is a sequence diagram containing the very simplified steps involved in issuing an IOU between two people:

very simple iou sequence diagram

Simplified diagram of an IOU transaction

As you can see, the borrower asks for some money. If the lender agrees, then they both try to remember that the borrower owes the lender some money. This is process is simple, but even then, it has been simplified some more. In this process, we haven’t actually mentioned when and how the money is transferred between them. For the purpose of this tutorial, we shall leave this transfer out and just focus on saving the intent of borrowing and lending money between them.

So, how is this sort of process modeled in Corda? Again, before we move on, I want to mention Corda’s key concepts as they are extremely important for understanding what is going on properly.

To model the process in Corda, we need to replace some of the steps. Determining how much money to borrow becomes the creation of a transaction containing this information. A party being happy with what is proposed is now represented by signing the transaction with their key. Communication between the borrower and lender is replaced with sending a transaction between them. Finally, remembering who owes who is now set in stone with both parties, saving the transaction that occurred between them. By altering the simple diagram with this new information, a new version is created:

iou sequence diagram 2

Diagram of an IOU transaction


There are more steps, but the process is still pretty simple. The diagram really speaks for itself, and I don’t think there is anything more I can add to it. What I will say is that I have still further simplified the diagram a little bit by removing the Notary, but we should only focus on the process we are trying to model. We’ll touch on what a Notary is very, very, very briefly later on, but I will again suggest Corda’s documentation on the subject (I’ll do the same again later on).

These diagrams should provide us with enough guidance to put our Corda application together, issuing an IOU between two parties.

States

Onto the coding sections, let’s start with states. Information on states can be found here from the Corda documentation. States are passed around the network between nodes and eventually find themselves stored on the ledger. In terms of a transaction, a state can be an input or an output and many of these can exist on a single transaction. They are also immutable, which is required to build up the chain of states that are used within transactions.

As mentioned earlier, I am leaning upon r3 training materials. Below is the IOUState that will be passed around with the application:

data class IOUState(val amount: Amount<Currency>,
                    val lender: Party,
                    val borrower: Party,
                    val paid: Amount<Currency> = Amount(0, amount.token),
                    override val linearId: UniqueIdentifier = UniqueIdentifier()) : LinearState {

    override val participants: List<Party> get() = listOf(lender, borrower)
}


Due to the “IOU” concept, pretty much all the fields make sense without much explanation: amount is the lent amount, lender the party lending the money, and so on. The only property that needs explaining is linearId, which is of type UniqueIdentifier. This class is basically a UUID, in fact, it’s internalId is generated from the UUID class.

The state extends LinearState,which is one of the general types of state that Corda uses with another being FungibleState. Both of these are implementations of ContractState. LinearStates are used to represent states that it “evolve by superseding itself." As such, when the state is updated, it should be included as an input of a transaction with a newer version being output. The old state will now be marked as CONSUMED from UNCONSUMED when saved to the vault (Corda’s abstraction over the database).

ContractState requires a property that returns the participants of the state.

The participants are a very important part of the state. The parties defined within this list determine who gets to have the state saved to their own vault/database. If you find that your state is not being saved to a party that should know about it, this is most likely the issue. I have personally run into and felt the pain of this mistake.

In the above code, the participants were not included in the constructor and, instead, relies on defining a get function that can be used to retrieve them. Here, it returns the lender and the borrower, since they are the only two parties involved in the transaction. If you wanted to, you could add the participants to the constructor like below:

data class IOUState(val amount: Amount<Currency>,
                    val lender: Party,
                    val borrower: Party,
                    val paid: Amount<Currency> = Amount(0, amount.token),
                    override val linearId: UniqueIdentifier = UniqueIdentifier(), 
                    override val participants: List<Party> = listOf(lender, borrower)) : LinearState


This allows you to define participants that might not be included in the state. Which route you take depends on your use-case. For this tutorial, either will do the job.

Contracts

Next up are contracts. Contracts are used to validate input and output states for a given command by all parties involved in the transaction. The command could be, for example, issuing the state or paying off owed money. We will look at commands later in this section, but we should be able to brush over them for now.

Contracts are quite nice to write due to their expressiveness. We are able to write conditions within the contract that must be met for a transaction to be valid. If any are not met, then an exception will be thrown, which will normally end in terminating the current transaction since an involved party has found it invalid.

These conditions use the requireThat DSL that Corda defines to specify conditions along with error messages that detail what is wrong with the transaction. This makes it nice and easy to go through a contract and understand what it is doing, since the code conditions are nicely complemented by the English messages (or whatever language you want to write them in).

Below is an example of a contract that is used to validate the IOUState defined above, again this is taken from r3’s training materials:

class IOUContract : Contract {
    companion object {
        @JvmStatic
        val IOU_CONTRACT_ID = "net.corda.contracts.IOUContract"
    }

    interface Commands : CommandData {
        class Issue : TypeOnlyCommandData(), Commands
        class Transfer : TypeOnlyCommandData(), Commands
        class Settle : TypeOnlyCommandData(), Commands
    }

    override fun verify(tx: LedgerTransaction) {
        val command = tx.commands.requireSingleCommand<Commands>()
        when (command.value) {
            is Commands.Issue -> requireThat {
                "No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
                "Only one output state should be created when issuing an IOU." using (tx.outputs.size == 1)
                val iou = tx.outputStates.single() as IOUState
                "A newly issued IOU must have a positive amount." using (iou.amount.quantity > 0)
                "The lender and borrower cannot have the same identity." using (iou.borrower != iou.lender)
                "Both lender and borrower together only may sign IOU issue transaction." using
                        (command.signers.toSet() == iou.participants.map { it.owningKey }.toSet())
            }
            is Commands.Transfer -> requireThat {
                // more conditions
            }
            is Commands.Settle -> {
               // more conditions
            }
        }
    }
}


I have simplified the contract for the purpose of this post, since we will only focus on implementing one command type. Let’s start from the top and work our way down.

The IOUContract implements the Contract, requiring it to now have a verify function that gets called to verify (hence the name) a transaction.

Contract Class Name

The class name of the contract has been included here:

companion object {
    @JvmStatic
    val IOU_CONTRACT_ID = "net.corda.contracts.IOUContract"
}


This is used in other parts of Corda when the reflection is required. R3’s training materials have done it this way, but I personally think it’s a bit funky and should be done differently.

companion object {
    @JvmStatic
    val IOU_CONTRACT_ID = IOUContract::class.qualifiedName!!
}


That’s a bit better in my opinion. This solution removes the need to change the string’s value if the class is moved or renamed. That being said, Corda has followed the convention of using strings in their code, so if you need to use the inbuilt contracts, you can expect them to follow this format. I’ll leave it up to you to decide which one you prefer.

Commands

Now, we can talk about the commands that I briefly mentioned earlier on in this section. I have put them below again so you don’t need to scroll again:

interface Commands : CommandData {
    class Issue : TypeOnlyCommandData(), Commands
    class Transfer : TypeOnlyCommandData(), Commands
    class Settle : TypeOnlyCommandData(), Commands
}


These commands are used to specify the intention of the transaction. It has been put here due to it’s connection to the contract, since they determine what conditions must be validated. That being said, you could put these commands somewhere else if you wanted, such as in its own file or outside of the contract class but within the same file. As long as your solution best describes your intention, then you are probably going in the correct direction.

Since these commands are simple and are only used to specify intent, TypeOnlyCommandData is extended. Other abstract classes are available that specify commands that we might want to use, such as MoveCommand.

We will see how to use the commands in the next section.

Implementing Verify

Here’s where most of the magic happens, the code has been copied and pasted below:

override fun verify(tx: LedgerTransaction) {
    val command = tx.commands.requireSingleCommand<Commands>()
    when (command.value) {
        is Commands.Issue -> requireThat {
            "No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
            "Only one output state should be created when issuing an IOU." using (tx.outputs.size == 1)
            val iou = tx.outputStates.single() as IOUState
            "A newly issued IOU must have a positive amount." using (iou.amount.quantity > 0)
            "The lender and borrower cannot have the same identity." using (iou.borrower != iou.lender)
            "Both lender and borrower together only may sign IOU issue transaction." using
                    (command.signers.toSet() == iou.participants.map { it.owningKey }.toSet())
        }
        is Commands.Transfer -> requireThat {
            // more conditions
        }
        is Commands.Settle -> {
            // more conditions
        }
    }
}


The verify function checks whether the proposed transaction is valid. If so, the transaction will continue forward and most likely be signed and committed to the ledger but if any of the conditions are not met an IllegalArgumentException is thrown thus likely leading to the termination of the proposed transaction.

Exceptions are generally how Corda deals with unmet requirements. When an exception is thrown, assuming nothing is trying to catch it, execution is terminated and propagated up until it is caught or it ends up in the console output. Using this, it provides a simple way to control the flow of the transaction since it can be stopped anywhere in its execution, even on the counterparty’s node, as the exception will be passed back to the initiator.

Onto the verification code itself. The command that the transaction is executing on its states is retrieved, and depending on the type, different checks are made to check the validity of the transaction. The requireThat DSL that Corda provides allows you to write a condition that must be true to continue along an error message that is output if the condition is false.

Let’s look at one of the requireThat statements a bit more closely:

val iou = tx.outputStates.single() as IOUState
"A newly issued IOU must have a positive amount." using (iou.amount.quantity > 0)


There's not much to explain here. The DSL takes care of the intent of the statement. What I will point out is the syntax:

<string message of what condition should be met> using <condition it must pass>


This is quite simple — a point that stupidly caught me out a bit. If the condition contains spaces in it, then it must be contained within brackets. Finally, the DSL can contain code that is not in a condition expression, allowing you to initialize variables that can then be used in the actual conditions.

That’s enough of contracts for now. They will pop up again in the next section when we put the IOUContract into action.

That's it for this installment on developing with Corda. Today, we focused on developing contracts and states. In our next installment, we will look at developing flows in Corda. Stay tuned!

Topics:
security ,corda ,dlt ,distributed ledger ,blockchain ,cybersecurity

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}