Hello, World! CorDapp tutorial

This commit is contained in:
Joel Dudley 2017-06-16 14:05:52 +01:00 committed by GitHub
parent bfd02f5d78
commit 73fbcea0e3
12 changed files with 2290 additions and 0 deletions

View File

@ -0,0 +1,738 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Writing the contract
====================
In Corda, the ledger is updated via transactions. Each transaction is a proposal to mark zero or more existing
states as historic (the inputs), while creating zero or more new states (the outputs).
It's easy to imagine that most CorDapps will want to impose some constraints on how their states evolve over time:
* A cash CorDapp would not want to allow users to create transactions that generate money out of thin air (at least
without the involvement of a central bank or commercial bank)
* A loan CorDapp might not want to allow the creation of negative-valued loans
* An asset-trading CorDapp would not want to allow users to finalise a trade without the agreement of their counterparty
In Corda, we impose constraints on what transactions are allowed using contracts. These contracts are very different
to the smart contracts of other distributed ledger platforms. They do not represent the current state of the ledger.
Instead, like a real-world contract, they simply impose rules on what kinds of agreements are allowed.
Every state is associated with a contract. A transaction is invalid if it does not satisfy the contract of every
input and output state in the transaction.
The Contract interface
----------------------
Just as every Corda state must implement the ``ContractState`` interface, every contract must implement the
``Contract`` interface:
.. container:: codeset
.. code-block:: kotlin
interface Contract {
// Implements the contract constraints in code.
@Throws(IllegalArgumentException::class)
fun verify(tx: TransactionForContract)
// Expresses the contract constraints as legal prose.
val legalContractReference: SecureHash
}
A few more Kotlinisms here:
* ``fun`` declares a function
* The syntax ``fun funName(arg1Name: arg1Type): returnType`` declares that ``funName`` takes an argument of type
``arg1Type`` and returns a value of type ``returnType``
We can see that ``Contract`` expresses its constraints in two ways:
* In legal prose, through a hash referencing a legal contract that expresses the contract's constraints in legal prose
* In code, through a ``verify`` function that takes a transaction as input, and:
* Throws an ``IllegalArgumentException`` if it rejects the transaction proposal
* Returns silently if it accepts the transaction proposal
Controlling IOU evolution
-------------------------
What would a good contract for an ``IOUState`` look like? There is no right or wrong answer - it depends on how you
want your CorDapp to behave.
For our CorDapp, let's impose the constraint that we only want to allow the creation of IOUs. We don't want nodes to
transfer them or redeem them for cash. One way to enforce this behaviour would be by imposing the following constraints:
* A transaction involving IOUs must consume zero inputs, and create one output of type ``IOUState``
* The transaction should also include a ``Create`` command, indicating the transaction's intent (more on commands
shortly)
* For the transactions's output IOU state:
* Its value must be non-negative
* Its sender and its recipient cannot be the same entity
* All the participants (i.e. both the sender and the recipient) must sign the transaction
We can picture this transaction as follows:
.. image:: resources/tutorial-transaction.png
:scale: 15%
:align: center
Let's write a contract that enforces these constraints. We'll do this by modifying either ``TemplateContract.java`` or
``TemplateContract.kt`` and updating ``TemplateContract`` to define an ``IOUContract``.
Defining IOUContract
--------------------
The Create command
^^^^^^^^^^^^^^^^^^
The first thing our contract needs is a *command*. Commands serve two purposes:
* They indicate the transaction's intent, allowing us to perform different verification given the situation
* For example, a transaction proposing the creation of an IOU could have to satisfy different constraints to one
redeeming an IOU
* They allow us to define the required signers for the transaction
* For example, IOU creation might require signatures from both the sender and the recipient, whereas the transfer
of an IOU might only require a signature from the IOUs current holder
Let's update the definition of ``TemplateContract`` (in ``TemplateContract.java`` or ``TemplateContract.kt``) to
define an ``IOUContract`` with a ``Create`` command:
.. container:: codeset
.. code-block:: kotlin
package com.template
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.sha256
open class IOUContract : Contract {
// Currently, verify() does no checking at all!
override fun verify(tx: TransactionForContract) {}
// Our Create command.
class Create : CommandData
// The legal contract reference - we'll leave this as a dummy hash for now.
override val legalContractReference = SecureHash.sha256("Prose contract.")
}
.. code-block:: java
package com.template;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.Contract;
import net.corda.core.crypto.SecureHash;
public class IOUContract implements Contract {
@Override
// Currently, verify() does no checking at all!
public void verify(TransactionForContract tx) {}
// Our Create command.
public static class Create implements CommandData {}
// The legal contract reference - we'll leave this as a dummy hash for now.
private final SecureHash legalContractReference = SecureHash.sha256("Prose contract.");
@Override public final SecureHash getLegalContractReference() { return legalContractReference; }
}
Aside from renaming ``TemplateContract`` to ``IOUContract``, we've also implemented the ``Create`` command. All
commands must implement the ``CommandData`` interface.
The ``CommandData`` interface is a simple marker interface for commands. In fact, its declaration is only two words
long (in Kotlin, interfaces do not require a body):
.. container:: codeset
.. code-block:: kotlin
interface CommandData
The verify logic
^^^^^^^^^^^^^^^^
We now need to define the actual contract constraints. For our IOU CorDapp, we won't concern ourselves with writing
valid legal prose to enforce the IOU agreement in court. Instead, we'll focus on implementing ``verify``.
Remember that our goal in writing the ``verify`` function is to write a function that, given a transaction:
* Throws an ``IllegalArgumentException`` if the transaction is considered invalid
* Does **not** throw an exception if the transaction is considered valid
In deciding whether the transaction is valid, the ``verify`` function only has access to the contents of the
transaction:
* ``tx.inputs``, which lists the inputs
* ``tx.outputs``, which lists the outputs
* ``tx.commands``, which lists the commands and their associated signers
Although we won't use them here, the ``verify`` function also has access to the transaction's attachments,
time-windows, notary and hash.
Based on the constraints enumerated above, we'll write a ``verify`` function that rejects a transaction if any of the
following are true:
* The transaction doesn't include a ``Create`` command
* The transaction has inputs
* The transaction doesn't have exactly one output
* The IOU itself is invalid
* The transaction doesn't require signatures from both the sender and the recipient
Let's work through these constraints one-by-one.
Command constraints
~~~~~~~~~~~~~~~~~~~
To test for the presence of the ``Create`` command, we can use Corda's ``requireSingleCommand`` function:
.. container:: codeset
.. code-block:: kotlin
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<Create>()
}
.. code-block:: java
// Additional imports.
import net.corda.core.contracts.AuthenticatedObject;
import net.corda.core.contracts.TransactionForContract;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
...
@Override
public void verify(TransactionForContract tx) {
final AuthenticatedObject<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
}
Here, ``requireSingleCommand`` performing a dual purpose:
* It's asserting that there is exactly one ``Create`` command in the transaction
* It's extracting the command and returning it
If the ``Create`` command isn't present, or if the transaction has multiple ``Create`` commands, contract
verification will fail.
Transaction constraints
~~~~~~~~~~~~~~~~~~~~~~~
We also wanted our transaction to have no inputs and only a single output. One way to impose this constraint is as
follows:
.. container:: codeset
.. code-block:: kotlin
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"Only one output state should be created." using (tx.outputs.size == 1)
}
}
.. code-block:: java
// Additional import.
import static net.corda.core.contracts.ContractsDSL.requireThat;
...
@Override
public void verify(TransactionForContract tx) {
final AuthenticatedObject<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("Only one output state should be created.", tx.getOutputs().size() == 1);
return null;
});
}
Note the use of Corda's built-in ``requireThat`` function. ``requireThat`` provides a terse way to write the following:
* If the condition on the right-hand side doesn't evaluate to true...
* ...throw an ``IllegalArgumentException`` with the message on the left-hand side
As before, the act of throwing this exception would cause transaction verification to fail.
IOU constraints
~~~~~~~~~~~~~~~
We want to impose two constraints on the ``IOUState`` itself:
* Its value must be non-negative
* Its sender and its recipient cannot be the same entity
We can impose these constraints in the same ``requireThat`` block as before:
.. container:: codeset
.. code-block:: kotlin
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"Only one output state should be created." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputs.single() as IOUState
"The IOU's value must be non-negative." using (out.value > 0)
"The sender and the recipient cannot be the same entity." using (out.sender != out.recipient)
}
}
.. code-block:: java
@Override
public void verify(TransactionForContract tx) {
final AuthenticatedObject<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("Only one output state should be created.", tx.getOutputs().size() == 1);
// IOU-specific constraints.
final IOUState out = (IOUState) tx.getOutputs().get(0);
check.using("The IOU's value must be non-negative.",out.getValue() > 0);
check.using("The sender and the recipient cannot be the same entity.", out.getSender() != out.getRecipient());
return null;
});
}
You can see that we're not restricted to only writing constraints in the ``requireThat`` block. We can also write
other statements - in this case, we're extracting the transaction's single ``IOUState`` and assigning it to a variable.
Signer constraints
~~~~~~~~~~~~~~~~~~
Our final constraint is that the required signers on the transaction are the sender and the recipient only. A
transaction's required signers is equal to the union of all the signers listed on the commands. We can therefore
extract the signers from the ``Create`` command we retrieved earlier.
.. container:: codeset
.. code-block:: kotlin
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"Only one output state should be created." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputs.single() as IOUState
"The IOU's value must be non-negative." using (out.value > 0)
"The sender and the recipient cannot be the same entity." using (out.sender != out.recipient)
// Constraints on the signers.
"All of the participants must be signers." using (command.signers.toSet() == out.participants.map { it.owningKey }.toSet())
}
}
.. code-block:: java
// Additional imports.
import com.google.common.collect.ImmutableList;
import java.security.PublicKey;
import java.util.List;
...
@Override
public void verify(TransactionForContract tx) {
final AuthenticatedObject<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("Only one output state should be created.", tx.getOutputs().size() == 1);
// IOU-specific constraints.
final IOUState out = (IOUState) tx.getOutputs().get(0);
final Party sender = out.getSender();
final Party recipient = out.getRecipient();
check.using("The IOU's value must be non-negative.",out.getValue() > 0);
check.using("The sender and the recipient cannot be the same entity.", out.getSender() != out.getRecipient());
// Constraints on the signers.
final Set<PublicKey> requiredSigners = Sets.newHashSet(sender.getOwningKey(), recipient.getOwningKey());
final Set<PublicKey> signerSet = Sets.newHashSet(command.getSigners());
check.using("All of the participants must be signers.", (signerSet.equals(requiredSigners)));
return null;
});
}
Checkpoint
----------
We've now defined the full contract logic of our ``IOUContract``. This contract means that transactions involving
``IOUState`` states will have to fulfill strict constraints to become valid ledger updates.
Before we move on, let's go back and modify ``IOUState`` to point to the new ``IOUContract``:
.. container:: codeset
.. code-block:: kotlin
class IOUState(val value: Int,
val sender: Party,
val recipient: Party) : ContractState {
override val contract: IOUContract = IOUContract()
override val participants get() = listOf(sender, recipient)
}
.. code-block:: java
public class IOUState implements ContractState {
private final Integer value;
private final Party sender;
private final Party recipient;
private final IOUContract contract = new IOUContract();
public IOUState(Integer value, Party sender, Party recipient) {
this.value = value;
this.sender = sender;
this.recipient = recipient;
}
public Integer getValue() {
return value;
}
public Party getSender() {
return sender;
}
public Party getRecipient() {
return recipient;
}
@Override
public IOUContract getContract() {
return contract;
}
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(sender, recipient);
}
}
Transaction tests
-----------------
How can we ensure that we've defined our contract constraints correctly?
One option would be to deploy the CorDapp onto a set of nodes, and test it manually. However, this is a relatively
slow process, and would take on the order of minutes to test each change.
Instead, we can test our contract logic using Corda's ``ledgerDSL`` transaction-testing framework. This will allow us
to test our contract without the overhead of spinning up a set of nodes.
Open either ``test/kotlin/com/template/contract/ContractTests.kt`` or
``test/java/com/template/contract/ContractTests.java``, and add the following as our first test:
.. container:: codeset
.. code-block:: kotlin
package com.template
import net.corda.testing.*
import org.junit.Test
class IOUTransactionTests {
@Test
fun `transaction must include Create command`() {
ledger {
transaction {
output { IOUState(1, MINI_CORP, MEGA_CORP) }
fails()
command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { IOUContract.Create() }
verifies()
}
}
}
}
.. code-block:: java
package com.template;
import net.corda.core.identity.Party;
import org.junit.Test;
import java.security.PublicKey;
import static net.corda.testing.CoreTestUtils.*;
public class IOUTransactionTests {
static private final Party miniCorp = getMINI_CORP();
static private final Party megaCorp = getMEGA_CORP();
static private final PublicKey[] keys = new PublicKey[2];
{
keys[0] = getMEGA_CORP_PUBKEY();
keys[1] = getMINI_CORP_PUBKEY();
}
@Test
public void transactionMustIncludeCreateCommand() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(1, miniCorp, megaCorp));
txDSL.fails();
txDSL.command(keys, IOUContract.Create::new);
txDSL.verifies();
return null;
});
return null;
});
}
}
This test uses Corda's built-in ``ledgerDSL`` to:
* Create a fake transaction
* Add inputs, outputs, commands, etc. (using the DSL's ``output``, ``input`` and ``command`` methods)
* At any point, asserting that the transaction built so far is either contractually valid (by calling ``verifies``) or
contractually invalid (by calling ``fails``)
In this instance:
* We initially create a transaction with an output but no command
* We assert that this transaction is invalid (since the ``Create`` command is missing)
* We then add the ``Create`` command
* We assert that transaction is now valid
Here is the full set of tests we'll be using to test the ``IOUContract``:
.. container:: codeset
.. code-block:: kotlin
class IOUTransactionTests {
@Test
fun `transaction must include Create command`() {
ledger {
transaction {
output { IOUState(1, MINI_CORP, MEGA_CORP) }
fails()
command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { IOUContract.Create() }
verifies()
}
}
}
@Test
fun `transaction must have no inputs`() {
ledger {
transaction {
input { IOUState(1, MINI_CORP, MEGA_CORP) }
output { IOUState(1, MINI_CORP, MEGA_CORP) }
command(MEGA_CORP_PUBKEY) { IOUContract.Create() }
`fails with`("No inputs should be consumed when issuing an IOU.")
}
}
}
@Test
fun `transaction must have one output`() {
ledger {
transaction {
output { IOUState(1, MINI_CORP, MEGA_CORP) }
output { IOUState(1, MINI_CORP, MEGA_CORP) }
command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { IOUContract.Create() }
`fails with`("Only one output state should be created.")
}
}
}
@Test
fun `sender must sign transaction`() {
ledger {
transaction {
output { IOUState(1, MINI_CORP, MEGA_CORP) }
command(MINI_CORP_PUBKEY) { IOUContract.Create() }
`fails with`("All of the participants must be signers.")
}
}
}
@Test
fun `recipient must sign transaction`() {
ledger {
transaction {
output { IOUState(1, MINI_CORP, MEGA_CORP) }
command(MEGA_CORP_PUBKEY) { IOUContract.Create() }
`fails with`("All of the participants must be signers.")
}
}
}
@Test
fun `sender is not recipient`() {
ledger {
transaction {
output { IOUState(1, MEGA_CORP, MEGA_CORP) }
command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { IOUContract.Create() }
`fails with`("The sender and the recipient cannot be the same entity.")
}
}
}
@Test
fun `cannot create negative-value IOUs`() {
ledger {
transaction {
output { IOUState(-1, MINI_CORP, MEGA_CORP) }
command(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY) { IOUContract.Create() }
`fails with`("The IOU's value must be non-negative.")
}
}
}
}
.. code-block:: java
public class IOUTransactionTests {
static private final Party miniCorp = getMINI_CORP();
static private final Party megaCorp = getMEGA_CORP();
static private final PublicKey[] keys = new PublicKey[2];
{
keys[0] = getMEGA_CORP_PUBKEY();
keys[1] = getMINI_CORP_PUBKEY();
}
@Test
public void transactionMustIncludeCreateCommand() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(1, miniCorp, megaCorp));
txDSL.fails();
txDSL.command(keys, IOUContract.Create::new);
txDSL.verifies();
return null;
});
return null;
});
}
@Test
public void transactionMustHaveNoInputs() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.input(new IOUState(1, miniCorp, megaCorp));
txDSL.output(new IOUState(1, miniCorp, megaCorp));
txDSL.command(keys, IOUContract.Create::new);
txDSL.failsWith("No inputs should be consumed when issuing an IOU.");
return null;
});
return null;
});
}
@Test
public void transactionMustHaveOneOutput() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(1, miniCorp, megaCorp));
txDSL.output(new IOUState(1, miniCorp, megaCorp));
txDSL.command(keys, IOUContract.Create::new);
txDSL.failsWith("Only one output state should be created.");
return null;
});
return null;
});
}
@Test
public void senderMustSignTransaction() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(1, miniCorp, megaCorp));
PublicKey[] keys = new PublicKey[1];
keys[0] = getMINI_CORP_PUBKEY();
txDSL.command(keys, IOUContract.Create::new);
txDSL.failsWith("All of the participants must be signers.");
return null;
});
return null;
});
}
@Test
public void recipientMustSignTransaction() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(1, miniCorp, megaCorp));
PublicKey[] keys = new PublicKey[1];
keys[0] = getMEGA_CORP_PUBKEY();
txDSL.command(keys, IOUContract.Create::new);
txDSL.failsWith("All of the participants must be signers.");
return null;
});
return null;
});
}
@Test
public void senderIsNotRecipient() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(1, megaCorp, megaCorp));
PublicKey[] keys = new PublicKey[1];
keys[0] = getMEGA_CORP_PUBKEY();
txDSL.command(keys, IOUContract.Create::new);
txDSL.failsWith("The sender and the recipient cannot be the same entity.");
return null;
});
return null;
});
}
@Test
public void cannotCreateNegativeValueIOUs() {
ledger(ledgerDSL -> {
ledgerDSL.transaction(txDSL -> {
txDSL.output(new IOUState(-1, miniCorp, megaCorp));
txDSL.command(keys, IOUContract.Create::new);
txDSL.failsWith("The IOU's value must be non-negative.");
return null;
});
return null;
});
}
}
Copy these tests into the ContractTests file, and run them to ensure that the ``IOUState`` and ``IOUContract`` are
defined correctly. All the tests should pass.
Progress so far
---------------
We've now written an ``IOUContract`` constraining the evolution of each ``IOUState`` over time:
* An ``IOUState`` can only be created, not transferred or redeemed
* Creating an ``IOUState`` requires an issuance transaction with no inputs, a single ``IOUState`` output, and a
``Create`` command
* The ``IOUState`` created by the issuance transaction must have a non-negative value, and its sender and recipient
must be different entities.
The final step in the creation of our CorDapp will be to write the ``IOUFlow`` that will allow nodes to orchestrate
the creation of a new ``IOUState`` on the ledger, while only sharing information on a need-to-know basis.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
Hello, World!
=============
.. toctree::
:maxdepth: 1
hello-world-introduction
hello-world-template
hello-world-state
hello-world-contract
hello-world-flow
hello-world-running

