Updates tutorial to reflect new template structure. Clean-up. (#4216)

* Initial improvements.

* Updates tutorials.

* Missing imports.

* Addresses review feedback.
This commit is contained in:
Joel Dudley 2018-11-13 11:50:24 +00:00 committed by GitHub
parent e2a351cb70
commit f3b09988a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 136 additions and 124 deletions

View File

@ -2,16 +2,19 @@ package net.corda.docs.java.tutorial.helloworld;
import co.paralleluniverse.fibers.Suspendable;
import com.template.TemplateContract;
import net.corda.core.flows.*;
import net.corda.core.flows.FlowException;
import net.corda.core.flows.FlowLogic;
import net.corda.core.flows.InitiatingFlow;
import net.corda.core.flows.StartableByRPC;
import net.corda.core.utilities.ProgressTracker;
// DOCSTART 01
// Add these imports:
import net.corda.core.contracts.Command;
import net.corda.core.contracts.CommandData;
import net.corda.core.flows.FinalityFlow;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
// Replace Initiator's definition with:
@InitiatingFlow
@ -46,13 +49,12 @@ public class IOUFlow extends FlowLogic<Void> {
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
CommandData cmdType = new TemplateContract.Commands.Action();
Command cmd = new Command<>(cmdType, getOurIdentity().getOwningKey());
Command command = new Command<>(new TemplateContract.Commands.Action(), getOurIdentity().getOwningKey());
// We create a transaction builder and add the components.
TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(cmd);
.addCommand(command);
// Signing the transaction.
SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);

View File

@ -2,11 +2,11 @@ package net.corda.docs.java.tutorial.helloworld;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import java.util.Arrays;
import java.util.List;
// DOCSTART 01
// Add these imports:
import com.google.common.collect.ImmutableList;
// Add this import:
import net.corda.core.identity.Party;
// Replace TemplateState's definition with:
@ -35,7 +35,7 @@ public class IOUState implements ContractState {
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
return Arrays.asList(lender, borrower);
}
}
// DOCEND 01

View File

@ -6,15 +6,14 @@ import net.corda.core.transactions.LedgerTransaction;
// DOCSTART 01
// Add these imports:
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.identity.Party;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
// Replace TemplateContract's definition with:
public class IOUContract implements Contract {
@ -28,26 +27,29 @@ public class IOUContract implements Contract {
public void verify(LedgerTransaction tx) {
final CommandWithParties<IOUContract.Create> command = requireSingleCommand(tx.getCommands(), IOUContract.Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1);
// Constraints on the shape of the transaction.
if (!tx.getInputs().isEmpty())
throw new IllegalArgumentException("No inputs should be consumed when issuing an IOU.");
if (!(tx.getOutputs().size() == 1))
throw new IllegalArgumentException("There should be one output state of type IOUState.");
// IOU-specific constraints.
final IOUState out = tx.outputsOfType(IOUState.class).get(0);
final Party lender = out.getLender();
final Party borrower = out.getBorrower();
check.using("The IOU's value must be non-negative.", out.getValue() > 0);
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
// IOU-specific constraints.
final IOUState output = tx.outputsOfType(IOUState.class).get(0);
final Party lender = output.getLender();
final Party borrower = output.getBorrower();
if (output.getValue() <= 0)
throw new IllegalArgumentException("The IOU's value must be non-negative.");
if (lender.equals(borrower))
throw new IllegalArgumentException("The lender and the borrower cannot be the same entity.");
// Constraints on the signers.
final List<PublicKey> signers = command.getSigners();
check.using("There must be two signers.", signers.size() == 2);
check.using("The borrower and lender must be signers.", signers.containsAll(
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
// Constraints on the signers.
final List<PublicKey> requiredSigners = command.getSigners();
final List<PublicKey> expectedSigners = Arrays.asList(borrower.getOwningKey(), lender.getOwningKey());
if (requiredSigners.size() != 2)
throw new IllegalArgumentException("There must be two signers.");
if (!(requiredSigners.containsAll(expectedSigners)))
throw new IllegalArgumentException("The borrower and lender must be signers.");
return null;
});
}
}
// DOCEND 01

View File

@ -2,9 +2,7 @@ package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndContract;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
@ -12,6 +10,7 @@ import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
// DOCEND 01
@ -42,22 +41,19 @@ public class IOUFlow extends FlowLogic<Void> {
@Suspendable
@Override
public Void call() throws FlowException {
// DOCSTART 02
// We retrieve the notary identity from the network map.
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// DOCSTART 02
// We create a transaction builder.
TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.ID);
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
List<PublicKey> requiredSigners = Arrays.asList(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command command = new Command<>(new IOUContract.Create(), requiredSigners);
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd);
// We create a transaction builder and add the components.
TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, IOUContract.ID)
.addCommand(command);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
@ -70,7 +66,7 @@ public class IOUFlow extends FlowLogic<Void> {
// Obtaining the counterparty's signature.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, ImmutableList.of(otherPartySession), CollectSignaturesFlow.tracker()));
signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.tracker()));
// Finalising the transaction.
subFlow(new FinalityFlow(fullySignedTx));

