Updates tutorials. (#1649)

* Updates tutorials.

* Addresses review comments.
This commit is contained in:
Joel Dudley 2017-10-02 10:04:27 +01:00 committed by josecoll
parent 569d4494d7
commit 27404005b2
6 changed files with 114 additions and 85 deletions

View File

@ -41,7 +41,8 @@ Just as every Corda state must implement the ``ContractState`` interface, every
You can read about function declarations in Kotlin `here <https://kotlinlang.org/docs/reference/functions.html>`_.
We can see that ``Contract`` expresses its constraints through a ``verify`` function that takes a transaction as input, and:
We can see that ``Contract`` expresses its constraints through a ``verify`` function that takes a transaction as input,
and:
* Throws an ``IllegalArgumentException`` if it rejects the transaction proposal
* Returns silently if it accepts the transaction proposal
@ -118,7 +119,6 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.Contract;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.crypto.SecureHash;
import net.corda.core.identity.Party;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
@ -258,7 +258,5 @@ We've now written an ``IOUContract`` constraining the evolution of each ``IOUSta
* The ``IOUState`` created by the issuance transaction must have a non-negative value, and the lender and borrower
must be different entities
Before we move on, make sure you go back and modify ``IOUState`` to point to the new ``IOUContract`` class.
The final step in the creation of our CorDapp will be to 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.

View File

@ -59,15 +59,20 @@ with the following:
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
val notary = serviceHub.networkMapCache.getAnyNotary()
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create a transaction builder
val txBuilder = TransactionBuilder(notary = notary)
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), ourIdentity.owningKey)
// We add the items to the builder.
val state = IOUState(iouValue, me, otherParty)
val cmd = Command(IOUContract.Create(), me.owningKey)
txBuilder.withItems(state, cmd)
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
@ -88,6 +93,7 @@ with the following:
import com.template.contract.IOUContract;
import com.template.state.IOUState;
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;
@ -121,17 +127,21 @@ with the following:
@Suspendable
@Override
public Void call() throws FlowException {
// We retrieve the required identities from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getAnyNotary(null);
// We retrieve the notary identity from the network map.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create a transaction builder.
final TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
String outputContract = IOUContract.class.getName();
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
Command cmd = new Command<>(new IOUContract.Create(), getOurIdentity().getOwningKey());
// We add the items to the builder.
IOUState state = new IOUState(iouValue, me, otherParty);
Command cmd = new Command(new IOUContract.Create(), me.getOwningKey());
txBuilder.withItems(state, cmd);
txBuilder.withItems(outputContractAndState, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
@ -200,7 +210,7 @@ the following transaction:
So we'll need the following:
* The output ``IOUState``
* The output ``IOUState`` and its associated contract
* A ``Create`` command listing the IOU's lender as a signer
The command we use pairs the ``IOUContract.Create`` command defined earlier with our public key. Including this command
@ -208,8 +218,8 @@ in the transaction makes us one of the transaction's required signers.
We add these items to the transaction using the ``TransactionBuilder.withItems`` method, which takes a ``vararg`` of:
* ``ContractState`` or ``TransactionState`` objects, which are added to the builder as output states
* ``StateRef`` objects (references to the outputs of previous transactions), which are added to the builder as input
* ``StateAndContract`` or ``TransactionState`` objects, which are added to the builder as output states
* ``StateAndRef`` objects (references to the outputs of previous transactions), which are added to the builder as input
state references
* ``Command`` objects, which are added to the builder as commands
* ``SecureHash`` objects, which are added to the builder as attachments

View File

@ -25,30 +25,30 @@ Let's take a look at the nodes we're going to deploy. Open the project's ``build
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
networkMap "O=Controller,OU=corda,L=London,C=UK"
networkMap "O=Controller,L=London,C=GB"
node {
name "O=Controller,OU=corda,L=London,C=UK"
name "O=Controller,L=London,C=GB"
advertisedServices = ["corda.notary.validating"]
p2pPort 10002
rpcPort 10003
cordapps = []
cordapps = ["net.corda:corda-finance:$corda_release_version"]
}
node {
name "CN=NodeA,O=NodeA,L=London,C=UK"
name "O=PartyA,L=London,C=GB"
advertisedServices = []
p2pPort 10005
rpcPort 10006
webPort 10007
cordapps = []
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
node {
name "CN=NodeB,O=NodeB,L=New York,C=US"
name "O=PartyB,L=New York,C=US"
advertisedServices = []
p2pPort 10008
rpcPort 10009
webPort 10010
cordapps = []
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
}
}
@ -111,57 +111,72 @@ Now that our nodes are running, let's order one of them to create an IOU by kick
app, we'd generally provide a web API sitting on top of our node. Here, for simplicity, we'll be interacting with the
node via its built-in CRaSH shell.
Go to the terminal window displaying the CRaSH shell of Node A. Typing ``help`` will display a list of the available
Go to the terminal window displaying the CRaSH shell of PartyA. Typing ``help`` will display a list of the available
commands.
We want to create an IOU of 100 with Node B. We start the ``IOUFlow`` by typing:
We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing:
.. container:: codeset
.. code-block:: java
start IOUFlow arg0: 99, arg1: "NodeB"
start IOUFlow arg0: 99, arg1: "O=PartyB,L=New York,C=US"
.. code-block:: kotlin
start IOUFlow iouValue: 99, otherParty: "NodeB"
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
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.
PartyA and PartyB 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 PartyA and PartyB.
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:
.. code:: python
.. container:: codeset
run vaultAndUpdates
.. code-block:: java
And we can also examine a node's transaction storage, by running:
run vaultQuery contractStateType: com.template.state.IOUState
.. code-block:: kotlin
run vaultQuery contractStateType: com.template.IOUState
The vaults of PartyA and PartyB should both display the following output:
.. code:: python
run verifiedTransactions
The vaults of Node A and Node B should both display the following output:
.. code:: python
first:
states:
- state:
data:
value: 99
lender: "CN=NodeA,O=NodeA,L=London,C=GB"
borrower: "CN=NodeB,O=NodeB,L=New York,C=US"
contract: {}
lender: "C=GB,L=London,O=PartyA"
borrower: "C=US,L=New York,O=PartyB"
participants:
- "CN=NodeA,O=NodeA,L=London,C=GB"
- "CN=NodeB,O=NodeB,L=New York,C=US"
notary: "O=Controller,OU=corda,L=London,C=GB,OU=corda.notary.validating"
- "C=GB,L=London,O=PartyA"
- "C=US,L=New York,O=PartyB"
contract: "com.template.contract.IOUContract"
notary: "C=GB,L=London,O=Controller,CN=corda.notary.validating"
encumbrance: null
constraint:
attachmentId: "F578320232CAB87BB1E919F3E5DB9D81B7346F9D7EA6D9155DC0F7BA8E472552"
ref:
txhash: "656A1BF64D5AEEC6F6C944E287F34EF133336F5FC2C5BFB9A0BFAE25E826125F"
txhash: "5CED068E790A347B0DD1C6BB5B2B463406807F95E080037208627565E6A2103B"
index: 0
second: "(observable)"
statesMetadata:
- ref:
txhash: "5CED068E790A347B0DD1C6BB5B2B463406807F95E080037208627565E6A2103B"
index: 0
contractStateClassName: "com.template.state.IOUState"
recordedTime: 1506415268.875000000
consumedTime: null
status: "UNCONSUMED"
notary: "C=GB,L=London,O=Controller,CN=corda.notary.validating"
lockId: null
lockUpdateTime: 1506415269.548000000
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
Conclusion
----------

View File

@ -20,9 +20,6 @@ defined as follows:
.. code-block:: kotlin
interface ContractState {
// The contract that imposes constraints on how this state can evolve over time.
val contract: Contract
// The list of entities considered to have a stake in this state.
val participants: List<AbstractParty>
}
@ -38,13 +35,10 @@ If you do want to dive into Kotlin, there's an official
`getting started guide <https://kotlinlang.org/docs/tutorials/>`_, and a series of
`Kotlin Koans <https://kotlinlang.org/docs/tutorials/koans.html>`_.
We can see that the ``ContractState`` interface declares two properties:
We can see that the ``ContractState`` interface has a single field, ``participants``. ``participants`` is a list of the
entities for which this state is relevant.
* ``contract``: the contract controlling transactions involving this state
* ``participants``: the list of entities that have to approve state changes such as changing the state's notary or
upgrading the state's contract
Beyond this, our state is free to define any properties, methods, helpers or inner classes it requires to accurately
Beyond this, our state is free to define any fields, methods, helpers or inner classes it requires to accurately
represent a given class of shared facts on the ledger.
``ContractState`` also has several child interfaces that you may wish to implement depending on your state, such as
@ -74,7 +68,6 @@ define an ``IOUState``:
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val contract = "net.corda.contract.TemplateContract"
override val participants get() = listOf(lender, borrower)
}
@ -83,7 +76,6 @@ define an ``IOUState``:
package com.template.state;
import com.google.common.collect.ImmutableList;
import com.template.contract.TemplateContract;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
@ -94,7 +86,6 @@ define an ``IOUState``:
private final int value;
private final Party lender;
private final Party borrower;
private final TemplateContract contract = new TemplateContract();
public IOUState(int value, Party lender, Party borrower) {
this.value = value;
@ -114,12 +105,6 @@ define an ``IOUState``:
return borrower;
}
@Override
// TODO: Once we've defined IOUContract, come back and update this.
public TemplateContract getContract() {
return contract;
}
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower);
@ -141,9 +126,6 @@ We've made the following changes:
* Actions such as changing a state's contract or notary will require approval from all the ``participants``
We've left ``IOUState``'s contract as ``TemplateContract`` for now. We'll update this once we've defined the
``IOUContract``.
Progress so far
---------------
We've defined an ``IOUState`` that can be used to represent IOUs as shared facts on the ledger. As we've seen, states in

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -29,10 +29,14 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
...
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder.
val state = IOUState(iouValue, me, otherParty)
val cmd = Command(IOUContract.Create(), listOf(me.owningKey, otherParty.owningKey))
txBuilder.withItems(state, cmd)
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
@ -40,9 +44,11 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Obtaining the counterparty's signature
val otherSession = initiateFlow(otherParty)
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, setOf(otherSession), CollectSignaturesFlow.tracker()))
// Creating a session with the other party.
val otherpartySession = initiateFlow(otherParty)
// Obtaining the counterparty's signature.
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(otherpartySession), CollectSignaturesFlow.tracker()))
// Finalising the transaction.
subFlow(FinalityFlow(fullySignedTx))
@ -58,11 +64,15 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
...
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
String outputContract = IOUContract.class.getName();
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
// We add the items to the builder.
IOUState state = new IOUState(iouValue, me, otherParty);
List<PublicKey> requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command(new IOUContract.Create(), requiredSigners);
txBuilder.withItems(state, cmd);
txBuilder.withItems(outputContractAndState, cmd);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
@ -70,20 +80,34 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
// Signing the transaction.
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Obtaining the counterparty's signature
final FlowSession otherSession = initiateFlow(otherParty)
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, Collections.singleton(otherSession), CollectSignaturesFlow.Companion.tracker()));
// Creating a session with the other party.
FlowSession otherpartySession = initiateFlow(otherParty);
// Obtaining the counterparty's signature.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, ImmutableList.of(otherpartySession), CollectSignaturesFlow.tracker()));
// Finalising the transaction.
subFlow(new FinalityFlow(fullySignedTx));
subFlow(new FinalityFlow(signedTx));
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``.
We now need to communicate with the borrower to request their signature. Whenever you want to communicate with another
party in the context of a flow, you first need to establish a flow session with them. If the counterparty has a
``FlowLogic`` registered to respond to the ``FlowLogic`` initiating the session, a session will be established. All
communication between the two ``FlowLogic`` instances will then place as part of this session.
Once we have a session with the borrower, we gather the borrower's signature using ``CollectSignaturesFlow``, which
takes:
* A transaction signed by the flow initiator
* A list of flow-sessions between the flow initiator and the required signers
And returns a transaction signed by all the required signers.
We then pass this fully-signed transaction into ``FinalityFlow``.
Creating the borrower's flow
----------------------------