Rao Zoraiz Mahmood
Rao Zoraiz Mahmood

Block8 Software Engineer

08.09.2020. in Technology, Solutions

Block8 Deep Dive Series: R3’s Corda vs Digital Asset’s DAML

Part One: Overview of State and Transactions

Distributed Ledger Technology (DLT) in the enterprise space has seen a number of new players in recent years, providing greater variety for businesses to select an appropriate technology for their needs. Often, these choices are not made on the basis of the merits of the technology alone, but rather on existing business relationships and, sometimes, hype.

The most well-known DLT technologies available for enterprise solutions today include Hyperledger Besu (an Ethereum client, previously Pantheon), R3’s Corda, Digital Asset’s DAML and Hyperledger Fabric. In each part of the Block8 Rates series, we will attempt to provide a direct comparison between two players in the scene against several key metrics.

In particular, we’ll be taking a look at writing smart contracts in R3’s Corda compared to Digital Asset’s DAML. This means the underlying ledger used is not the primary focus of this blog.

Other parts in this series:

Part Two - DevEx: Documentation, Ease of Development and Testability

Part Three - Functionality: What can it do?

Part Four - Code Compare: A Multisignature Transaction

DOWNLOAD THE FULL SERIES HERE

To appreciate the nuances of these distributed ledger technologies, we shall begin by briefly demonstrating how one might use these technologies by taking a look at how they manage state and state mutations (transactions). We will not be going into full implementation detail, however, code snippets will be shown so a user who is not familiar with the technology may get a better understanding. If you are familiar with the implementation of both technologies, you may skip ahead to Part Two.

State

We shall model the same state in both Corda and DAML. The state we shall represent is known as an IOU. An IOU has three very simple properties - An Issuer, an Owner and a Value. A rudimentary parallel would be the Reserve Bank creating money (Issuer) and providing it to ComBankA (Owner). The ComBankA can then transfer ownership of the IOU to one of its clients, in which case the Owner is changed, but the Issuer and Value remain the same. The code snippets below are missing several features and are part of larger code segments to hide complexity and aid in understanding. Furthermore, Corda provides us the option to write code in both Kotlin and Java, which the documentation reflects. Hence for our demonstration, we will be using Java over Kotlin due to its greater mainstream adoption.

Corda

A state in Corda is simply modelled as a Java class. Creating instances of the class (and a few extra steps) is the process by which states are put onto the ledger.


  public class IOUState implements ContractState {
    private int value;  
    private AbstractParty issuer;
    private AbstractParty owner;

    public IOUState(int value, AbstractParty issuer, AbstractParty owner) {  
          this.value = value;  
          this.issuer = issuer;  
          this.owner = owner;  
    }  

      public int getValue() {  
          return value;  
    }  

      public AbstractParty getIssuer() {  
          return issuer;  
    }  

      public AbstractParty getOwner() {  
          return owner;  
    } 
      @Override
      public  List getParticipants() {
          return  Arrays.asList(issuer, owner);
      } 
  }

  

As you can see, the code is relatively simple. We have an IOUState class which implements the ContractState interface. This is required for all states modelled in Corda. The interface requires us to implement the getParticipants() method, which defines who is able to see the state. In this case, we set the participants of any IOUState to be the issuer and owner of that IOUState. An IOUState has a constructor which takes a value, an issuer and an owner. The issuer and owner are stored as AbstractParty, which is just a way of representing parties in Corda. We also note that the Kotlin variant is more succinct, not requiring explicit getters for all parameters, as shown below.


class IOUState(val value: Int,
              val issuer: Party,
              val owner: Party) : ContractState {
   override val participants get() = listOf(issuer, owner)
}
  

DAML

DAML is a programming language inspired by Haskell and built specifically for DLT platforms. Where Corda uses classes, DAML uses templates. A template is a built-in DAML type which can be thought of as a class without Object Oriented (OO) constructs such as interfaces, abstract classes and inheritance. This may lead you to believe that Corda is superior in modelling due to the power of Java’s OO, however, Corda currently uses a custom serializer which will only construct objects using their constructors, limiting the ability to take full advantage of OO practices. Both classes and templates define a base structure from which we can create instances of objects from a constructor.


template Iou
	with
		issuer :  Party
		owner :  Party
		value :  Decimal
	where
	      signatory issuer, owner
  