View File

@ -1,10 +1,10 @@
package net.corda.docs.java.tutorial.twoparty;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import java.util.Arrays;
import java.util.List;
public class IOUState implements ContractState {
@ -32,6 +32,6 @@ public class IOUState implements ContractState {
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
return Arrays.asList(lender, borrower);
}
}

View File

@ -8,13 +8,13 @@ import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.utilities.ProgressTracker
// DOCSTART 01
// Add these imports:
import net.corda.core.contracts.Command
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
// Replace Initiator's definition with:
@InitiatingFlow
@ -33,12 +33,12 @@ class IOUFlow(val iouValue: Int,
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val cmd = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)
val command = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(cmd)
.addCommand(command)
// We sign the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)

View File

@ -5,7 +5,7 @@ package net.corda.docs.kotlin.tutorial.helloworld
import net.corda.core.contracts.ContractState
// DOCSTART 01
// Add these imports:
// Add this import:
import net.corda.core.identity.Party
// Replace TemplateState's definition with:

View File

@ -5,7 +5,7 @@ import net.corda.core.contracts.Contract
import net.corda.core.transactions.LedgerTransaction
// DOCSTART 01
// Add these imports:
// Add this import:
import net.corda.core.contracts.*
class IOUContract : Contract {
@ -25,14 +25,14 @@ class IOUContract : Contract {
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputsOfType<IOUState>().single()
"The IOU's value must be non-negative." using (out.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
val output = tx.outputsOfType<IOUState>().single()
"The IOU's value must be non-negative." using (output.value > 0)
"The lender and the borrower cannot be the same entity." using (output.lender != output.borrower)
// Constraints on the signers.
val expectedSigners = listOf(output.borrower.owningKey, output.lender.owningKey)
"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)))
"The borrower and lender must be signers." using (command.signers.containsAll(expectedSigners))
}
}
}

View File

@ -5,7 +5,6 @@ package net.corda.docs.kotlin.tutorial.twoparty
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.CollectSignaturesFlow
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
@ -27,20 +26,18 @@ class IOUFlow(val iouValue: Int,
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
// DOCSTART 02
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// DOCSTART 02
// We create a transaction builder.
val txBuilder = TransactionBuilder(notary = notary)
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContractAndState = StateAndContract(outputState, IOUContract.ID)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
val command = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd)
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, IOUContract.ID)
.addCommand(command)
// Verifying the transaction.
txBuilder.verify(serviceHub)

View File

@ -3,10 +3,7 @@
package net.corda.docs.kotlin.tutorial.twoparty
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.SignTransactionFlow
import net.corda.core.flows.*
import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow
import net.corda.docs.kotlin.tutorial.helloworld.IOUState

View File

@ -40,8 +40,7 @@ FlowLogic
---------
All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``.
Let's define our ``IOUFlow`` in either ``Initiator.java`` or ``Flows.kt``. Delete the two existing flows in the
template (``Initiator`` and ``Responder``), and replace them with the following:
Let's define our ``IOUFlow``. Delete the existing ``Responder`` flow. Then replace the definition of ``Initiator`` with the following:
.. container:: codeset

View File

