Adds a tutorial showing multi-party communication.

This commit is contained in:
Joel Dudley 2017-07-17 12:23:14 +01:00 committed by GitHub
parent b7ee7d42a5
commit 5df0de8ff7
6 changed files with 300 additions and 0 deletions

View File

@ -0,0 +1,33 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Updating the contract
=====================
Remember that each state references a contract. The contract imposes constraints on transactions involving that state.
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:
.. container:: codeset
.. code-block:: kotlin
"The borrower and lender must be signers." using (command.signers.containsAll(listOf(
out.borrower.owningKey, out.lender.owningKey)))
.. code-block:: java
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.

View File

@ -0,0 +1,203 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
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
Updating the borrower'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.
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.
In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
.. container:: codeset
.. 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))
txBuilder.withItems(state, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Obtaining the counterparty's signature
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx))
// Finalising the transaction.
subFlow(FinalityFlow(fullySignedTx))
.. code-block:: java
// We add the items to the builder.
IOUState state = new IOUState(iouValue, me, otherParty);
List<PublicKey> requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command(new IOUContract.Create(), requiredSigners);
txBuilder.withItems(state, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Obtaining the counterparty's signature
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, null));
// 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.
``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
^^^^^^^^^^^^^^^^^^^^^^^^^
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:
.. container:: codeset
.. code-block:: kotlin
@InitiatedBy(Initiator::class)
class Acceptor(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.value < 100)
}
}
subFlow(signTransactionFlow)
}
}
.. code-block:: java
@InitiatedBy(Initiator.class)
public static class Acceptor extends FlowLogic<Void> {
private final Party otherParty;
public Acceptor(Party otherParty) {
this.otherParty = otherParty;
}
@Suspendable
@Override
public Void call() throws FlowException {
class signTxFlow extends SignTransactionFlow {
private signTxFlow(Party otherParty) {
super(otherParty, null);
}
@Override
protected void checkTransaction(SignedTransaction stx) {
requireThat(require -> {
ContractState output = stx.getTx().getOutputs().get(0).getData();
require.using("This must be an IOU transaction.", output instanceof IOUState);
IOUState iou = (IOUState) output;
require.using("The IOU's value can't be too high.", iou.getValue() < 100);
return null;
});
}
}
subFlow(new signTxFlow(otherParty));
return null;
}
}
As with the ``Initiator``, our ``Acceptor`` 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.
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
``SignTransactionFlow.checkTransaction``.
Once we've defined the subclass, we invoke it using ``FlowLogic.subFlow``, and the communication with the borrower's
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?
Overriding ``SignTransactionFlow.checkTransaction`` allows us to define these additional checks. In our case, we are
checking that:
* The transaction involves an ``IOUState`` - this ensures that ``IOUContract`` will be run to verify the transaction
* The IOU's value is less than some amount (100 in this case)
If either of these conditions are not met, we will not sign the transaction - even if the transaction and its
signatures are valid.
Conclusion
----------
We have now updated our flow to gather the lender's signature as well, in line with the constraints in ``IOUContract``.
We can now run our updated CorDapp, using the instructions :doc:`here <hello-world-running>`.
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.

View File

@ -0,0 +1,10 @@
Two-party flows
===============
.. toctree::
:maxdepth: 1
tut-two-party-introduction
tut-two-party-contract
tut-two-party-flow
tut-two-party-running

View File

@ -0,0 +1,25 @@
Introduction
============
.. note:: This tutorial extends the CorDapp built during the :doc:`Hello, World tutorial <hello-world-index>`. You can
find the final version of the CorDapp produced in that tutorial
`here <https://github.com/joeldudleyr3/tutorials/tree/master/Tut1Repo>`_.
In the Hello, World tutorial, we built a CorDapp allowing us to model IOUs on ledger. Our CorDapp was made up of three
elements:
* An ``IOUState``, representing IOUs on the ledger
* 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
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
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
We'll start by updating the contract.

View File

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

View File

@ -5,6 +5,7 @@ Tutorials
:maxdepth: 1
hello-world-index
tut-two-party-index
tutorial-contract
tutorial-contract-clauses
tutorial-test-dsl