diff --git a/docs/source/hello-world-contract.rst b/docs/source/hello-world-contract.rst index 253020441e..59b3b81da8 100644 --- a/docs/source/hello-world-contract.rst +++ b/docs/source/hello-world-contract.rst @@ -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. diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 2696af6b23..1baaccf4e3 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -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 diff --git a/docs/source/hello-world-template.rst b/docs/source/hello-world-template.rst index 6ef1be4905..acb131dc6f 100644 --- a/docs/source/hello-world-template.rst +++ b/docs/source/hello-world-template.rst @@ -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 --------------- diff --git a/docs/source/resources/simple-tutorial-transaction.png b/docs/source/resources/simple-tutorial-transaction.png index f881346b06..7b1b128dda 100644 Binary files a/docs/source/resources/simple-tutorial-transaction.png and b/docs/source/resources/simple-tutorial-transaction.png differ diff --git a/docs/source/tut-two-party-contract.rst b/docs/source/tut-two-party-contract.rst index 71f35f60cf..077133b378 100644 --- a/docs/source/tut-two-party-contract.rst +++ b/docs/source/tut-two-party-contract.rst @@ -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/docs/source/tut-two-party-flow.rst b/docs/source/tut-two-party-flow.rst index 00a2658441..441bd43c3b 100644 --- a/docs/source/tut-two-party-flow.rst +++ b/docs/source/tut-two-party-flow.rst @@ -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)) @@ -39,13 +41,21 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows: 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 requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey()); @@ -59,62 +69,39 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows: 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() { - - .. code-block:: java - - public class IOUFlow { - @InitiatingFlow - @StartableByRPC - public static class Initiator extends FlowLogic { - -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() { + ... + + import net.corda.core.transactions.SignedTransaction + + ... + + @InitiatedBy(IOUFlow::class) + class IOUFlowResponder(val otherParty: Party) : FlowLogic() { @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 { + 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 { 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 `_ -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