mirror of
https://github.com/corda/corda.git
synced 2024-12-22 06:17:55 +00:00
Adds a tutorial showing multi-party communication.
This commit is contained in:
parent
b7ee7d42a5
commit
5df0de8ff7
33
docs/source/tut-two-party-contract.rst
Normal file
33
docs/source/tut-two-party-contract.rst
Normal 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.
|
203
docs/source/tut-two-party-flow.rst
Normal file
203
docs/source/tut-two-party-flow.rst
Normal 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.
|
10
docs/source/tut-two-party-index.rst
Normal file
10
docs/source/tut-two-party-index.rst
Normal 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
|
25
docs/source/tut-two-party-introduction.rst
Normal file
25
docs/source/tut-two-party-introduction.rst
Normal 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.
|
28
docs/source/tut-two-party-running.rst
Normal file
28
docs/source/tut-two-party-running.rst
Normal 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>`_.
|
@ -5,6 +5,7 @@ Tutorials
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
hello-world-index
|
hello-world-index
|
||||||
|
tut-two-party-index
|
||||||
tutorial-contract
|
tutorial-contract
|
||||||
tutorial-contract-clauses
|
tutorial-contract-clauses
|
||||||
tutorial-test-dsl
|
tutorial-test-dsl
|
||||||
|
Loading…
Reference in New Issue
Block a user