View File

@ -0,0 +1,63 @@
Introduction
============
By this point, :doc:`your dev environment should be set up <getting-set-up>`, you've run
:doc:`your first CorDapp <tutorial-cordapp>`, and you're familiar with Corda's :doc:`key concepts <key-concepts>`. What
comes next?
If you're a developer, the next step is to write your own CorDapp. Each CorDapp takes the form of a plugin that is
installed on one or more Corda nodes, and gives them the ability to conduct some new process - anything from
issuing a debt instrument to making a restaurant booking.
Our use-case
------------
Our CorDapp will seek to model IOUs on ledger. An IOU short for “I Owe yoU” records the fact that one person owes
another a given amount of money. We can imagine that this is potentially sensitive information that we'd only want to
communicate on a need-to-know basis. This is one of the areas where Corda excels - allowing a small set of parties to
agree on a fact without needing to share this fact with everyone else on the network, as you do with most other
blockchain platforms.
To serve any useful function, a CorDapp needs three core elements:
* **One or more states** the shared facts that will be agreed upon and stored on the ledger
* **One or more contracts** the rules governing how these states can evolve over time
* **One or more flows** the step-by-step process for carrying out a ledger update
Our IOU CorDapp is no exception. It will have the following elements:
State
^^^^^
The states will be IOUStates, with each instance representing a single IOU. We can visualize an IOUState as follows:
.. image:: resources/tutorial-state.png
:scale: 25%
:align: center
Contract
^^^^^^^^
Our contract will be the IOUContract, imposing rules on the evolution of IOUs over time:
* Only the creation of new IOUs will be allowed
* Transferring existing IOUs or paying off an IOU with cash will not be allowed
However, we can easily extend our CorDapp to handle additional use-cases later on.
Flow
^^^^
Our flow will be the IOUFlow. It will allow two nodes to orchestrate the creation of a new IOU on the ledger, via the
following steps:
.. image:: resources/tutorial-flow.png
:scale: 25%
:align: center
In traditional distributed ledger systems, where all data is broadcast to every network participant, you dont even
think about this step you simply package up your ledger update and send it out into the world. But in Corda, where
privacy is a core focus, flows are used to carefully control who sees what during the process of agreeing a
ledger update.
Progress so far
---------------
We've sketched out a simple CorDapp that will allow nodes to confidentially agree the creation of new IOUs.
Next, we'll be taking a look at the template project we'll be using as a base for our work.