An instance of Iou would be instantiated similar to as we would with Java constructors, however, we do not actually need to define a constructor as DAML will assume that the parameters define the constructor. As you can see, we store the same parameters issuer, owner, and value. Corda states need to implement the getParticipants() method, which is how we determine who can see the state. In DAML, this is done through the required signatory field. As with the Corda example, we set this to be issuer and owner.

Transactions

When it comes to implementation, transactions are where we really start to see the divergence between Corda and DAML. Both Corda and DAML are designed in such a way that transactions can consume some previous state to create future states. We can also have transactions that do not consume some previous state.

Corda

In Corda, there are three types of files which we need to account for - States, Flows and Contracts. We have already looked at State files in the IOUState example. The next type is Flow files.

Flows

Flows are how we create and mutate shared state. For any state to exist on the ledger, it must go through a flow. Say for example, we are the Reserve Bank (Issuer) and we wish to create an IOU and give it to CommercialBankA (Owner) of value 50,000. This could be initiated by making an API call to the Corda node. Let’s take a look at the code in Java.


@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic {
    private final Integer iouValue;
    private final Party owner;
    private final Party issuer;

    public IOUFlow(Party issuer, Integer iouValue, Party owner) {
        this.iouValue = iouValue;
        this.owner = owner;
        this.issuer = issuer;
    }

    @Suspendable
    @Override
    public SignedTransaction call() throws FlowException {
        // We retrieve the notary identity from the network map.
        Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
        // We create the transaction components.
        IOUState outputState = new IOUState(iouValue, issuer, owner);
        List requiredSigners = Arrays.asList(issuer.getOwningKey(), owner.getOwningKey());
        Command command = new Command<>(new IOUContract.Commands.Create(), requiredSigners);

        // We create a transaction builder and add the components.
        TransactionBuilder txBuilder = new TransactionBuilder(notary)
                .addOutputState(outputState, IOUContract.ID)
                .addCommand(command);

        // Signing the transaction.
        SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);

        // Creating a session with the other party.
        FlowSession otherPartySession = initiateFlow(owner);

        // Obtaining the counterparty's signature.
        SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
                signedTx, Arrays.asList(otherPartySession)));

        // Finalising the transaction.
        return subFlow(new FinalityFlow(fullySignedTx, otherPartySession));
    }
}

  

We won’t be diving into the complete explanation of this snippet, but there are a few overarching concepts we need to understand from this code segment. Firstly, we can see that there exists a constructor:



public  IOUFlow(Party issuer, Integer iouValue, Party owner)

  

Calling this constructor is all we actually need to initiate the IOUFlow and can be done via an API call once the full implementation with RPC is completed. Once the constructor is used, it kicks off:



public SignedTransaction call()

  

In this function, we build the transaction by creating the IOUState we desire, as shown in the line:



IOUState outputState =  new  IOUState(iouValue, issuer, owner);

  

We then add the outputState to a TransactionBuilder object, along with several parameters. The TransactionBuilder generally requires a notary, a command and an input or output state (commands shall be covered in a later section):


TransactionBuilder txBuilder  =  new TransactionBuilder(notary)
	.addOutputState(outputState, IOUContract.ID)
	.addCommand(command) ;

  

The TransactionBuilder can be considered an object that carries the input states, output states, information about signatories and more. First we sign ourselves, where upon signing, the TransactionBuilder object becomes immutable. Afterwards, we send it to the owner for signing. Signing in this case means that a party agrees to the proposed transaction. In this case, the Issuer (Reserve Bank) and Owner (ComBankA) must sign. The owner may implement their own validation logic for a transaction, which results in them either signing or not signing a transaction. The owner would codify their validation logic in another class which they host, where the class would be associated with the appropriate CordApp through configuration files. The validation logic could check the owner, the value and more, however, we will not delve into this yet.

Communication between nodes only occurs within the context of these flows, which is how Corda ensures point to point privacy. There also exists built in flows used to automate common tasks. Flows enable users to write their own personalised logic for any state update that requires their signature. This codified acceptance criteria is what enables the automation provided by Corda.

It is important to note that anyone can create their own flow with any state. This raises the concern, what if someone just issues themselves an IOU with an IOU value of $1million without repercussion? This is where we get into Contract classes.

Contracts

In Corda, every state class we create must have an associated Contract class.

