mirror of
https://github.com/corda/corda.git
synced 2025-01-27 14:49:35 +00:00
Applies tutorial fixes back onto M14.
This commit is contained in:
parent
129e5088cf
commit
8cedce735f
@ -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
|
* Its value must be non-negative
|
||||||
* The lender and the borrower cannot be the same entity
|
* 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:
|
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;
|
package com.template.contract;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.template.state.IOUState;
|
import com.template.state.IOUState;
|
||||||
import net.corda.core.contracts.AuthenticatedObject;
|
import net.corda.core.contracts.AuthenticatedObject;
|
||||||
import net.corda.core.contracts.CommandData;
|
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);
|
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
|
||||||
|
|
||||||
// Constraints on the signers.
|
// 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()));
|
check.using("The signer must be the lender.", command.getSigners().contains(lender.getOwningKey()));
|
||||||
|
|
||||||
return null;
|
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
|
example, a transaction proposing the creation of an IOU could have to satisfy different constraints to one redeeming
|
||||||
an IOU
|
an IOU
|
||||||
* They allow us to define the required signers for the transaction. For example, IOU creation might require signatures
|
* 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.
|
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 has inputs
|
||||||
* The transaction doesn't have exactly one output
|
* The transaction doesn't have exactly one output
|
||||||
* The IOU itself is invalid
|
* 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
|
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
|
* Creating an ``IOUState`` requires an issuance transaction with no inputs, a single ``IOUState`` output, and a
|
||||||
``Create`` command
|
``Create`` command
|
||||||
* The ``IOUState`` created by the issuance transaction must have a non-negative value, and the lender and borrower
|
* 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.
|
Before we move on, make sure you go back and modify ``IOUState`` to point to the new ``IOUContract`` class.
|
||||||
|
|
||||||
|
@ -16,27 +16,25 @@ Kotlin) file. We won't be using it, and it will cause build errors unless we rem
|
|||||||
|
|
||||||
Deploying our CorDapp
|
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``
|
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
|
||||||
or ``kotlin-source/build.gradle`` and scroll down to the ``task deployNodes`` section. This section defines four
|
``task deployNodes`` section. This section defines three nodes - the Controller, NodeA, and NodeB:
|
||||||
nodes - the Controller, and NodeA, NodeB and NodeC:
|
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
.. code-block:: kotlin
|
.. code-block:: kotlin
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
directory "./build/nodes"
|
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 {
|
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"]
|
advertisedServices = ["corda.notary.validating"]
|
||||||
p2pPort 10002
|
p2pPort 10002
|
||||||
rpcPort 10003
|
rpcPort 10003
|
||||||
webPort 10004
|
|
||||||
cordapps = []
|
cordapps = []
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "CN=NodeA,O=NodeA,L=London,C=GB"
|
name "CN=NodeA,O=NodeA,L=London,C=UK"
|
||||||
advertisedServices = []
|
advertisedServices = []
|
||||||
p2pPort 10005
|
p2pPort 10005
|
||||||
rpcPort 10006
|
rpcPort 10006
|
||||||
@ -53,15 +51,6 @@ nodes - the Controller, and NodeA, NodeB and NodeC:
|
|||||||
cordapps = []
|
cordapps = []
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
|
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
|
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 the nodes
|
||||||
-----------------
|
-----------------
|
||||||
Running ``deployNodes`` will build the nodes under both ``java-source/build/nodes`` and ``kotlin-source/build/nodes``.
|
Running ``deployNodes`` will build the nodes under ``build/nodes``. If we navigate to one of these folders, we'll see
|
||||||
If we navigate to one of these folders, we'll see four node folder. Each node folder has the following structure:
|
the three node folders. Each node folder has the following structure:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
@ -102,17 +91,11 @@ Let's start the nodes by running the following commands from the root of the pro
|
|||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
// On Windows for a Java CorDapp
|
// On Windows
|
||||||
java-source/build/nodes/runnodes.bat
|
build/nodes/runnodes.bat
|
||||||
|
|
||||||
// On Windows for a Kotlin CorDapp
|
// On Mac
|
||||||
kotlin-source/build/nodes/runnodes.bat
|
build/nodes/runnodes
|
||||||
|
|
||||||
// On Mac for a Java CorDapp
|
|
||||||
java-source/build/nodes/runnodes
|
|
||||||
|
|
||||||
// On Mac for a Kotlin CorDapp
|
|
||||||
kotlin-source/build/nodes/runnodes
|
|
||||||
|
|
||||||
This will start a terminal window for each node, and an additional terminal window for each node's webserver - eight
|
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
|
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"
|
start IOUFlow iouValue: 99, otherParty: "NodeB"
|
||||||
|
|
||||||
Node A and Node B will automatically agree an IOU.
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
We can check the flow has worked by using an RPC operation to check the contents of each node's vault. Typing ``run``
|
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:
|
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
|
index: 0
|
||||||
second: "(observable)"
|
second: "(observable)"
|
||||||
|
|
||||||
But the vault of Node C should output nothing!
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
first: []
|
|
||||||
second: "(observable)"
|
|
||||||
|
|
||||||
Conclusion
|
Conclusion
|
||||||
----------
|
----------
|
||||||
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Like all CorDapps, our
|
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Like all CorDapps, our
|
||||||
|
@ -40,22 +40,25 @@ Open a terminal window in the directory where you want to download the CorDapp t
|
|||||||
Template structure
|
Template structure
|
||||||
------------------
|
------------------
|
||||||
We can write our CorDapp in either Java or Kotlin, and will be providing the code in both languages throughout. To
|
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
|
.. container:: codeset
|
||||||
|
|
||||||
.. code-block:: java
|
.. code-block:: java
|
||||||
|
|
||||||
// 1. The state
|
// 1. The state
|
||||||
java-source/src/main/java/com/template/state/TemplateState.java
|
src/main/java/com/template/state/TemplateState.java
|
||||||
|
|
||||||
// 2. The contract
|
// 2. The contract
|
||||||
java-source/src/main/java/com/template/contract/TemplateContract.java
|
src/main/java/com/template/contract/TemplateContract.java
|
||||||
|
|
||||||
// 3. The flow
|
// 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
|
Progress so far
|
||||||
---------------
|
---------------
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 208 KiB |
@ -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
|
If the transaction does not obey the constraints of all the contracts of all its states, it cannot become a valid
|
||||||
ledger update.
|
ledger update.
|
||||||
|
|
||||||
We need to modify our contract so that the lender's signature is required in any IOU creation transaction. This will
|
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 line of the
|
only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final two lines of
|
||||||
``requireThat`` block as follows:
|
the ``requireThat`` block as follows:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
.. code-block:: kotlin
|
.. 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(
|
"The borrower and lender must be signers." using (command.signers.containsAll(listOf(
|
||||||
out.borrower.owningKey, out.lender.owningKey)))
|
out.borrower.owningKey, out.lender.owningKey)))
|
||||||
|
|
||||||
.. code-block:: java
|
.. 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(
|
check.using("The borrower and lender must be signers.", command.getSigners().containsAll(
|
||||||
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
|
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
|
||||||
|
|
||||||
Progress so far
|
Progress so far
|
||||||
---------------
|
---------------
|
||||||
Our contract now imposes an additional constraint - the lender must also sign an IOU creation transaction. Next, we
|
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 counterparty's signature as part of the flow.
|
need to update ``IOUFlow`` so that it actually gathers the borrower's signature as part of the flow.
|
@ -9,17 +9,17 @@ Updating the flow
|
|||||||
|
|
||||||
To update the flow, we'll need to do two things:
|
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
|
* Update the lender's side of the flow to request the borrower's signature
|
||||||
* Create a flow for the lender to run in response to a signature request from the borrower
|
* 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
|
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
|
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
|
We also need to add the borrower's public key to the transaction's command, making the borrower one of the required
|
||||||
on the transaction.
|
signers on the transaction.
|
||||||
|
|
||||||
In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
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
|
.. code-block:: kotlin
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
// We add the items to the builder.
|
// We add the items to the builder.
|
||||||
val state = IOUState(iouValue, me, otherParty)
|
val state = IOUState(iouValue, me, otherParty)
|
||||||
val cmd = Command(IOUContract.Create(), listOf(me.owningKey, otherParty.owningKey))
|
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)
|
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
||||||
|
|
||||||
// Obtaining the counterparty's signature
|
// Obtaining the counterparty's signature
|
||||||
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx))
|
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, CollectSignaturesFlow.tracker()))
|
||||||
|
|
||||||
// Finalising the transaction.
|
// Finalising the transaction.
|
||||||
subFlow(FinalityFlow(fullySignedTx))
|
subFlow(FinalityFlow(fullySignedTx))
|
||||||
|
|
||||||
.. code-block:: java
|
.. code-block:: java
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
// We add the items to the builder.
|
// We add the items to the builder.
|
||||||
IOUState state = new IOUState(iouValue, me, otherParty);
|
IOUState state = new IOUState(iouValue, me, otherParty);
|
||||||
List<PublicKey> requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey());
|
List<PublicKey> 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);
|
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
|
||||||
|
|
||||||
// Obtaining the counterparty's signature
|
// 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.
|
// Finalising the transaction.
|
||||||
subFlow(new FinalityFlow(fullySignedTx));
|
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
|
``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
|
signed by all the transaction's other required signers. We then pass this fully-signed transaction into
|
||||||
``FinalityFlow``.
|
``FinalityFlow``.
|
||||||
|
|
||||||
The lender's flow
|
Creating the borrower'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.
|
We're now ready to write the lender's flow, which will respond to the borrower's attempt to gather our signature.
|
||||||
|
In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file in Kotlin, add the following class:
|
||||||
Inside the ``IOUFlow`` class/singleton object, add the following class:
|
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
.. code-block:: kotlin
|
.. 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
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
|
val signTransactionFlow = object : SignTransactionFlow(otherParty, SignTransactionFlow.tracker()) {
|
||||||
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||||
val output = stx.tx.outputs.single().data
|
val output = stx.tx.outputs.single().data
|
||||||
"This must be an IOU transaction." using (output is IOUState)
|
"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
|
.. code-block:: java
|
||||||
|
|
||||||
@InitiatedBy(Initiator.class)
|
package com.template.flow;
|
||||||
public static class Acceptor extends FlowLogic<Void> {
|
|
||||||
|
|
||||||
|
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;
|
private final Party otherParty;
|
||||||
|
|
||||||
public Acceptor(Party otherParty) {
|
public IOUFlowResponder(Party otherParty) {
|
||||||
this.otherParty = otherParty;
|
this.otherParty = otherParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,8 +143,8 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
|
|||||||
@Override
|
@Override
|
||||||
public Void call() throws FlowException {
|
public Void call() throws FlowException {
|
||||||
class signTxFlow extends SignTransactionFlow {
|
class signTxFlow extends SignTransactionFlow {
|
||||||
private signTxFlow(Party otherParty) {
|
private signTxFlow(Party otherParty, ProgressTracker progressTracker) {
|
||||||
super(otherParty, null);
|
super(otherParty, progressTracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
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``
|
The flow is annotated with ``InitiatedBy(IOUFlow.class)``, which means that your node will invoke
|
||||||
when it receives a message from a instance of ``Initiator`` running on another node. What will this message from the
|
``IOUFlowResponder.call`` when it receives a message from a instance of ``Initiator`` running on another node. What
|
||||||
``Initiator`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that we'll be sent a
|
will this message from the ``IOUFlow`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that
|
||||||
``SignedTransaction``, and are expected to send back our signature over that transaction.
|
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
|
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
|
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.
|
and the lender's flow is conducted automatically.
|
||||||
|
|
||||||
CheckTransactions
|
CheckTransactions
|
||||||
~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^
|
||||||
``SignTransactionFlow`` will automatically verify the transaction and its signatures before signing it. However, just
|
``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
|
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?
|
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.
|
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
|
This prevents either the lender or the borrower from unilaterally updating the ledger in a way that only benefits
|
||||||
themselves.
|
themselves.
|
@ -6,5 +6,4 @@ Two-party flows
|
|||||||
|
|
||||||
tut-two-party-introduction
|
tut-two-party-introduction
|
||||||
tut-two-party-contract
|
tut-two-party-contract
|
||||||
tut-two-party-flow
|
tut-two-party-flow
|
||||||
tut-two-party-running
|
|
@ -12,14 +12,14 @@ elements:
|
|||||||
* An ``IOUContract``, controlling the evolution of IOUs over time
|
* An ``IOUContract``, controlling the evolution of IOUs over time
|
||||||
* An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
|
* 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.
|
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:
|
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
|
* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the borrower's
|
||||||
signature (as well as the borrower's) to become valid ledger updates
|
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 lender's signature
|
* The ``IOUFlow`` will need to be updated to allow for the gathering of the borrower's signature
|
||||||
|
|
||||||
We'll start by updating the contract.
|
We'll start by updating the contract.
|
@ -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>`_.
|
|
Loading…
x
Reference in New Issue
Block a user