Block8

27.01.2020. in DLT, daml, Blockchain, Guilds, Developers, corda

R3’s Corda vs Digital Asset’s DAML Part One

Blockchain and 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 this series, we take a look at R3’s Corda and Digital Asset’s DAML.

Other parts in this series: (links will be up soon)

  • Part Two - Developer Experience: Learnability and Documentation
  • Part Three - Functionality: What can it do?
  • Part Four - Code Compare: A Multisignature Transaction

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.

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<AbstractParty> 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.

DAML

DAML is a programming language inspired by Haskell, and so is quite different to the Corda implementation which uses Java or Kotlin and targets a modified version of the JVM to make it deterministic. 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
participants: [Party]
where
observer participants

 

Transactions

When it comes to implementation, transactions are where we really start to see the divergence between the 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 actually 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

@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic<SignedTransaction> {
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<PublicKey> 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));
}
}

 

<code>public  IOUFlow(Party issuer, Integer iouValue, Party owner)</code>

 

<code>public SignedTransaction call()</code>

 

<code>IOUState outputState =  new  IOUState(iouValue, issuer, owner);</code>

 

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

 

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

 

<code>public  void  verify(LedgerTransaction tx)</code>

 

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

 

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.");

 

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

 

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.");

 

IOU workflow in Corda diagram

 

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.

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

 

<code>ensure value > 0.0</code>

 

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

<code>signatory  issuer,  owner</code>

 

The next thing we see is:

controller owner can

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

 

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 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 would 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. A diagram summarising this can be found below.

IOU workflow in DAML diagram

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.

Authors

Rao Zoraiz Mahood
Software Engineer, Block8

Samuel Brooks
Chief Technology Officer, Block8

Subscribe to our Newsletter

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