diff --git a/docs/source/hello-world-contract.rst b/docs/source/hello-world-contract.rst index d5e4de152f..7e367ff7eb 100644 --- a/docs/source/hello-world-contract.rst +++ b/docs/source/hello-world-contract.rst @@ -41,7 +41,8 @@ Just as every Corda state must implement the ``ContractState`` interface, every You can read about function declarations in Kotlin `here `_. -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. diff --git a/docs/source/hello-world-flow.rst b/docs/source/hello-world-flow.rst index e3bd8db52b..351888fd0a 100644 --- a/docs/source/hello-world-flow.rst +++ b/docs/source/hello-world-flow.rst @@ -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 diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 6bc74a3117..7bf00e4771 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -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 ---------- diff --git a/docs/source/hello-world-state.rst b/docs/source/hello-world-state.rst index 1677e3276a..dbcbe7c4b7 100644 --- a/docs/source/hello-world-state.rst +++ b/docs/source/hello-world-state.rst @@ -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 } @@ -38,13 +35,10 @@ If you do want to dive into Kotlin, there's an official `getting started guide `_, and a series of `Kotlin Koans `_. -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 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 diff --git a/docs/source/resources/tutorial-state.png b/docs/source/resources/tutorial-state.png index 11b0a0d385..d621277ba9 100644 Binary files a/docs/source/resources/tutorial-state.png and b/docs/source/resources/tutorial-state.png differ diff --git a/docs/source/tut-two-party-flow.rst b/docs/source/tut-two-party-flow.rst index 9837f600c8..84acdb6f7c 100644 --- a/docs/source/tut-two-party-flow.rst +++ b/docs/source/tut-two-party-flow.rst @@ -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 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 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 ---------------------------- @@ -101,10 +125,10 @@ In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file i ... @InitiatedBy(IOUFlow::class) - class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic() { + class IOUFlowResponder(val otherPartyFlow: FlowSession) : FlowLogic() { @Suspendable override fun call() { - val signTransactionFlow = object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) { + val signTransactionFlow = object : SignTransactionFlow(otherPartyFlow, 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) @@ -124,11 +148,7 @@ In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file i 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.FlowSession; - import net.corda.core.flows.InitiatedBy; - import net.corda.core.flows.SignTransactionFlow; + import net.corda.core.flows.*; import net.corda.core.transactions.SignedTransaction; import net.corda.core.utilities.ProgressTracker; @@ -136,18 +156,18 @@ In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file i @InitiatedBy(IOUFlow.class) public class IOUFlowResponder extends FlowLogic { - private final FlowSession otherPartySession; + private final FlowSession otherPartyFlow; - public IOUFlowResponder(FlowSession otherPartySession) { - this.otherPartySession = otherPartySession; + public IOUFlowResponder(FlowSession otherPartyFlow) { + this.otherPartyFlow = otherPartyFlow; } @Suspendable @Override public Void call() throws FlowException { - class SignTxFlow extends SignTransactionFlow { - private signTxFlow(FlowSession otherPartySession, ProgressTracker progressTracker) { - super(otherPartySession, progressTracker); + class signTxFlow extends SignTransactionFlow { + private signTxFlow(FlowSession otherPartyFlow, ProgressTracker progressTracker) { + super(otherPartyFlow, progressTracker); } @Override @@ -162,7 +182,7 @@ In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file i } } - subFlow(new SignTxFlow(otherPartySession, SignTransactionFlow.Companion.tracker())); + subFlow(new signTxFlow(otherPartyFlow, SignTransactionFlow.Companion.tracker())); return null; }