Additional fixes to the tutorial.

This commit is contained in:
Joel Dudley 2017-08-17 12:02:44 +01:00 committed by GitHub
parent 4286065872
commit 4eb58b874a
9 changed files with 112 additions and 153 deletions

View File

@ -67,7 +67,7 @@ transfer them or redeem them for cash. One way to enforce this behaviour would b
* Its value must be non-negative
* The lender and the borrower cannot be the same entity
* The IOU's borrower must sign the transaction
* The IOU's lender must sign the transaction
We can picture this transaction as follows:
@ -122,7 +122,6 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi
package com.template.contract;
import com.google.common.collect.ImmutableSet;
import com.template.state.IOUState;
import net.corda.core.contracts.AuthenticatedObject;
import net.corda.core.contracts.CommandData;
@ -155,7 +154,7 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
// Constraints on the signers.
check.using("There must only be one signer.", ImmutableSet.of(command.getSigners()).size() == 1);
check.using("There must only be one signer.", command.getSigners().size() == 1);
check.using("The signer must be the lender.", command.getSigners().contains(lender.getOwningKey()));
return null;
@ -179,7 +178,7 @@ The first thing we add to our contract is a *command*. Commands serve two functi
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 the borrower alone, whereas the transfer of an IOU might require signatures from both the IOU's borrower and lender
from the lender only, whereas the transfer of an IOU might require signatures from both the IOU's borrower and lender
Our contract has one command, a ``Create`` command. All commands must implement the ``CommandData`` interface.
@ -219,7 +218,7 @@ following are true:
* The transaction has inputs
* The transaction doesn't have exactly one output
* The IOU itself is invalid
* The transaction doesn't require the borrower's signature
* The transaction doesn't require the lender's signature
Command constraints
~~~~~~~~~~~~~~~~~~~
@ -270,7 +269,7 @@ We've now written an ``IOUContract`` constraining the evolution of each ``IOUSta
* 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 the lender and borrower
must be different entities.
must be different entities
Before we move on, make sure you go back and modify ``IOUState`` to point to the new ``IOUContract`` class.

View File