@ -13,29 +13,38 @@ By this point, :doc:`your dev environment should be set up <getting-set-up>`, yo
:doc:`your first CorDapp <tutorial-cordapp>`, and you're familiar with Corda's :doc:`key concepts <key-concepts>`. What
comes next?
If you're a developer, the next step is to write your own CorDapp. CorDapps are plugins that are installed on one or
more Corda nodes, and give the nodes' owners the ability to make their node conduct some new process - anything from
If you're a developer, the next step is to write your own CorDapp. CorDapps are applications that are installed on one or
more Corda nodes, and that allow the node's operator to instruct their node to perform some new process - anything from
issuing a debt instrument to making a restaurant booking.
Our use-case
------------
Our CorDapp will model IOUs on-ledger. An IOU short for “I O(we) (yo)U” records the fact that one person owes
another person a given amount of money. Clearly this is sensitive information that we'd only want to communicate on
a need-to-know basis between the lender and the borrower. Fortunately, this is one of the areas where Corda excels.
Corda makes it easy to allow a small set of parties to agree on a shared fact without needing to share this fact with
everyone else on the network, as is the norm in blockchain platforms.
We will write a CorDapp to model IOUs on the blockchain. Each IOU short for “I O(we) (yo)U” will record the fact that one node owes
another node a certain amount. This simple CorDapp will showcase several key benefits of Corda as a blockchain platform:
To serve any useful function, our CorDapp will need at least two things:
* **Privacy** - Since IOUs represent sensitive information, we will be taking advantage of Corda's ability to only share
ledger updates with other nodes on a need-to-know basis, instead of using a gossip protocol to share this information with every node on
the network as you would with a traditional blockchain platform
* **States**, the shared facts that Corda nodes reach consensus over and are then stored on the ledger
* **Flows**, which encapsulate the procedure for carrying out a specific ledger update
* **Well-known identities** - Each Corda node has a well-known identity on the network. This allows us to write code in terms of real
identities, rather than anonymous public keys
Our IOU CorDapp is no exception. It will define both a state and a flow:
* **Re-use of existing, proven technologies** - We will be writing our CorDapp using standard Java. It will run on a Corda node, which is
simply a Java process and runs on a regular Java machine (e.g. on your local machine or in the cloud). The nodes will store their data in
a standard SQL database
CorDapps usually define at least three things:
* **States** - the (possibly shared) facts that are written to the ledger
* **Flows** - the procedures for carrying out specific ledger updates
* **Contracts** - the constraints governing how states of a given type can evolve over time
Our IOU CorDapp is no exception. It will define the following components:
The IOUState
^^^^^^^^^^^^
Our state will be the ``IOUState``. It will store the value of the IOU, as well as the identities of the lender and the
borrower. We can visualize ``IOUState`` as follows:
Our state will be the ``IOUState``, representing an IOU. It will contain the IOU's value, its lender and its borrower. We can visualize
``IOUState`` as follows:
.. image:: resources/tutorial-state.png
:scale: 25%
@ -43,20 +52,20 @@ borrower. We can visualize ``IOUState`` as follows:
The IOUFlow
^^^^^^^^^^^
Our flow will be the ``IOUFlow``. This flow will completely automate the process of issuing a new IOU onto a ledger. It
is composed of the following steps:
Our flow will be the ``IOUFlow``. This flow will completely automate the process of issuing a new IOU onto a ledger. It has the following
steps:
.. image:: resources/simple-tutorial-flow.png
:scale: 25%
:align: center
In traditional distributed ledger systems, where all data is broadcast to every network participant, you dont need to
think about data flows you simply package up your ledger update and send it to everyone else on the network. But in
Corda, where privacy is a core focus, flows allow us to carefully control who sees what during the process of
agreeing a ledger update.
The IOUContract
^^^^^^^^^^^^^^^
For this tutorial, we will use the default ``TemplateContract``. We will update it to create a fully-fledged ``IOUContract`` in the next
tutorial.
Progress so far
---------------
We've sketched out a simple CorDapp that will allow nodes to confidentially issue new IOUs onto a ledger.
We've designed a simple CorDapp that will allow nodes to agree new IOUs on the blockchain.
Next, we'll be taking a look at the template project we'll be using as the basis for our CorDapp.
Next, we'll take a look at the template project we'll be using as the basis for our CorDapp.

