mirror of
https://github.com/corda/corda.git
synced 2025-01-21 03:55:00 +00:00
Additional fixes to the tutorial.
This commit is contained in:
parent
4286065872
commit
4eb58b874a
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
---------------
|
||||
|
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
|
||||
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.
|
||||
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.
|
@ -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))
|
||||
@ -36,16 +38,24 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
||||
txBuilder.verify(serviceHub)
|
||||
|
||||
// Signing the transaction.
|
||||
val signedTx = serviceHub.toSignedTransaction(txBuilder)
|
||||
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<PublicKey> requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey());
|
||||
@ -56,65 +66,42 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
||||
txBuilder.verify(getServiceHub());
|
||||
|
||||
// Signing the transaction.
|
||||
final SignedTransaction signedTx = getServiceHub().toSignedTransaction(txBuilder);
|
||||
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<Unit>() {
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
public class IOUFlow {
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
public static class Initiator extends FlowLogic<Void> {
|
||||
|
||||
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<Unit>() {
|
||||
...
|
||||
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
...
|
||||
|
||||
@InitiatedBy(IOUFlow::class)
|
||||
class IOUFlowResponder(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
@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<Void> {
|
||||
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<Void> {
|
||||
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?
|
||||
|
@ -7,4 +7,3 @@ Two-party flows
|
||||
tut-two-party-introduction
|
||||
tut-two-party-contract
|
||||
tut-two-party-flow
|
||||
tut-two-party-running
|
@ -12,14 +12,14 @@ elements:
|
||||
* 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
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
* 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
|
||||
* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the borrower's
|
||||
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 borrower's signature
|
||||
|
||||
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…
Reference in New Issue
Block a user