@ -16,27 +16,25 @@ Kotlin) file. We won't be using it, and it will cause build errors unless we rem
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:
Let's take a look at the nodes we're going to deploy. Open the project's ``build.gradle`` file and scroll down to the
``task deployNodes`` section. This section defines three nodes - the Controller, NodeA, and NodeB:
.. container:: codeset
.. code-block:: kotlin
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB"
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=UK"
node {
name "CN=Controller,O=R3,OU=corda,L=London,C=GB"
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=GB"
name "CN=NodeA,O=NodeA,L=London,C=UK"
advertisedServices = []
p2pPort 10005
rpcPort 10006
@ -53,15 +51,6 @@ nodes - the Controller, and NodeA, NodeB and NodeC:
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
@ -85,8 +74,8 @@ We can do that now by running the following commands from the root of the projec
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:
Running ``deployNodes`` will build the nodes under ``build/nodes``. If we navigate to one of these folders, we'll see
the three node folders. Each node folder has the following structure:
.. code:: python
@ -102,17 +91,11 @@ Let's start the nodes by running the following commands from the root of the pro
.. code:: python
// On Windows for a Java CorDapp
java-source/build/nodes/runnodes.bat
// On Windows
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
// On Mac
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
@ -143,10 +126,8 @@ We want to create an IOU of 100 with Node B. We start the ``IOUFlow`` by typing:
start IOUFlow iouValue: 99, otherParty: "NodeB"
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.
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.
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:
@ -183,13 +164,6 @@ The vaults of Node A and Node B should both display the following output:
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

View File

@ -40,22 +40,25 @@ Open a terminal window in the directory where you want to download the CorDapp t
Template structure
------------------
We can write our CorDapp in either Java or Kotlin, and will be providing the code in both languages throughout. To
implement our IOU CorDapp in Java, we'll only need to modify three files:
implement our IOU CorDapp in Java, we'll need to modify three files. For Kotlin, we'll simply be modifying the
``App.kt`` file:
.. container:: codeset
.. code-block:: java
// 1. The state
java-source/src/main/java/com/template/state/TemplateState.java
src/main/java/com/template/state/TemplateState.java
// 2. The contract
java-source/src/main/java/com/template/contract/TemplateContract.java
src/main/java/com/template/contract/TemplateContract.java
// 3. The flow
java-source/src/main/java/com/template/flow/TemplateFlow.java
src/main/java/com/template/flow/TemplateFlow.java
For Kotlin, we'll simply be modifying the ``App.kt`` file.
.. code-block:: kotlin
src/main/kotlin/com/template/App.kt
Progress so far
---------------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

@ -11,23 +11,33 @@ Remember that each state references a contract. The contract imposes constraints
If the transaction does not obey the constraints of all the contracts of all its states, it cannot become a valid
ledger update.
We need to modify our contract so that the lender's signature is required in any IOU creation transaction. This will
only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final line of the
``requireThat`` block as follows:
We need to modify our contract so that the borrower's signature is required in any IOU creation transaction. This will
only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final two lines of
the ``requireThat`` block as follows:
.. container:: codeset
.. code-block:: kotlin
// Constraints on the signers.
"There must be two signers." using (command.signers.toSet().size == 2)
"The borrower and lender must be signers." using (command.signers.containsAll(listOf(
out.borrower.owningKey, out.lender.owningKey)))
.. code-block:: java
...
import com.google.common.collect.ImmutableList;
...
// Constraints on the signers.
check.using("There must be two signers.", command.getSigners().size() == 2);
check.using("The borrower and lender must be signers.", command.getSigners().containsAll(
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
Progress so far
---------------
Our contract now imposes an additional constraint - the lender must also sign an IOU creation transaction. Next, we
need to update ``IOUFlow`` so that it actually gathers the counterparty's signature as part of the flow.
Our contract now imposes an additional constraint - the borrower must also sign an IOU creation transaction. Next, we
need to update ``IOUFlow`` so that it actually gathers the borrower's signature as part of the flow.

View File

@ -9,17 +9,17 @@ Updating the flow
To update the flow, we'll need to do two things:
* Update the borrower's side of the flow to request the lender's signature
* Create a flow for the lender to run in response to a signature request from the borrower
* Update the lender's side of the flow to request the borrower's signature
* Create a flow for the borrower to run in response to a signature request from the lender
Updating the borrower's flow
----------------------------
Updating the lender's flow
--------------------------
In the original CorDapp, we automated the process of notarising a transaction and recording it in every party's vault
by invoking a built-in flow called ``FinalityFlow`` as a subflow. We're going to use another pre-defined flow, called
``CollectSignaturesFlow``, to gather the lender's signature.
``CollectSignaturesFlow``, to gather the borrower's signature.
We also need to add the lender's public key to the transaction's command, making the lender one of the required signers
on the transaction.
We also need to add the borrower's public key to the transaction's command, making the borrower one of the required
signers on the transaction.
In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
@ -27,6 +27,8 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
.. code-block:: kotlin
...
// We add the items to the builder.
val state = IOUState(iouValue, me, otherParty)
val cmd = Command(IOUContract.Create(), listOf(me.owningKey, otherParty.owningKey))
@ -36,16 +38,24 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.toSignedTransaction(txBuilder)
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Obtaining the counterparty's signature
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx))
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, CollectSignaturesFlow.tracker()))
// Finalising the transaction.
subFlow(FinalityFlow(fullySignedTx))
.. code-block:: java
...
import com.google.common.collect.ImmutableList;
import java.security.PublicKey;
import java.util.List;
...
// We add the items to the builder.
IOUState state = new IOUState(iouValue, me, otherParty);
List<PublicKey> requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey());
@ -56,65 +66,42 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
txBuilder.verify(getServiceHub());
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().toSignedTransaction(txBuilder);
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Obtaining the counterparty's signature
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, null));
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, CollectSignaturesFlow.Companion.tracker()));
// Finalising the transaction.
subFlow(new FinalityFlow(fullySignedTx));
To make the lender a required signer, we simply add the lender's public key to the list of signers on the command.
return null;
To make the borrower a required signer, we simply add the borrower's public key to the list of signers on the command.
``CollectSignaturesFlow``, meanwhile, takes a transaction signed by the flow initiator, and returns a transaction
signed by all the transaction's other required signers. We then pass this fully-signed transaction into
``FinalityFlow``.
The lender's flow
-----------------
Reorganising our class
^^^^^^^^^^^^^^^^^^^^^^
Before we define the lender's flow, let's reorganise ``IOUFlow.java``/``IOUFlow.kt`` a little bit:
* Rename ``IOUFlow`` to ``Initiator``
* In Java, make the ``Initiator`` class static, rename its constructor to match the new name, and move the definition
inside an enclosing ``IOUFlow`` class
* In Kotlin, move the definition of ``Initiator`` class inside an enclosing ``IOUFlow`` singleton object
We will end up with the following structure:
.. container:: codeset
.. code-block:: kotlin
object IOUFlow {
@InitiatingFlow
@StartableByRPC
class Initiator(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
.. code-block:: java
public class IOUFlow {
@InitiatingFlow
@StartableByRPC
public static class Initiator extends FlowLogic<Void> {
Writing the lender's flow
^^^^^^^^^^^^^^^^^^^^^^^^^
Creating the borrower's flow
----------------------------
We're now ready to write the lender's flow, which will respond to the borrower's attempt to gather our signature.
Inside the ``IOUFlow`` class/singleton object, add the following class:
In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file in Kotlin, add the following class:
.. container:: codeset
.. code-block:: kotlin
@InitiatedBy(Initiator::class)
class Acceptor(val otherParty: Party) : FlowLogic<Unit>() {
...
import net.corda.core.transactions.SignedTransaction
...
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
val signTransactionFlow = object : SignTransactionFlow(otherParty, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
@ -129,12 +116,26 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
.. code-block:: java
@InitiatedBy(Initiator.class)
public static class Acceptor extends FlowLogic<Void> {
package com.template.flow;
import co.paralleluniverse.fibers.Suspendable;
import com.template.state.IOUState;
import net.corda.core.contracts.ContractState;
import net.corda.core.flows.FlowException;
import net.corda.core.flows.FlowLogic;
import net.corda.core.flows.InitiatedBy;
import net.corda.core.flows.SignTransactionFlow;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.utilities.ProgressTracker;
import static net.corda.core.contracts.ContractsDSL.requireThat;
@InitiatedBy(IOUFlow.class)
public class IOUFlowResponder extends FlowLogic<Void> {
private final Party otherParty;
public Acceptor(Party otherParty) {
public IOUFlowResponder(Party otherParty) {
this.otherParty = otherParty;
}
@ -142,8 +143,8 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
@Override
public Void call() throws FlowException {
class signTxFlow extends SignTransactionFlow {
private signTxFlow(Party otherParty) {
super(otherParty, null);
private signTxFlow(Party otherParty, ProgressTracker progressTracker) {
super(otherParty, progressTracker);
}
@Override
@ -158,18 +159,19 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
}
}
subFlow(new signTxFlow(otherParty));
subFlow(new signTxFlow(otherParty, SignTransactionFlow.Companion.tracker()));
return null;
}
}
As with the ``Initiator``, our ``Acceptor`` flow is a ``FlowLogic`` subclass where we've overridden ``FlowLogic.call``.
As with the ``IOUFlow``, our ``IOUFlowResponder`` flow is a ``FlowLogic`` subclass where we've overridden
``FlowLogic.call``.
The flow is annotated with ``InitiatedBy(Initiator.class)``, which means that your node will invoke ``Acceptor.call``
when it receives a message from a instance of ``Initiator`` running on another node. What will this message from the
``Initiator`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that we'll be sent a
``SignedTransaction``, and are expected to send back our signature over that transaction.
The flow is annotated with ``InitiatedBy(IOUFlow.class)``, which means that your node will invoke
``IOUFlowResponder.call`` when it receives a message from a instance of ``Initiator`` running on another node. What
will this message from the ``IOUFlow`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that
we'll be sent a ``SignedTransaction``, and are expected to send back our signature over that transaction.
We could handle this manually. However, there is also a pre-defined flow called ``SignTransactionFlow`` that can handle
this process for us automatically. ``SignTransactionFlow`` is an abstract class, and we must subclass it and override
@ -179,7 +181,7 @@ Once we've defined the subclass, we invoke it using ``FlowLogic.subFlow``, and t
and the lender's flow is conducted automatically.
CheckTransactions
~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^
``SignTransactionFlow`` will automatically verify the transaction and its signatures before signing it. However, just
because a transaction is valid doesn't mean we necessarily want to sign. What if we don't want to deal with the
counterparty in question, or the value is too high, or we're not happy with the transaction's structure?
@ -200,4 +202,4 @@ We can now run our updated CorDapp, using the instructions :doc:`here <hello-wor
Our CorDapp now requires agreement from both the lender and the borrower before an IOU can be created on the ledger.
This prevents either the lender or the borrower from unilaterally updating the ledger in a way that only benefits
themselves.
themselves.

View File

@ -6,5 +6,4 @@ Two-party flows
tut-two-party-introduction
tut-two-party-contract
tut-two-party-flow
tut-two-party-running
tut-two-party-flow

View File

@ -12,14 +12,14 @@ elements:
* An ``IOUContract``, controlling the evolution of IOUs over time
* An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
However, in our original CorDapp, only the IOU's borrower was required to sign transactions issuing IOUs. The lender
However, in our original CorDapp, only the IOU's lender was required to sign transactions issuing IOUs. The borrower
had no say in whether the issuance of the IOU was a valid ledger update or not.
In this tutorial, we'll update our code so that the borrower requires the lender's agreement before they can issue an
In this tutorial, we'll update our code so that the lender requires the borrower's agreement before they can issue an
IOU onto the ledger. We'll need to make two changes:
* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the lender's
signature (as well as the borrower's) to become valid ledger updates
* The ``IOUFlow`` will need to be updated to allow for the gathering of the lender's signature
* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the borrower's
signature (as well as the lender's) to become valid ledger updates
* The ``IOUFlow`` will need to be updated to allow for the gathering of the borrower's signature
We'll start by updating the contract.

View File

@ -1,28 +0,0 @@
Running our CorDapp
===================
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>`_.