View File

@ -107,11 +107,9 @@ commands.
We want to create an IOU of 99 with PartyB. We start the ``IOUFlow`` by typing:
.. container:: codeset
.. code-block:: bash
.. code-block:: kotlin
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
This single command will cause PartyA and PartyB to automatically agree an IOU. This is one of the great advantages of
the flow framework - it allows you to reduce complex negotiation and update processes into a single function call.
@ -122,7 +120,7 @@ We can check the contents of each node's vault by running:
.. code-block:: bash
run vaultQuery contractStateType: com.template.IOUState
run vaultQuery contractStateType: com.template.IOUState
The vaults of PartyA and PartyB should both display the following output:
@ -162,12 +160,27 @@ The vaults of PartyA and PartyB should both display the following output:
This is the transaction issuing our ``IOUState`` onto a ledger.
However, if we run the same command on the other node (the notary), we will see the following:
.. code:: bash
{
"states" : [ ],
"statesMetadata" : [ ],
"totalStatesAvailable" : -1,
"stateTypes" : "UNCONSUMED",
"otherResults" : [ ]
}
This is the result of Corda's privacy model. Because the notary was not involved in the transaction and had no need to see the data, the
transaction was not distributed to them.
Conclusion
----------
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Our CorDapp is made up of two key
parts:
* The ``IOUState``, representing IOUs on the ledger
* The ``IOUState``, representing IOUs on the blockchain
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
After completing this tutorial, your CorDapp should look like this:

View File

@ -7,7 +7,7 @@
Writing the state
=================
In Corda, shared facts on the ledger are represented as states. Our first task will be to define a new state type to
In Corda, shared facts on the blockchain are represented as states. Our first task will be to define a new state type to
represent an IOU.
The ContractState interface
@ -28,7 +28,7 @@ We can see that the ``ContractState`` interface has a single field, ``participan
entities for which this state is relevant.
Beyond this, our state is free to define any fields, methods, helpers or inner classes it requires to accurately
represent a given type of shared fact on the ledger.
represent a given type of shared fact on the blockchain.
.. note::
@ -46,7 +46,7 @@ represent a given type of shared fact on the ledger.
Modelling IOUs
--------------
How should we define the ``IOUState`` representing IOUs on the ledger? Beyond implementing the ``ContractState``
How should we define the ``IOUState`` representing IOUs on the blockchain? Beyond implementing the ``ContractState``
interface, our ``IOUState`` will also need properties to track the relevant features of the IOU:
* The value of the IOU
@ -99,7 +99,7 @@ Corda are simply classes that implement the ``ContractState`` interface. They ca
methods you like.
All that's left to do is write the ``IOUFlow`` that will allow a node to orchestrate the creation of a new ``IOUState``
on the ledger, while only sharing information on a need-to-know basis.
on the blockchain, while only sharing information on a need-to-know basis.
What about the contract?
------------------------

View File

@ -7,41 +7,39 @@
The CorDapp Template
====================
When writing a new CorDapp, youll generally want to base it on the standard templates:
When writing a new CorDapp, youll generally want to start from one of the standard templates:
* The `Java Cordapp Template <https://github.com/corda/cordapp-template-java>`_
* The `Kotlin Cordapp Template <https://github.com/corda/cordapp-template-kotlin>`_
The Cordapp templates provide the required boilerplate for developing a CorDapp, and allow you to quickly deploy your
CorDapp onto a local test network of dummy nodes to test its functionality.
The Cordapp templates provide the boilerplate for developing a new CorDapp. CorDapps can be written in either Java or Kotlin. We will be
providing the code in both languages throughout this tutorial.
CorDapps can be written in both Java and Kotlin, and will be providing the code in both languages in this tutorial.
Note that there's no need to download and install Corda itself. Corda's required libraries will be downloaded
automatically from an online Maven repository.
Note that there's no need to download and install Corda itself. The required libraries are automatically downloaded from an online Maven
repository and cached locally.
Downloading the template
------------------------
To download the template, open a terminal window in the directory where you want to download the CorDapp template, and
run the following command:
Open a terminal window in the directory where you want to download the CorDapp template, and run the following command:
.. code-block:: bash
.. container:: codeset
git clone https://github.com/corda/cordapp-template-java.git ; cd cordapp-template-java
.. code-block:: java
*or*
git clone https://github.com/corda/cordapp-template-java.git ; cd cordapp-template-java
git clone https://github.com/corda/cordapp-template-kotlin.git ; cd cordapp-template-kotlin
.. code-block:: kotlin
git clone https://github.com/corda/cordapp-template-kotlin.git ; cd cordapp-template-kotlin
Opening the template in IntelliJ
--------------------------------
Once the template is download, open it in IntelliJ by following the instructions here:
https://docs.corda.net/tutorial-cordapp.html#opening-the-example-cordapp-in-intellij.
Template structure
------------------
The template has a number of files, but we can ignore most of them. We will only be modifying the following files:
For this tutorial, we will only be modifying the following files:
.. container:: codeset

