mirror of
https://github.com/corda/corda.git
synced 2025-01-19 11:16:54 +00:00
Updates tutorials. (#1649)
* Updates tutorials. * Addresses review comments.
This commit is contained in:
parent
569d4494d7
commit
27404005b2
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
----------
|
||||
|
@ -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 |
@ -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
|
||||
----------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user