diff --git a/docs/source/tut-two-party-contract.rst b/docs/source/tut-two-party-contract.rst new file mode 100644 index 0000000000..71f35f60cf --- /dev/null +++ b/docs/source/tut-two-party-contract.rst @@ -0,0 +1,33 @@ +.. highlight:: kotlin +.. raw:: html + + + + +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. \ No newline at end of file diff --git a/docs/source/tut-two-party-flow.rst b/docs/source/tut-two-party-flow.rst new file mode 100644 index 0000000000..00a2658441 --- /dev/null +++ b/docs/source/tut-two-party-flow.rst @@ -0,0 +1,203 @@ +.. highlight:: kotlin +.. raw:: html + + + + +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 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() { + + .. code-block:: java + + public class IOUFlow { + @InitiatingFlow + @StartableByRPC + public static class Initiator extends FlowLogic { + +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() { + @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 { + + 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 `. + +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. diff --git a/docs/source/tut-two-party-index.rst b/docs/source/tut-two-party-index.rst new file mode 100644 index 0000000000..27c69f3e33 --- /dev/null +++ b/docs/source/tut-two-party-index.rst @@ -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 \ No newline at end of file diff --git a/docs/source/tut-two-party-introduction.rst b/docs/source/tut-two-party-introduction.rst new file mode 100644 index 0000000000..46f924583f --- /dev/null +++ b/docs/source/tut-two-party-introduction.rst @@ -0,0 +1,25 @@ +Introduction +============ + +.. note:: This tutorial extends the CorDapp built during the :doc:`Hello, World tutorial `. You can + find the final version of the CorDapp produced in that tutorial + `here `_. + +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. \ No newline at end of file diff --git a/docs/source/tut-two-party-running.rst b/docs/source/tut-two-party-running.rst new file mode 100644 index 0000000000..a616769fda --- /dev/null +++ b/docs/source/tut-two-party-running.rst @@ -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 `_ +with an API and web front-end, and a set of example CorDapps in +`the main Corda repo `_, under ``samples``. An explanation of how to run these +samples :doc:`here `. + +As you write CorDapps, you can learn more about the API available :doc:`here `. + +If you get stuck at any point, please reach out on `Slack `_, +`Discourse `_, or `Stack Overflow `_. \ No newline at end of file diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst index bd92064680..faaa498335 100644 --- a/docs/source/tutorials-index.rst +++ b/docs/source/tutorials-index.rst @@ -5,6 +5,7 @@ Tutorials :maxdepth: 1 hello-world-index + tut-two-party-index tutorial-contract tutorial-contract-clauses tutorial-test-dsl