View File

@ -154,7 +154,7 @@ Transaction constraints
~~~~~~~~~~~~~~~~~~~~~~~
We also want our transaction to have no inputs and only a single output - an issuance transaction.
To impose this and the subsequent constraints, we are using Corda's built-in ``requireThat`` block. ``requireThat``
In Kotlin, we impose these and the subsequent constraints using Corda's built-in ``requireThat`` block. ``requireThat``
provides a terse way to write the following:
* If the condition on the right-hand side doesn't evaluate to true...
@ -162,6 +162,8 @@ provides a terse way to write the following:
As before, the act of throwing this exception causes the transaction to be considered invalid.
In Java, we simply throw an ``IllegalArgumentException`` manually instead.
IOU constraints
~~~~~~~~~~~~~~~
We want to impose two constraints on the ``IOUState`` itself:
@ -169,9 +171,7 @@ We want to impose two constraints on the ``IOUState`` itself:
* Its value must be non-negative
* The lender and the borrower cannot be the same entity
We impose these constraints in the same ``requireThat`` block as before.
You can see that we're not restricted to only writing constraints in the ``requireThat`` block. We can also write
You can see that we're not restricted to only writing constraints inside ``verify``. We can also write
other statements - in this case, extracting the transaction's single ``IOUState`` and assigning it to a variable.
Signer constraints
@ -180,7 +180,7 @@ Finally, we require both the lender and the borrower to be required signers on t
required signers is equal to the union of all the signers listed on the commands. We therefore extract the signers from
the ``Create`` command we retrieved earlier.
This is an absolutely essential constraint - it ensures that no ``IOUState`` can ever be created on the ledger without
This is an absolutely essential constraint - it ensures that no ``IOUState`` can ever be created on the blockchain without
the express agreement of both the lender and borrower nodes.
Progress so far

View File

@ -31,8 +31,7 @@ In ``IOUFlow.java``/``Flows.kt``, change the imports block to the following:
:start-after: DOCSTART 01
:end-before: DOCEND 01
And update ``IOUFlow.call`` by changing the code following the retrieval of the notary's identity from the network as
follows:
And update ``IOUFlow.call`` to the following:
.. container:: codeset
@ -136,7 +135,7 @@ defined in ``IOUContract``. We can now re-run our updated CorDapp, using the
:doc:`same instructions as before <hello-world-running>`.
Our CorDapp now imposes restrictions on the issuance of IOUs. Most importantly, IOU issuance 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
from both the lender and the borrower before an IOU can be created on the blockchain. This prevents either the lender or
the borrower from unilaterally updating the ledger in a way that only benefits themselves.
After completing this tutorial, your CorDapp should look like this:

View File

@ -6,10 +6,10 @@ Hello, World! Pt.2 - Contract constraints
In the Hello, World tutorial, we built a CorDapp allowing us to model IOUs on ledger. Our CorDapp was made up of two
elements:
* An ``IOUState``, representing IOUs on the ledger
* An ``IOUState``, representing IOUs on the blockchain
* An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
However, our CorDapp did not impose any constraints on the evolution of IOUs on the ledger over time. Anyone was free
However, our CorDapp did not impose any constraints on the evolution of IOUs on the blockchain over time. Anyone was free
to create IOUs of any value, between any party.
In this tutorial, we'll write a contract to imposes rules on how an ``IOUState`` can change over time. In turn, this