View File

@ -0,0 +1,208 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Running our CorDapp
===================
Now that we've written a CorDapp, it's time to test it by running it on some real Corda nodes.
Deploying our CorDapp
---------------------
Let's take a look at the nodes we're going to deploy. Open the project's build file under ``java-source/build.gradle``
or ``kotlin-source/build.gradle`` and scroll down to the ``task deployNodes`` section. This section defines four
nodes - the Controller, and NodeA, NodeB and NodeC:
.. container:: codeset
.. code-block:: kotlin
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
directory "./build/nodes"
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=UK"
node {
name "CN=Controller,O=R3,OU=corda,L=London,C=UK"
advertisedServices = ["corda.notary.validating"]
p2pPort 10002
rpcPort 10003
webPort 10004
cordapps = []
}
node {
name "CN=NodeA,O=NodeA,L=London,C=UK"
advertisedServices = []
p2pPort 10005
rpcPort 10006
webPort 10007
cordapps = []
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
node {
name "CN=NodeB,O=NodeB,L=New York,C=US"
advertisedServices = []
p2pPort 10008
rpcPort 10009
webPort 10010
cordapps = []
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
node {
name "CN=NodeC,O=NodeC,L=Paris,C=FR"
advertisedServices = []
p2pPort 10011
rpcPort 10012
webPort 10013
cordapps = []
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
}
We have three standard nodes, plus a special Controller node that is running the network map service, and is also
advertising a validating notary service. Feel free to add additional node definitions here to expand the size of the
test network.
We can run this ``deployNodes`` task using Gradle. For each node definition, Gradle will:
* Package the project's source files into a CorDapp jar
* Create a new node in ``build/nodes`` with our CorDapp already installed
We can do that now by running the following commands from the root of the project:
.. code:: python
// On Windows
gradlew clean deployNodes
// On Mac
./gradlew clean deployNodes
Running the nodes
-----------------
Running ``deployNodes`` will build the nodes under both ``java-source/build/nodes`` and ``kotlin-source/build/nodes``.
If we navigate to one of these folders, we'll see four node folder. Each node folder has the following structure:
.. code:: python
.
// The runnable node
|____corda.jar
// The node's webserver
|____corda-webserver.jar
|____dependencies
// The node's configuration file
|____node.conf
|____plugins
// Our IOU CorDapp
|____java/kotlin-source-0.1.jar
Let's start the nodes by running the following commands from the root of the project:
.. code:: python
// On Windows for a Java CorDapp
java-source/build/nodes/runnodes.bat
// On Windows for a Kotlin CorDapp
kotlin-source/build/nodes/runnodes.bat
// On Mac for a Java CorDapp
java-source/build/nodes/runnodes
// On Mac for a Kotlin CorDapp
kotlin-source/build/nodes/runnodes
This will start a terminal window for each node, and an additional terminal window for each node's webserver - eight
terminal windows in all. Give each node a moment to start - you'll know it's ready when its terminal windows displays
the message, "Welcome to the Corda interactive shell.".
.. image:: resources/running_node.png
:scale: 25%
:align: center
Interacting with the nodes
--------------------------
Now that our nodes are running, let's order one of them to create an IOU by kicking off our ``IOUFlow``. In a larger
app, we'd generally provide a web API sitting on top of our node. Here, for simplicity, we'll be interacting with the
node via its built-in CRaSH shell.
Go to the terminal window displaying the CRaSH shell of Node A. Typing ``help`` will display a list of the available
commands.
We want to create an IOU of 100 with Node B. We start the ``IOUFlow`` by typing:
.. code:: python
start IOUFlow arg0: 99, arg1: "CN=NodeB,O=NodeB,L=New York,C=US"
Node A and Node B will automatically agree an IOU.
If the flow worked, it should have led to the recording of a new IOU in the vaults of both Node A and Node B. Equally
importantly, Node C - although it sits on the same network - should not be aware of this transaction.
We can check the flow has worked by using an RPC operation to check the contents of each node's vault. Typing ``run``
will display a list of the available commands. We can examine the contents of a node's vault by running:
.. code:: python
run vaultAndUpdates
And we can also examine a node's transaction storage, by running:
.. code:: python
run verifiedTransactions
The vaults of Node A and Node B should both display the following output:
.. code:: python
first:
- state:
data:
value: 99
sender: "CN=NodeA,O=NodeA,L=London,C=UK"
recipient: "CN=NodeB,O=NodeB,L=New York,C=US"
contract:
legalContractReference: "559322B95BCF7913E3113962DC3F3CBD71C818C66977721580C045DC41C813A5"
participants:
- "CN=NodeA,O=NodeA,L=London,C=UK"
- "CN=NodeB,O=NodeB,L=New York,C=US"
notary: "CN=Controller,O=R3,OU=corda,L=London,C=UK,OU=corda.notary.validating"
encumbrance: null
ref:
txhash: "656A1BF64D5AEEC6F6C944E287F34EF133336F5FC2C5BFB9A0BFAE25E826125F"
index: 0
second: "(observable)"
But the vault of Node C should output nothing!
.. code:: python
first: []
second: "(observable)"
Conclusion
----------
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Like all CorDapps, our
CorDapp is made up of three key parts:
* The ``IOUState``, representing IOUs on the ledger
* The ``IOUContract``, controlling the evolution of IOUs over time
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger.
Together, these three parts completely determine how IOUs are created and evolved on the ledger.
Next steps
----------
You should now be ready to develop your own CorDapps. There's
`a more fleshed-out version of the IOU CorDapp <https://github.com/corda/cordapp-tutorial>`_
with an API and web front-end, and a set of example CorDapps in
`the main Corda repo <https://github.com/corda/corda>`_, under ``samples``. An explanation of how to run these
samples :doc:`here <running-the-demos>`.
As you write CorDapps, you can learn more about the API available :doc:`here <api>`.
If you get stuck at any point, please reach out on `Slack <https://slack.corda.net/>`_,
`Discourse <https://discourse.corda.net/>`_, or `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_.

View File

@ -0,0 +1,163 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Writing the state
=================
In Corda, shared facts on the ledger are represented as states. Our first task will be to define a new state type to
represent an IOU.
The ContractState interface
---------------------------
In Corda, any JVM class that implements the ``ContractState`` interface is a valid state. ``ContractState`` is
defined as follows:
.. container:: codeset
.. code-block:: kotlin
interface ContractState {
// The contract that imposes constraints on how this state can evolve over time.
val contract: Contract
// The list of entities considered to have a stake in this state.
val participants: List<AbstractParty>
}
The first thing you'll probably notice about this interface declaration is that its not written in Java or another
common language. The core Corda platform, including the interface declaration above, is entirely written in Kotlin.
Learning some Kotlin will be very useful for understanding how Corda works internally, and usually only takes an
experienced Java developer a day or so to pick up. However, learning Kotlin isn't essential. Because Kotlin code
compiles down to JVM bytecode, CorDapps written in other JVM languages can interoperate with Corda.
If you do want to dive into Kotlin, there's an official
`getting started guide <https://kotlinlang.org/docs/tutorials/>`_, and a series of
`Kotlin Koans <https://kotlinlang.org/docs/tutorials/koans.html>`_.
If not, here's a quick primer on the Kotlinisms in the declaration of ``ContractState``:
* ``val`` declares a read-only property, similar to Java's ``final`` keyword
* The syntax ``varName: varType`` declares ``varName`` as being of type ``varType``
We can see that the ``ContractState`` interface declares two properties:
* ``contract``: the contract controlling transactions involving this state
* ``participants``: the list of entities that have to approve state changes such as changing the state's notary or
upgrading the state's contract
Beyond this, our state is free to define any properties, methods, helpers or inner classes it requires to accurately
represent a given class of shared facts on the ledger.
``ContractState`` also has several child interfaces that you may wish to implement depending on your state, such as
``LinearState`` and ``OwnableState``.
Modelling IOUs
--------------
How should we define the ``IOUState`` representing IOUs on the ledger? Beyond implementing the ``ContractState``
interface, our ``IOUState`` will also need properties to track the relevant features of the IOU:
* The sender of the IOU
* The IOU's recipient
* The value of the IOU
There are many more fields you could include, such as the IOU's currency. We'll abstract them away for now. If
you wish to add them later, its as simple as adding an additional property to your class definition.
Defining IOUState
-----------------
Let's open ``TemplateState.java`` (for Java) or ``TemplateState.kt`` (for Kotlin) and update ``TemplateState`` to
define an ``IOUState``:
.. container:: codeset
.. code-block:: kotlin
package com.template
import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party
class IOUState(val value: Int,
val sender: Party,
val recipient: Party,
// TODO: Once we've defined IOUContract, come back and update this.
override val contract: TemplateContract = TemplateContract()) : ContractState {
override val participants get() = listOf(sender, recipient)
}
.. code-block:: java
package com.template;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import java.util.List;
public class IOUState implements ContractState {
private final int value;
private final Party sender;
private final Party recipient;
// TODO: Once we've defined IOUContract, come back and update this.
private final TemplateContract contract;
public IOUState(int value, Party sender, Party recipient, IOUContract contract) {
this.value = value;
this.sender = sender;
this.recipient = recipient;
this.contract = contract;
}
public int getValue() {
return value;
}
public Party getSender() {
return sender;
}
public Party getRecipient() {
return recipient;
}
@Override
// TODO: Once we've defined IOUContract, come back and update this.
public TemplateContract getContract() {
return contract;
}
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(sender, recipient);
}
}
We've made the following changes:
* We've renamed ``TemplateState`` to ``IOUState``
* We've added properties for ``value``, ``sender`` and ``recipient`` (along with any getters and setters in Java):
* ``value`` is just a standard int (in Java)/Int (in Kotlin), but ``sender`` and ``recipient`` are of type
``Party``. ``Party`` is a built-in Corda type that represents an entity on the network.
* We've overridden ``participants`` to return a list of the ``sender`` and ``recipient``
* This means that actions such as changing the state's contract or its notary will require approval from both the
``sender`` and the ``recipient``
We've left ``IOUState``'s contract as ``TemplateContract`` for now. We'll update this once we've defined the
``IOUContract``.
Progress so far
---------------
We've defined an ``IOUState`` that can be used to represent IOUs as shared facts on the ledger. As we've seen, states in
Corda are simply JVM classes that implement the ``ContractState`` interface. They can have any additional properties and
methods you like.
Next, we'll be writing our ``IOUContract`` to control the evolution of these shared facts over time.

View File

@ -0,0 +1,85 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
The CorDapp Template
====================
When writing a new CorDapp, youll generally want to base it on the
`Cordapp Template <https://github.com/corda/cordapp-template>`_. The Cordapp Template allows you to quickly deploy
your CorDapp onto a local test network of dummy nodes to evaluate its functionality.
Note that there's no need to download and install Corda itself. As long as you're working from a stable Milestone
branch, the required libraries will be downloaded automatically from an online repository.
If you do wish to work from the latest snapshot, please follow the instructions
`here <https://docs.corda.net/tutorial-cordapp.html#using-a-snapshot-release>`_.
Downloading the template
------------------------
Open a terminal window in the directory where you want to download the CorDapp template, and run the following commands:
.. code-block:: text
# Clone the template from GitHub:
git clone https://github.com/corda/cordapp-template.git & cd cordapp-template
# Retrieve a list of the stable Milestone branches using:
git branch -a --list *release-M*
# Check out the Milestone branch with the latest version number:
git checkout release-M[*version number*] & git pull
Template structure
------------------
We can write our CorDapp in either Java or Kotlin, and will be providing the code in both languages throughout. If
you want to write the CorDapp in Java, you'll be modifying the files under ``java-source``. If you prefer to use
Kotlin, you'll be modifying the files under ``kotlin-source``.
To implement our IOU CorDapp, we'll only need to modify five files:
.. container:: codeset
.. code-block:: java
// 1. The state
java-source/src/main/java/com/template/state/TemplateState.java
// 2. The contract
java-source/src/main/java/com/template/contract/TemplateContract.java
// 3. The flow
java-source/src/main/java/com/template/flow/TemplateFlow.java
// Tests for our contract and flow:
// 1. The contract tests
java-source/src/test/java/com/template/contract/ContractTests.java
// 2. The flow tests
java-source/src/test/java/com/template/flow/FlowTests.java
.. code-block:: kotlin
// 1. The state
kotlin-source/src/main/kotlin/com/template/state/TemplateState.kt
// 2. The contract
kotlin-source/src/main/kotlin/com/template/contract/TemplateContract.kt
// 3. The flow
kotlin-source/src/main/kotlin/com/template/flow/TemplateFlow.kt
// Tests for our contract and flow:
// 1. The contract tests
kotlin-source/src/test/kotlin/com/template/contract/ContractTests.kt
// 2. The flow tests
kotlin-source/src/test/kotlin/com/template/flow/FlowTests.kt
Progress so far
---------------
We now have a template that we can build upon to define our IOU CorDapp.
We'll begin writing the CorDapp proper by writing the definition of the ``IOUState``.

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

View File

@ -4,6 +4,7 @@ Tutorials
.. toctree::
:maxdepth: 1
hello-world-index
tutorial-contract
tutorial-contract-clauses
tutorial-test-dsl