For the IOUState class, we can call it IOUContract. This is the general naming convention for Contracts in Corda but is not required. The Contract class cannot be changed after creation and defines the rules by which the State must play in order for it to be a valid state mutation. This is not where transactions are executed. The transactions are executed in the Flow classes. When someone signs a transaction in the flow, the Contract classes for the states involved are automatically checked.


public class IOUContract implements Contract {

    public static class Commands implements CommandData {
        public static class Create extends Commands{}

        public static class Transfer extends Commands{}
    }

    @Override
    public void verify(LedgerTransaction tx) {
        final CommandWithParties command = requireSingleCommand(tx.getCommands(), Commands.class);
        if (command.getValue() instanceof IOUContract.Commands.Create) {

            // Constraints on the shape of the transaction.
            if (!tx.getInputs().isEmpty())
                throw new IllegalArgumentException("No inputs should be consumed when issuing an IOU.");
            if (!(tx.getOutputs().size() == 1))
                throw new IllegalArgumentException("There should be one output state of type IOUState.");

            // IOU-specific constraints.
            final IOUState output = tx.outputsOfType(IOUState.class).get(0);
            final Party lender = (Party) output.getIssuer();
            final Party borrower = (Party) output.getOwner();
            if (output.getValue() <= 0)
                throw new IllegalArgumentException("The IOU's value must be non-negative.");
            //Signatory Specific constraints
            //Removed for simplicity 
        }
        else if (command.getValue() instanceof IOUContract.Commands.Transfer){
            System.out.println("Checking if IOUState follows rules for Transfer Command");
            //TODO
        }else{
            throw new IllegalArgumentException("Command is not of type Transfer or Create!");
        }
    }
}

  

Every Contract must implement the method:


public  void  verify(LedgerTransaction tx)
  

In this method, we check if the transaction which was executed was a “Create” Command or a “Transfer” Command:


if (command.getValue() instanceof IOUContract.Commands.Create){
    ......
 else if (command.getValue() instanceof IOUContract.Commands.Transfer){  
	......
}else{  
    throw new IllegalArgumentException("Command is not of type Transfer or Create!");  
}
  

Commands are a way for us to associate different state mutations with a single state type. We can add as many commands as we like to the Contract, currently we have “Create” and “Transfer”, but we could also add more such as “Destroy”. We pass the Command information when we actually perform a transaction.

If the Command is “Create”, we check if the transaction has 0 inputs and 1 output:


if (!tx.getInputs().isEmpty())  
    throw new IllegalArgumentException("No inputs should be consumed when issuing an IOU.");  
if (!(tx.getOutputs().size() == 1))  
    throw new IllegalArgumentException("There should be one output state of type IOUState.");
  

We also ensure the value of the IOUState is greater than 0:


if (output.getValue() <= 0)  
    throw new IllegalArgumentException("The IOU's value must be non-negative.");
 

Finally, we check that the expected signatories have signed:


if (requiredSigners.size() != 2)  
    throw new IllegalArgumentException("There must be two signers.");  
if (!(requiredSigners.containsAll(expectedSigners)))  
    throw new IllegalArgumentException("The borrower and lender must be signers.");
 

If the Command is “Transfer”, we may expect 1 input state, and 1 output state, and we would add this logic to the contract. For a “Destroy” Command, we may expect 1 input state and 0 output states. We may add any number of Commands with any variation of verification logic. It is worth knowing that we could leave the verify function empty and it would not perform any validation for transactions, however, this would leave the ledger open to accepting any state which is not secure.

 

IOU Workflow in Corda

 

DAML

In Corda, state mutations occur by executing Flows, where the state mutations in the flows are validated by the Contracts. In DAML, there are two ways that state mutation occurs. The first is by creating an object (instance) from a template, where no previous state is required and the second is by executing a method on such an object, which in DAML is called “exercising a choice”.

The reason we don’t need Flows and Contracts is because methods in DAML encapsulate the function of Flows and Contracts in Corda. Every method in DAML is immutable and defined when the template is written. In Corda, only Contracts are immutable and any user may create their own Flows.

Recall the Iou template we showed earlier. Here is a larger piece of the code:


template Iou
  with
    issuer : Party
    owner : Party
    value : Decimal
  where
-- Like asserts in C, postconditions required for the creation of of Iou
    ensure value > 0.0

-- Signatory says that Iou can only be created if it is authorized by both issuer and owner
-- These are the parties whose authority is required to create the contract or archive it again
    signatory issuer, owner  
    
-- This defines what the owner has the right to do
    controller owner can

      Iou_Transfer : ContractId IouTransfer
        with
          newOwner : Party
        do create IouTransfer with 
            iou = this
            newOwner = newOwner

There are a few new things to note. The first of which is:


ensure value > 0.0

This serves a similar purpose to that of the Contract class we showcased in Corda. We can use ensure to set post-conditions on the values of an Iou object.

We also specify who must sign the transaction for it to be valid:


signatory  issuer,  owner
  

In Corda, the required signatories would be specified in the Contract class.

The next thing we see is:


   controller owner can

      Iou_Transfer : ContractId IouTransfer
        with
          newOwner : Party
        do create IouTransfer with 
            iou = this
            newOwner = newOwner
  

This specifies permissions, stating that only the owner can execute the Iou_Transfer method. This method takes newOwner of type Party as an argument and results in an IouTransfer being created. A Party is just a representation of a user. Taking a look at the IouTransfer template:


template IouTransfer
  with
    iou : Iou
    newOwner : Party
  where
    signatory iou.issuer, iou.owner
    
    controller iou.owner can
      IouTransfer_Cancel : IouCid
        do create iou

    controller newOwner can
      IouTransfer_Reject : IouCid
        do create iou

      IouTransfer_Accept : IouCid
        do
          create iou with
            owner = newOwner
            observers = []
  

Let’s take a moment to consider what’s happening before we dive into the code. The first state we had was Iou and we executed a choice (ie. method) which resulted in a new state: IouTransfer. The old Iou state was consumed, as the default implementation is that the base state is consumed when a choice is executed. Now we have an IouTransfer state. This state represents a transition state, where we are waiting to be transferred to a new owner, contingent on the new owner executing the IouTransfer_Accept method. Any other action will result in this transition state effectively reverting back to its original Iou state by a new Iou with the same parameters replacing the old Iou.

Let’s make a comparison with our rudimentary real world example. Let’s say the ReserveBank wanted to issue an Iou of value 1,000 to CommericalBankA. The first thing the ReserveBank could do is create an Iou state with issuer=ReserveBank, owner=ReserveBank, value=1000. You may wonder, why can’t we just start off with the owner=CommercialBankA. We can’t have a party be a signatory without their permission, and since the Iou template, states signatory issuer, owner, a direct creation is not possible. Adding signatories is usually done by letting the potential signatory execute a method which would add them as signatory (in this case the IouTransfer_Accept method).

In Corda, we would model this by requiring that a state has the respective signatories, as specified in a Contract class, before the state can be instantiated. The signatories could then sign transactions during the Corda flow.

Now that the reserve bank has an Iou with issuer=ReserveBank, owner=ReserveBank, value=1000, he can execute the Iou_Transfer method with argument newOwner=CommercialBankA. The old Iou state is consumed.

Now we have an IouTransfer state and newOwner can execute the IouTransfer_Accept method, resulting in a new Iou state with him as the owner. The old IouTransfer state is consumed in the process. By executing the IouTransfer_Accept method, he acknowledges himself as a signatory for all subsequent states, which in this case is a state of type Iou. It is worth noting that there are multiple ways to achieve the same outcome, and there exist more efficient ways than the steps mentioned above, for example, the ReserveBank could directly create an IouTransfer, however, the steps have been expanded for clarity. A diagram summarising the aforementioned steps can be found below.

 

enter image description here

 

We have now briefly covered how one might model states and transactions in Corda and DAML!

In the next part of this series, we will review the developer experience of Corda versus DAML, including documentation, ease of development and testability.

New call-to-action

About the Author: 

ZoraizZoraiz Mahmood is a Software Engineer at Block8, working on emerging technology.
His current research and development interest focuses on protocol development for distributed systems (blockchain), and is currently writing a paper on the Performance Comparison of Blockchain Data Storage Models.

Zoraiz regularly creates blockchain technical and related content, having written multiple technical articles, hosted A and produced YouTube videos.

When not generating value, Zoraiz can be found out and about exploring the Australian landscape.

 

Subscribe to our Newsletter

Get the latest news on our products or learn what's happening in our guilds.