Tutorial refresh for v1.0 and moving of code into separate files. (#1758)

* Moves code sections in tutorials to code files.

* Removes wallet references.

* Updates repo layout doc.

* Removes remaining cordapp-tutorial references, replaced with cordapp-example.

* Fixes broken link.

* Misc docs fixes.

* Refreshes the ServiceHub and rpc ops api pages.

* Updates the cheat sheet.

* Updates cookbooks.

* Refreshes the running-a-notary tutorial.

* Updates flow-testing tutorial

* Updates tear-offs tutorial.

* Refreshes integration-testing tutorial.

* Updates to contract tutorial and accompanying code to bring inline with V1 release.

* Refreshes contract-upgrade tutorial.

* Fixed broken code sample in "writing a contract" and updated contracts dsl.

* Added contract ref to java code. Fixed broken rst markup.

* Updates transaction-building tutorial.

* Updates the client-rpc and flow-state-machines tutorials.

* Updates the oracles tutorial.

* Amended country in X500 names from "UK" to "GB"

* Update FlowCookbook.kt

* Amended cheatsheet. Minor update on contract upgrades tutoraial.

* Added `extraCordappPackagesToScan` to node driver.

* Changes to match new function signature.

* Update to reflect change in location of cash contract name.
This commit is contained in:
Joel Dudley 2017-10-02 10:11:33 +01:00 committed by josecoll
parent 27404005b2
commit f0138dfe17
42 changed files with 2257 additions and 2074 deletions

View File

@ -22,11 +22,13 @@ object ContractUpgradeFlow {
*
* This flow will NOT initiate the upgrade process. To start the upgrade process, see [Initiate].
*/
// DOCSTART 1
@StartableByRPC
class Authorise(
val stateAndRef: StateAndRef<*>,
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
) : FlowLogic<Void?>() {
// DOCEND 1
@Suspendable
override fun call(): Void? {
val upgrade = upgradedContractClass.newInstance()
@ -43,10 +45,12 @@ object ContractUpgradeFlow {
* Deauthorise a contract state upgrade.
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
*/
// DOCSTART 2
@StartableByRPC
class Deauthorise(val stateRef: StateRef) : FlowLogic<Void?>() {
@Suspendable
override fun call(): Void? {
//DOCEND 2
serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
return null
}

View File

@ -28,6 +28,7 @@ interface NetworkMapCache {
data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange()
}
// DOCSTART 1
/**
* A list of notary services available on the network.
*
@ -35,6 +36,8 @@ interface NetworkMapCache {
*/
// TODO this list will be taken from NetworkParameters distributed by NetworkMap.
val notaryIdentities: List<Party>
// DOCEND 1
/** Tracks changes to the network map cache. */
val changed: Observable<MapChange>
/** Future to track completion of the NetworkMapService registration. */
@ -81,8 +84,10 @@ interface NetworkMapCache {
/** Returns information about the party, which may be a specific node or a service */
fun getPartyInfo(party: Party): PartyInfo?
// DOCSTART 2
/** Gets a notary identity by the given name. */
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
// DOCEND 2
/** Checks whether a given party is an advertised notary identity. */
fun isNotary(party: Party): Boolean = party in notaryIdentities

View File

@ -25,6 +25,7 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull
// DOCSTART 3
class ResolveTransactionsFlowTest {
lateinit var mockNet: MockNetwork
lateinit var a: StartedNode<MockNetwork.MockNode>
@ -53,6 +54,8 @@ class ResolveTransactionsFlowTest {
mockNet.stopNodes()
unsetCordappPackages()
}
// DOCEND 3
// DOCSTART 1
@Test

View File

@ -13,6 +13,8 @@ This section describes the APIs that are available for the development of CorDap
api-transactions
api-flows
api-identity
api-service-hub
api-rpc
api-core-types
Before reading this page, you should be familiar with the :doc:`key concepts of Corda <key-concepts>`.

View File

@ -9,9 +9,7 @@ The key RPC operations exposed by the node are:
* Extract states from the node's vault based on a query criteria
* ``CordaRPCOps.vaultTrackBy``
* As above, but also returns an observable of future states matching the query
* ``CordaRPCOps.verifiedTransactions``
* Extract all transactions from the node's local storage, as well as an observable of all future transactions
* ``CordaRPCOps.networkMapUpdates``
* ``CordaRPCOps.networkMapFeed``
* A list of network nodes, and an observable of changes to the network map
* ``CordaRPCOps.registeredFlows``
* See a list of registered flows on the node
@ -19,10 +17,10 @@ The key RPC operations exposed by the node are:
* Start one of the node's registered flows
* ``CordaRPCOps.startTrackedFlowDynamic``
* As above, but also returns a progress handle for the flow
* ``CordaRPCOps.nodeIdentity``
* Returns the node's identity
* ``CordaRPCOps.nodeInfo``
* Returns information about the node
* ``CordaRPCOps.currentNodeTime``
* Returns the node's current time
* Returns the current time according to the node's clock
* ``CordaRPCOps.partyFromKey/CordaRPCOps.wellKnownPartyFromX500Name``
* Retrieves a party on the network based on a public key or X500 name
* ``CordaRPCOps.uploadAttachment``/``CordaRPCOps.openAttachment``/``CordaRPCOps.attachmentExists``

View File

@ -24,7 +24,5 @@ Additional, ``ServiceHub`` exposes the following properties:
* ``ServiceHub.loadState`` and ``ServiceHub.toStateAndRef`` to resolve a ``StateRef`` into a ``TransactionState`` or
a ``StateAndRef``
* ``ServiceHub.toSignedTransaction`` to sign a ``TransactionBuilder`` and convert it into a ``SignedTransaction``
* ``ServiceHub.createSignature`` and ``ServiceHub.addSignature`` to create and add signatures to a ``SignedTransaction``
Finally, ``ServiceHub`` exposes notary identity key via ``ServiceHub.notaryIdentityKey``.
* ``ServiceHub.signInitialTransaction`` to sign a ``TransactionBuilder`` and convert it into a ``SignedTransaction``
* ``ServiceHub.createSignature`` and ``ServiceHub.addSignature`` to create and add signatures to a ``SignedTransaction``

View File

@ -7,95 +7,93 @@
Upgrading contracts
===================
While every care is taken in development of contract code,
inevitably upgrades will be required to fix bugs (in either design or implementation).
Upgrades can involve a substitution of one version of the contract code for another or changing
to a different contract that understands how to migrate the existing state objects. State objects
refer to the contract code (by hash) they are intended for, and even where state objects can be used
with different contract versions, changing this value requires issuing a new state object.
While every care is taken in development of contract code, inevitably upgrades will be required to fix bugs (in either
design or implementation). Upgrades can involve a substitution of one version of the contract code for another or
changing to a different contract that understands how to migrate the existing state objects. When state objects are
added as outputs to transactions, they are linked to the contract code they are intended for via the
``StateAndContract`` type. Changing a state's contract only requires substituting one ``ContractClassName`` for another.
Workflow
--------
Here's the workflow for contract upgrades:
1. Two banks, A and B negotiate a trade, off-platform
1. Banks A and B negotiate a trade, off-platform
2. Banks A and B execute a protocol to construct a state object representing the trade, using contract X, and include it in a transaction (which is then signed and sent to the consensus service).
2. Banks A and B execute a flow to construct a state object representing the trade, using contract X, and include it in
a transaction (which is then signed and sent to the consensus service)
3. Time passes.
3. Time passes
4. The developer of contract X discovers a bug in the contract code, and releases a new version, contract Y. The developer will then notify all existing users (e.g. via a mailing list or CorDapp store) to stop their nodes from issuing further states with contract X.
4. The developer of contract X discovers a bug in the contract code, and releases a new version, contract Y. The
developer will then notify all existing users (e.g. via a mailing list or CorDapp store) to stop their nodes from
issuing further states with contract X
5. Banks A and B review the new contract via standard change control processes and identify the contract states they agree to upgrade (they may decide not to upgrade some contract states as these might be needed for some other obligation contract).
5. Banks A and B review the new contract via standard change control processes and identify the contract states they
agree to upgrade (they may decide not to upgrade some contract states as these might be needed for some other
obligation contract)
6. Banks A and B instruct their Corda nodes (via RPC) to be willing to upgrade state objects of contract X, to state objects for contract Y using agreed upgrade path.
6. Banks A and B instruct their Corda nodes (via RPC) to be willing to upgrade state objects with contract X to state
objects with contract Y using the agreed upgrade path
7. One of the parties initiates (``Initiator``) an upgrade of state objects referring to contract X, to a new state object referring to contract Y.
7. One of the parties (the ``Initiator``) initiates a flow to replace state objects referring to contract X with new
state objects referring to contract Y
8. A proposed transaction ``Proposal``, taking in the old state and outputting the reissued version, is created and signed with the node's private key.
8. A proposed transaction (the ``Proposal``), with the old states as input and the reissued states as outputs, is
created and signed with the node's private key
9. The ``Initiator`` node sends the proposed transaction, along with details of the new contract upgrade path its proposing, to all participants of the state object.
9. The ``Initiator`` node sends the proposed transaction, along with details of the new contract upgrade path that it
is proposing, to all participants of the state object
10. Each counterparty ``Acceptor`` verifies the proposal, signs or rejects the state reissuance accordingly, and sends a signature or rejection notification back to the initiating node.
10. Each counterparty (the ``Acceptor``s) verifies the proposal, signs or rejects the state reissuance accordingly, and
sends a signature or rejection notification back to the initiating node
11. If signatures are received from all parties, the initiating node assembles the complete signed transaction and sends it to the consensus service.
11. If signatures are received from all parties, the ``Initiator`` assembles the complete signed transaction and sends
it to the notary
Authorising an upgrade
----------------------
Each of the participants in the state for which the contract is being upgraded will have to instruct their node that
they agree to the upgrade before the upgrade can take place. The ``ContractUpgradeFlow`` is used to manage the
authorisation process. Each node administrator can use RPC to trigger either an ``Authorise`` or a ``Deauthorise`` flow
for the state in question.
Authorising upgrade
-------------------
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
Each of the participants in the upgrading contract will have to instruct their node that they are willing to upgrade the state object before the upgrade.
The ``ContractUpgradeFlow`` is used to manage the authorisation records. The administrator can use RPC to trigger either an ``Authorise`` or ``Deauthorise`` flow.
.. container:: codeset
.. sourcecode:: kotlin
/**
* Authorise a contract state upgrade.
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
* Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
* This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
*/
@StartableByRPC
class Authorise(
val stateAndRef: StateAndRef<*>,
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
) : FlowLogic<Void?>()
/**
* Deauthorise a contract state upgrade.
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
*/
@StartableByRPC
class Deauthorise(
val stateRef: StateRef
) : FlowLogic< Void?>()
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
Proposing an upgrade
--------------------
After all parties have authorised the contract upgrade for the state, one of the contract participants can initiate the
upgrade process by triggering the ``ContractUpgradeFlow.Initiate`` flow. ``Initiate`` creates a transaction including
the old state and the updated state, and sends it to each of the participants. Each participant will verify the
transaction, create a signature over it, and send the signature back to the initiator. Once all the signatures are
collected, the transaction will be notarised and persisted to every participant's vault.
After all parties have registered the intention of upgrading the contract state, one of the contract participants can initiate the upgrade process by triggering the ``Initiator`` contract upgrade flow.
The ``Initiator`` will create a new state and sent to each participant for signatures, each of the participants (Acceptor) will verify, sign the proposal and return to the initiator.
The transaction will be notarised and persisted once every participant verified and signed the upgrade proposal.
Example
-------
Suppose Bank A has entered into an agreement with Bank B which is represented by the state object
``DummyContractState`` and governed by the contract code ``DummyContract``. A few days after the exchange of contracts,
the developer of the contract code discovers a bug in the contract code.
Examples
--------
Bank A and Bank B decide to upgrade the contract to ``DummyContractV2``:
Lets assume Bank A has entered into an agreement with Bank B, and the contract is translated into contract code ``DummyContract`` with state object ``DummyContractState``.
1. The developer creates a new contract ``DummyContractV2`` extending the ``UpgradedContract`` class, and a new state
object ``DummyContractV2.State`` referencing the new contract.
A few days after the exchange of contracts, the developer of the contract code discovered a bug/misrepresentation in the contract code.
Bank A and Bank B decided to upgrade the contract to ``DummyContractV2``
1. Developer will create a new contract extending the ``UpgradedContract`` class, and a new state object ``DummyContractV2.State`` referencing the new contract.
.. literalinclude:: /../../test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt
.. literalinclude:: /../../testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
2. Bank A will instruct its node to accept the contract upgrade to ``DummyContractV2`` for the contract state.
2. Bank A instructs its node to accept the contract upgrade to ``DummyContractV2`` for the contract state.
.. container:: codeset
@ -105,9 +103,9 @@ Bank A and Bank B decided to upgrade the contract to ``DummyContractV2``
val rpcA = rpcClient.proxy()
rpcA.startFlow(ContractUpgradeFlow.Authorise(<<StateAndRef of the contract state>>, DummyContractV2::class.java))
3. Bank B now initiate the upgrade Flow, this will send a upgrade proposal to all contract participants.
Each of the participants of the contract state will sign and return the contract state upgrade proposal once they have validated and agreed with the upgrade.
The upgraded transaction state will be recorded in every participant's node at the end of the flow.
3. Bank B initiates the upgrade flow, which will send an upgrade proposal to all contract participants. Each of the
participants of the contract state will sign and return the contract state upgrade proposal once they have validated
and agreed with the upgrade. The upgraded transaction will be recorded in every participant's node by the flow.
.. container:: codeset

View File

@ -6,11 +6,11 @@ The Corda repository comprises the following folders:
* **buildSrc** contains necessary gradle plugins to build Corda
* **client** contains libraries for connecting to a node, working with it remotely and binding server-side data to
JavaFX UI
* **confidential-identities** contains experimental support for confidential identities on the ledger
* **config** contains logging configurations and the default node configuration file
* **core** containing the core Corda libraries such as crypto functions, types for Corda's building blocks: states,
contracts, transactions, attachments, etc. and some interfaces for nodes and protocols
* **docs** contains the Corda docsite in restructured text format as well as the built docs in html. The docs can be
accessed via ``/docs/index.html`` from the root of the repo
* **docs** contains the Corda docsite in restructured text format
* **experimental** contains platform improvements that are still in the experimental stage
* **finance** defines a range of elementary contracts (and associated schemas) and protocols, such as abstract fungible
assets, cash, obligation and commercial paper
@ -20,7 +20,7 @@ The Corda repository comprises the following folders:
* **node** contains the core code of the Corda node (eg: node driver, node services, messaging, persistence)
* **node-api** contains data structures shared between the node and the client module, e.g. types sent via RPC
* **samples** contains all our Corda demos and code samples
* **test-utils** contains some utilities for unit testing contracts ( the contracts testing DSL) and protocols (the
* **testing** contains some utilities for unit testing contracts (the contracts testing DSL) and flows (the
mock network) implementation
* **tools** contains the explorer which is a GUI front-end for Corda, and also the DemoBench which is a GUI tool that
allows you to run Corda nodes locally for demonstrations

View File

@ -42,7 +42,7 @@ There are two main steps to implementing scheduled events:
``nextScheduledActivity`` to be implemented which returns an optional ``ScheduledActivity`` instance.
``ScheduledActivity`` captures what ``FlowLogic`` instance each node will run, to perform the activity, and when it
will run is described by a ``java.time.Instant``. Once your state implements this interface and is tracked by the
wallet, it can expect to be queried for the next activity when committed to the wallet. The ``FlowLogic`` must be
vault, it can expect to be queried for the next activity when committed to the vault. The ``FlowLogic`` must be
annotated with ``@SchedulableFlow``.
* If nothing suitable exists, implement a ``FlowLogic`` to be executed by each node as the activity itself.
The important thing to remember is that in the current implementation, each node that is party to the transaction
@ -58,7 +58,7 @@ handler to help with obtaining a unique and secure random session. An example i
The production and consumption of ``ContractStates`` is observed by the scheduler and the activities associated with
any consumed states are unscheduled. Any newly produced states are then queried via the ``nextScheduledActivity``
method and if they do not return ``null`` then that activity is scheduled based on the content of the
``ScheduledActivity`` object returned. Be aware that this *only* happens if the wallet considers the state
``ScheduledActivity`` object returned. Be aware that this *only* happens if the vault considers the state
"relevant", for instance, because the owner of the node also owns that state. States that your node happens to
encounter but which aren't related to yourself will not have any activities scheduled.
@ -68,21 +68,13 @@ An example
Let's take an example of the interest rate swap fixings for our scheduled events. The first task is to implement the
``nextScheduledActivity`` method on the ``State``.
.. container:: codeset
.. sourcecode:: kotlin
override fun nextScheduledActivity(thisStateRef: StateRef,
flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
val nextFixingOf = nextFixingOf() ?: return null
val (instant, duration) = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name,
source = floatingLeg.indexSource,
date = nextFixingOf.forDay)
return ScheduledActivity(flowLogicRefFactory.create(TwoPartyDealFlow.FixingRoleDecider::class.java,
thisStateRef, duration), instant)
}
.. literalinclude:: ../../samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 8
The first thing this does is establish if there are any remaining fixings. If there are none, then it returns ``null``
to indicate that there is no activity to schedule. Otherwise it calculates the ``Instant`` at which the interest rate

View File

@ -24,7 +24,7 @@ class IntegrationTestingTutorial {
fun `alice bob cash exchange example`() {
// START 1
driver(startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
@ -104,7 +104,7 @@ class IntegrationTestingTutorial {
}
)
}
// END 5
}
}
}
// END 5
}

View File

@ -36,8 +36,6 @@ import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.testing.TestConstants.getALICE_KEY;
import static net.corda.testing.contracts.DummyContractKt.DUMMY_PROGRAM_ID;
// We group our two flows inside a singleton object to indicate that they work
// together.
@SuppressWarnings("unused")
public class FlowCookbookJava {
// ``InitiatorFlow`` is our first flow, and will communicate with
@ -123,20 +121,24 @@ public class FlowCookbookJava {
// A transaction generally needs a notary:
// - To prevent double-spends if the transaction has inputs
// - To serve as a timestamping authority if the transaction has a time-window
// - To serve as a timestamping authority if the transaction has a
// time-window
// We retrieve a notary from the network map.
// DOCSTART 1
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(new CordaX500Name("Notary Service", "London", "UK"));
// Alternatively, we can pick an arbitrary notary from the notary list. However, it is always preferable to
// specify which notary to use explicitly, as the notary list might change when new notaries are introduced,
// or old ones decommissioned.
CordaX500Name notaryName = new CordaX500Name("Notary Service", "London", "GB");
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(notaryName);
// Alternatively, we can pick an arbitrary notary from the notary
// list. However, it is always preferable to specify the notary
// explicitly, as the notary list might change when new notaries are
// introduced, or old ones decommissioned.
Party firstNotary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// DOCEND 1
// We may also need to identify a specific counterparty.
// Again, we do so using the network map.
// We may also need to identify a specific counterparty. We do so
// using the identity service.
// DOCSTART 2
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(new CordaX500Name("NodeA", "London", "UK"));
CordaX500Name counterPartyName = new CordaX500Name("NodeA", "London", "GB");
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(counterPartyName);
Party keyedCounterparty = getServiceHub().getIdentityService().partyFromKey(dummyPubKey);
// DOCEND 2
@ -145,6 +147,13 @@ public class FlowCookbookJava {
------------------------------*/
progressTracker.setCurrentStep(SENDING_AND_RECEIVING_DATA);
// We start by initiating a flow session with the counterparty. We
// will use this session to send and receive messages from the
// counterparty.
// DOCSTART initiateFlow
FlowSession counterpartySession = initiateFlow(counterparty);
// DOCEND initiateFlow
// We can send arbitrary data to a counterparty.
// If this is the first ``send``, the counterparty will either:
// 1. Ignore the message if they are not registered to respond
@ -156,7 +165,6 @@ public class FlowCookbookJava {
// registered to respond to this flow, and has a corresponding
// ``receive`` call.
// DOCSTART 4
FlowSession counterpartySession = initiateFlow(counterparty);
counterpartySession.send(new Object());
// DOCEND 4
@ -261,8 +269,7 @@ public class FlowCookbookJava {
// We then need to pair our output state with a contract.
// DOCSTART 47
String contractName = "net.corda.testing.contracts.DummyContract";
StateAndContract ourOutput = new StateAndContract(ourOutputState, contractName);
StateAndContract ourOutput = new StateAndContract(ourOutputState, DUMMY_PROGRAM_ID);
// DOCEND 47
// Commands pair a ``CommandData`` instance with a list of
@ -620,7 +627,7 @@ public class FlowCookbookJava {
progressTracker.setCurrentStep(RECEIVING_AND_SENDING_DATA);
// We need to respond to the messages sent by the initiator:
// 1. They sent us an ``Any`` instance
// 1. They sent us an ``Object`` instance
// 2. They waited to receive an ``Integer`` instance back
// 3. They sent a ``String`` instance and waited to receive a
// ``Boolean`` instance back

View File

@ -0,0 +1,101 @@
package net.corda.docs.java.tutorial.contract;
import net.corda.core.contracts.*;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.LedgerTransaction.InOutGroup;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.finance.utils.StateSumming.sumCashBy;
public class CommercialPaper implements Contract {
// DOCSTART 1
public static final String IOU_CONTRACT_ID = "com.example.contract.IOUContract";
// DOCEND 1
// DOCSTART 3
@Override
public void verify(LedgerTransaction tx) {
List<InOutGroup<State, State>> groups = tx.groupStates(State.class, State::withoutOwner);
CommandWithParties<Commands> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
// DOCEND 3
// DOCSTART 4
TimeWindow timeWindow = tx.getTimeWindow();
for (InOutGroup group : groups) {
List<State> inputs = group.getInputs();
List<State> outputs = group.getOutputs();
if (cmd.getValue() instanceof Commands.Move) {
State input = inputs.get(0);
requireThat(require -> {
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
require.using("the state is propagated", outputs.size() == 1);
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
return null;
});
} else if (cmd.getValue() instanceof Commands.Redeem) {
// Redemption of the paper requires movement of on-ledger cash.
State input = inputs.get(0);
Amount<Issued<Currency>> received = sumCashBy(tx.getOutputStates(), input.getOwner());
if (timeWindow == null) throw new IllegalArgumentException("Redemptions must be timestamped");
Instant time = timeWindow.getFromTime();
requireThat(require -> {
require.using("the paper must have matured", time.isAfter(input.getMaturityDate()));
require.using("the received amount equals the face value", received == input.getFaceValue());
require.using("the paper must be destroyed", outputs.size() == 0);
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
return null;
});
} else if (cmd.getValue() instanceof Commands.Issue) {
State output = outputs.get(0);
if (timeWindow == null) throw new IllegalArgumentException("Issuances must be timestamped");
Instant time = timeWindow.getUntilTime();
requireThat(require -> {
// Don't allow people to issue commercial paper under other entities identities.
require.using("output states are issued by a command signer", cmd.getSigners().contains(output.getIssuance().getParty().getOwningKey()));
require.using("output values sum to more than the inputs", output.getFaceValue().getQuantity() > 0);
require.using("the maturity date is not in the past", time.isBefore(output.getMaturityDate()));
// Don't allow an existing CP state to be replaced by this issuance.
require.using("can't reissue an existing state", inputs.isEmpty());
return null;
});
} else {
throw new IllegalArgumentException("Unrecognised command");
}
}
// DOCEND 4
}
// DOCSTART 2
public static class Commands implements CommandData {
public static class Move extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Move;
}
}
public static class Redeem extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Redeem;
}
}
public static class Issue extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Issue;
}
}
}
// DOCEND 2
}

View File

@ -0,0 +1,90 @@
package net.corda.docs.java.tutorial.contract;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.*;
import net.corda.core.crypto.NullKeys;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
// DOCSTART 1
public class State implements OwnableState {
private PartyAndReference issuance;
private AbstractParty owner;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
public State() {
} // For serialization
public State(PartyAndReference issuance, AbstractParty owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
}
public State withoutOwner() {
return new State(this.issuance, new AnonymousParty(NullKeys.NullPublicKey.INSTANCE), this.faceValue, this.maturityDate);
}
@NotNull
@Override
public CommandAndState withNewOwner(@NotNull AbstractParty newOwner) {
return new CommandAndState(new CommercialPaper.Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
}
public PartyAndReference getIssuance() {
return issuance;
}
public AbstractParty getOwner() {
return owner;
}
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
public Instant getMaturityDate() {
return maturityDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
State state = (State) o;
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(this.owner);
}
}
// DOCEND 1

View File

@ -0,0 +1,38 @@
package net.corda.docs.java.tutorial.flowstatemachines;
import net.corda.core.flows.SignTransactionFlow;
import net.corda.core.utilities.ProgressTracker;
import org.jetbrains.annotations.Nullable;
public class TutorialFlowStateMachines {
// DOCSTART 1
private final ProgressTracker progressTracker = new ProgressTracker(
RECEIVING,
VERIFYING,
SIGNING,
COLLECTING_SIGNATURES,
RECORDING
);
private static final ProgressTracker.Step RECEIVING = new ProgressTracker.Step(
"Waiting for seller trading info");
private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step(
"Verifying seller assets");
private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step(
"Generating and signing transaction proposal");
private static final ProgressTracker.Step COLLECTING_SIGNATURES = new ProgressTracker.Step(
"Collecting signatures from other parties");
private static final ProgressTracker.Step RECORDING = new ProgressTracker.Step(
"Recording completed transaction");
// DOCEND 1
// DOCSTART 2
private static final ProgressTracker.Step VERIFYING_AND_SIGNING = new ProgressTracker.Step("Verifying and signing transaction proposal") {
@Nullable
@Override
public ProgressTracker childProgressTracker() {
return SignTransactionFlow.Companion.tracker();
}
};
// DOCEND 2
}

View File

@ -0,0 +1,271 @@
package net.corda.docs.java.tutorial.testdsl;
import kotlin.Unit;
import net.corda.core.contracts.PartyAndReference;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.contracts.ICommercialPaperState;
import net.corda.finance.contracts.JavaCommercialPaper;
import net.corda.finance.contracts.asset.Cash;
import org.junit.Test;
import java.time.temporal.ChronoUnit;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy;
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
import static net.corda.finance.contracts.asset.CashUtilities.CASH_PROGRAM_ID;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.NodeTestUtils.ledger;
import static net.corda.testing.NodeTestUtils.transaction;
import static net.corda.testing.TestConstants.*;
public class CommercialPaperTest {
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
// DOCSTART 1
private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State(
getMEGA_CORP().ref(defaultRef),
getMEGA_CORP(),
issuedBy(DOLLARS(1000), getMEGA_CORP().ref(defaultRef)),
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
);
}
// DOCEND 1
// DOCSTART 2
@Test
public void simpleCP() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.attachments(JCP_PROGRAM_ID);
tx.input(JCP_PROGRAM_ID, inState);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 2
// DOCSTART 3
@Test
public void simpleCPMove() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 3
// DOCSTART 4
@Test
public void simpleCPMoveFails() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.failsWith("the state is propagated");
});
return Unit.INSTANCE;
});
}
// DOCEND 4
// DOCSTART 5
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.failsWith("the state is propagated");
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(getALICE()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 5
// DOCSTART 6
@Test
public void simpleIssuanceWithTweak() {
ledger(l -> {
l.transaction(tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(getBIG_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
});
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 6
// DOCSTART 7
@Test
public void simpleIssuanceWithTweakTopLevelTx() {
transaction(tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(getBIG_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
});
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
}
// DOCEND 7
// DOCSTART 8
@Test
public void chainCommercialPaper() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(CASH_PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
tx.attachments(CASH_PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(CASH_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 8
// DOCSTART 9
@Test
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(CASH_PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
tx.attachments(CASH_PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(CASH_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(CASH_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB()));
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
return Unit.INSTANCE;
});
}
// DOCEND 9
// DOCSTART 10
@Test
public void chainCommercialPaperTweak() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(CASH_PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
tx.attachments(CASH_PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(CASH_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(CASH_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.tweak(lw -> {
lw.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB()));
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
lw.fails();
return Unit.INSTANCE;
});
l.verifies();
return Unit.INSTANCE;
});
}
// DOCEND 10
}

View File

@ -48,7 +48,7 @@ fun main(args: Array<String>) {
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>()))
driver(driverDirectory = baseDirectory) {
driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) {
startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get()
// END 1
@ -99,10 +99,9 @@ fun main(args: Array<String>) {
}
}
waitForAllNodesToFinish()
// END 5
}
}
// END 5
// START 6
fun generateTransactions(proxy: CordaRPCOps) {

View File

@ -0,0 +1,135 @@
package net.corda.docs.tutorial.contract
import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.utils.sumCashBy
import net.corda.testing.chooseIdentityAndCert
import java.time.Instant
import java.util.*
class CommercialPaper : Contract {
// DOCSTART 8
companion object {
const val CP_PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.CommercialPaper"
}
// DOCEND 8
// DOCSTART 3
override fun verify(tx: LedgerTransaction) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates(State::withoutOwner)
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
// it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
// DOCEND 3
// DOCSTART 4
val timeWindow: TimeWindow? = tx.timeWindow
for ((inputs, outputs, _) in groups) {
when (command.value) {
is Commands.Move -> {
val input = inputs.single()
requireThat {
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
"the state is propagated" using (outputs.size == 1)
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
}
}
is Commands.Redeem -> {
// Redemption of the paper requires movement of on-ledger cash.
val input = inputs.single()
val received = tx.outputs.map { it.data }.sumCashBy(input.owner)
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" using (time >= input.maturityDate)
"the received amount equals the face value" using (received == input.faceValue)
"the paper must be destroyed" using outputs.isEmpty()
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
}
}
is Commands.Issue -> {
val output = outputs.single()
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// Don't allow people to issue commercial paper under other entities identities.
"output states are issued by a command signer" using (output.issuance.party.owningKey in command.signers)
"output values sum to more than the inputs" using (output.faceValue.quantity > 0)
"the maturity date is not in the past" using (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance.
"can't reissue an existing state" using inputs.isEmpty()
}
}
else -> throw IllegalArgumentException("Unrecognised command")
}
}
// DOCEND 4
}
// DOCSTART 2
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Redeem : TypeOnlyCommandData(), Commands
class Issue : TypeOnlyCommandData(), Commands
}
// DOCEND 2
// DOCSTART 5
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party, faceValue, maturityDate)
val stateAndContract = StateAndContract(state, CP_PROGRAM_ID)
return TransactionBuilder(notary = notary).withItems(stateAndContract, Command(Commands.Issue(), issuance.party.owningKey))
}
// DOCEND 5
// DOCSTART 6
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
tx.addInputState(paper)
val outputState = paper.state.data.withNewOwner(newOwner).ownableState
tx.addOutputState(outputState, CP_PROGRAM_ID)
tx.addCommand(Command(Commands.Move(), paper.state.data.owner.owningKey))
}
// DOCEND 6
// DOCSTART 7
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault.
Cash.generateSpend(
services = services,
tx = tx,
amount = paper.state.data.faceValue.withoutIssuer(),
to = paper.state.data.owner
)
tx.addInputState(paper)
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
}
// DOCEND 7
}
// DOCSTART 1
data class State(
val issuance: PartyAndReference,
override val owner: AbstractParty,
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant
) : OwnableState {
override val participants = listOf(owner)
fun withoutOwner() = copy(owner = AnonymousParty(NullKeys.NullPublicKey))
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(CommercialPaper.Commands.Move(), copy(owner = newOwner))
}
// DOCEND 1

View File

@ -0,0 +1,64 @@
package net.corda.docs.tutorial.flowstatemachines
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.finance.flows.TwoPartyTradeFlow
import java.util.*
// DOCSTART 1
object TwoPartyTradeFlow {
class UnacceptablePriceException(givenPrice: Amount<Currency>) : FlowException("Unacceptable price: $givenPrice")
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : FlowException() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
/**
* This object is serialised to the network and is the first flow message the seller sends to the buyer.
*
* @param payToIdentity anonymous identity of the seller, for payment to be sent to.
*/
@CordaSerializable
data class SellerTradeInfo(
val price: Amount<Currency>,
val payToIdentity: PartyAndCertificate
)
open class Seller(private val otherSideSession: FlowSession,
private val assetToSell: StateAndRef<OwnableState>,
private val price: Amount<Currency>,
private val myParty: PartyAndCertificate,
override val progressTracker: ProgressTracker = TwoPartyTradeFlow.Seller.tracker()) : FlowLogic<SignedTransaction>() {
companion object {
fun tracker() = ProgressTracker()
}
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
open class Buyer(private val sellerSession: FlowSession,
private val notary: Party,
private val acceptablePrice: Amount<Currency>,
private val typeToBuy: Class<out OwnableState>,
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
}
// DOCEND 1

View File

@ -0,0 +1,45 @@
package net.corda.docs.tutorial.tearoffs
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.FilteredTransactionVerificationException
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.contracts.Fix
import net.corda.testing.ALICE
import java.util.function.Predicate
fun main(args: Array<String>) {
// Typealias to make the example coherent.
val oracle = ALICE
val stx = Any() as SignedTransaction
// DOCSTART 1
val filtering = Predicate<Any> {
when (it) {
is Command<*> -> oracle.owningKey in it.signers && it.value is Fix
else -> false
}
}
// DOCEND 1
// DOCSTART 2
val ftx: FilteredTransaction = stx.buildFilteredTransaction(filtering)
// DOCEND 2
// DOCSTART 3
// Direct access to included commands, inputs, outputs, attachments etc.
val cmds: List<Command<*>> = ftx.commands
val ins: List<StateRef> = ftx.inputs
val timeWindow: TimeWindow? = ftx.timeWindow
// ...
// DOCEND 3
try {
ftx.verify()
} catch (e: FilteredTransactionVerificationException) {
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
}

View File

@ -0,0 +1,244 @@
package net.corda.docs.tutorial.testdsl
import net.corda.core.utilities.days
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.CP_PROGRAM_ID
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.ICommercialPaperState
import net.corda.finance.contracts.asset.*
import net.corda.testing.*
import org.junit.Test
class CommercialPaperTest {
// DOCSTART 1
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days
)
// DOCEND 1
// DOCSTART 2
@Test
fun simpleCP() {
val inState = getPaper()
ledger {
transaction {
attachments(CP_PROGRAM_ID)
input(CP_PROGRAM_ID) { inState }
this.verifies()
}
}
}
// DOCEND 2
// DOCSTART 3
@Test
fun simpleCPMove() {
val inState = getPaper()
ledger {
transaction {
input(CP_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
attachments(CP_PROGRAM_ID)
this.verifies()
}
}
}
// DOCEND 3
// DOCSTART 4
@Test
fun simpleCPMoveFails() {
val inState = getPaper()
ledger {
transaction {
input(CP_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
attachments(CP_PROGRAM_ID)
this `fails with` "the state is propagated"
}
}
}
// DOCEND 4
// DOCSTART 5
@Test
fun simpleCPMoveSuccess() {
val inState = getPaper()
ledger {
transaction {
input(CP_PROGRAM_ID) { inState }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
attachments(CP_PROGRAM_ID)
this `fails with` "the state is propagated"
output(CP_PROGRAM_ID, "alice's paper") { inState.withOwner(ALICE) }
this.verifies()
}
}
}
// DOCEND 5
// DOCSTART 6
@Test
fun `simple issuance with tweak`() {
ledger {
transaction {
output(CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID)
tweak {
// The wrong pubkey.
command(BIG_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this.verifies()
}
}
}
// DOCEND 6
// DOCSTART 7
@Test
fun `simple issuance with tweak and top level transaction`() {
transaction {
output(CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID)
tweak {
// The wrong pubkey.
command(BIG_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timeWindow(TEST_TX_TIME)
this.verifies()
}
}
// DOCEND 7
// DOCSTART 8
@Test
fun `chain commercial paper`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(CASH_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
}
}
// DOCEND 8
// DOCSTART 9
@Test
fun `chain commercial paper double spend`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(CASH_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>().withOwner(BOB) }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
this.fails()
}
}
// DOCEND 9
// DOCSTART 10
@Test
fun `chain commercial tweak`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
attachments(CASH_PROGRAM_ID)
output(CASH_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(CASH_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
tweak {
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>().withOwner(BOB) }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
this.fails()
}
this.verifies()
}
}
// DOCEND 10
}

View File

@ -86,13 +86,14 @@ Our flow has two parties (B and S for buyer and seller) and will proceed as foll
1. S sends a ``StateAndRef`` pointing to the state they want to sell to B, along with info about the price they require
B to pay.
2. B sends to S a ``SignedTransaction`` that includes the state as input, B's cash as input, the state with the new
owner key as output, and any change cash as output. It contains a single signature from B but isn't valid because
it lacks a signature from S authorising movement of the asset.
2. B sends to S a ``SignedTransaction`` that includes two inputs (the state owned by S, and cash owned by B) and three
outputs (the state now owned by B, the cash now owned by S, and any change cash still owned by B). The
``SignedTransaction`` has a single signature from B but isn't valid because it lacks a signature from S authorising
movement of the asset.
3. S signs the transaction and sends it back to B.
4. B *finalises* the transaction by sending it to the notary who checks the transaction for validity,
recording the transaction in B's local vault, and then sending it on to S who also checks it and commits
the transaction to S's local vault.
4. B *finalises* the transaction by sending it to the notary who checks the transaction for validity, recording the
transaction in B's local vault, and then sending it on to S who also checks it and commits the transaction to S's
local vault.
You can find the implementation of this flow in the file ``finance/src/main/kotlin/net/corda/finance/TwoPartyTradeFlow.kt``.
@ -109,64 +110,31 @@ each side.
.. container:: codeset
.. sourcecode:: kotlin
object TwoPartyTradeFlow {
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : FlowException("Unacceptable price: $givenPrice")
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : FlowException() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
// This object is serialised to the network and is the first flow message the seller sends to the buyer.
@CordaSerializable
data class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val sellerOwnerKey: PublicKey
)
open class Seller(val otherParty: Party,
val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val myKey: PublicKey,
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
open class Buyer(val otherParty: Party,
val notary: Party,
val acceptablePrice: Amount<Currency>,
val typeToBuy: Class<out OwnableState>) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
}
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowStateMachines.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
This code defines several classes nested inside the main ``TwoPartyTradeFlow`` singleton. Some of the classes are
simply flow messages or exceptions. The other two represent the buyer and seller side of the flow.
Going through the data needed to become a seller, we have:
- ``otherParty: Party`` - the party with which you are trading.
- ``notaryNode: NodeInfo`` - the entry in the network map for the chosen notary. See ":doc:`key-concepts-notaries`" for more
information on notaries.
- ``assetToSell: StateAndRef<OwnableState>`` - a pointer to the ledger entry that represents the thing being sold.
- ``price: Amount<Currency>`` - the agreed on price that the asset is being sold for (without an issuer constraint).
- ``myKey: PublicKey`` - the PublicKey part of the node's internal KeyPair that controls the asset being sold.
The matching PrivateKey stored in the KeyManagementService will be used to sign the transaction.
- ``otherSideSession: FlowSession`` - a flow session for communication with the buyer
- ``assetToSell: StateAndRef<OwnableState>`` - a pointer to the ledger entry that represents the thing being sold
- ``price: Amount<Currency>`` - the agreed on price that the asset is being sold for (without an issuer constraint)
- ``myParty: PartyAndCertificate`` - the certificate representing the party that controls the asset being sold
And for the buyer:
- ``sellerSession: FlowSession`` - a flow session for communication with the seller
- ``notary: Party`` - the entry in the network map for the chosen notary. See “Notaries” for more information on
notaries
- ``acceptablePrice: Amount<Currency>`` - the price that was agreed upon out of band. If the seller specifies
a price less than or equal to this, then the trade will go ahead.
a price less than or equal to this, then the trade will go ahead
- ``typeToBuy: Class<out OwnableState>`` - the type of state that is being purchased. This is used to check that the
sell side of the flow isn't trying to sell us the wrong thing, whether by accident or on purpose.
sell side of the flow isn't trying to sell us the wrong thing, whether by accident or on purpose
- ``anonymous: Boolean`` - whether to generate a fresh, anonymous public key for the transaction
Alright, so using this flow shouldn't be too hard: in the simplest case we can just create a Buyer or Seller
with the details of the trade, depending on who we are. We then have to start the flow in some way. Just
@ -191,9 +159,9 @@ and try again.
Whitelisted classes with the Corda node
---------------------------------------
For security reasons, we do not want Corda nodes to be able to receive instances of any class on the classpath
For security reasons, we do not want Corda nodes to be able to just receive instances of any class on the classpath
via messaging, since this has been exploited in other Java application containers in the past. Instead, we require
that every class contained in messages is whitelisted. Some classes are whitelisted by default (see ``DefaultWhitelist``),
every class contained in messages to be whitelisted. Some classes are whitelisted by default (see ``DefaultWhitelist``),
but others outside of that set need to be whitelisted either by using the annotation ``@CordaSerializable`` or via the
plugin framework. See :doc:`serialization`. You can see above that the ``SellerTradeInfo`` has been annotated.
@ -231,26 +199,28 @@ These will return a ``FlowProgressHandle``, which is just like a ``FlowHandle``
Implementing the seller
-----------------------
Let's implement the ``Seller.call`` method. This will be run when the flow is invoked.
Let's implement the ``Seller.call`` method that will be run when the flow is invoked.
.. container:: codeset
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 4
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 8
We start by sending information about the asset we wish to sell to the buyer. We fill out the initial flow message with
the trade info, and then call ``send``. which takes two arguments:
the trade info, and then call ``otherSideSession.send``. which takes two arguments:
- The party we wish to send the message to.
- The payload being sent.
- The party we wish to send the message to
- The payload being sent
``send`` will serialise the payload and send it to the other party automatically.
``otherSideSession.send`` will serialise the payload and send it to the other party automatically.
Next, we call a *subflow* called ``SignTransactionFlow`` (see :ref:`subflows`). ``SignTransactionFlow`` automates the
process of:
Next, we call a *subflow* called ``IdentitySyncFlow.Receive`` (see :ref:`subflows`). ``IdentitySyncFlow.Receive``
ensures that our node can de-anonymise any confidential identities in the transaction it's about to be asked to sign.
Next, we call another subflow called ``SignTransactionFlow``. ``SignTransactionFlow`` automates the process of:
* Receiving a proposed trade transaction from the buyer, with the buyer's signature attached.
* Checking that the proposed transaction is valid.
@ -273,7 +243,7 @@ OK, let's do the same for the buyer side:
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
:dedent: 8
This code is longer but no more complicated. Here are some things to pay attention to:
@ -441,7 +411,7 @@ Exception handling
Flows can throw exceptions to prematurely terminate their execution. The flow framework gives special treatment to
``FlowException`` and its subtypes. These exceptions are treated as error responses of the flow and are propagated
to all counterparties it is communicating with. The receiving flows will throw the same exception the next time they do
a ``receive`` or ``sendAndReceive`` and thus end the flow session. If the receiver was invoked via ``subFlow`` (details below)
a ``receive`` or ``sendAndReceive`` and thus end the flow session. If the receiver was invoked via ``subFlow``
then the exception can be caught there enabling re-invocation of the sub-flow.
If the exception thrown by the erroring flow is not a ``FlowException`` it will still terminate but will not propagate to
@ -474,59 +444,40 @@ A flow might declare some steps with code inside the flow class like this:
.. container:: codeset
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 8
.. sourcecode:: java
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/TutorialFlowStateMachines.java
:language: java
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
private final ProgressTracker progressTracker = new ProgressTracker(
RECEIVING,
VERIFYING,
SIGNING,
COLLECTING_SIGNATURES,
RECORDING
);
Each step exposes a label. By defining your own step types, you can export progress in a way that's both human readable
and machine readable.
private static final ProgressTracker.Step RECEIVING = new ProgressTracker.Step(
"Waiting for seller trading info");
private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step(
"Verifying seller assets");
private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step(
"Generating and signing transaction proposal");
private static final ProgressTracker.Step COLLECTING_SIGNATURES = new ProgressTracker.Step(
"Collecting signatures from other parties");
private static final ProgressTracker.Step RECORDING = new ProgressTracker.Step(
"Recording completed transaction");
Each step exposes a label. By default labels are fixed, but by subclassing ``RelabelableStep`` you can make a step
that can update its label on the fly. That's useful for steps that want to expose non-structured progress information
like the current file being downloaded. By defining your own step types, you can export progress in a way that's both
human readable and machine readable.
Progress trackers are hierarchical. Each step can be the parent for another tracker. By altering the
``ProgressTracker.childrenFor`` map, a tree of steps can be created. It's allowed to alter the hierarchy
at runtime, on the fly, and the progress renderers will adapt to that properly. This can be helpful when you don't
fully know ahead of time what steps will be required. If you *do* know what is required, configuring as much of the
hierarchy ahead of time is a good idea, as that will help the users see what is coming up. You can pre-configure
steps by overriding the ``Step`` class like this:
Progress trackers are hierarchical. Each step can be the parent for another tracker. By setting
``Step.childProgressTracker``, a tree of steps can be created. It's allowed to alter the hierarchy at runtime, on the
fly, and the progress renderers will adapt to that properly. This can be helpful when you don't fully know ahead of
time what steps will be required. If you *do* know what is required, configuring as much of the hierarchy ahead of time
is a good idea, as that will help the users see what is coming up. You can pre-configure steps by overriding the
``Step`` class like this:
.. container:: codeset
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 12
.. sourcecode:: java
private static final ProgressTracker.Step VERIFYING_AND_SIGNING = new ProgressTracker.Step("Verifying and signing transaction proposal") {
@Nullable @Override public ProgressTracker childProgressTracker() {
return SignTransactionFlow.Companion.tracker();
}
};
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/TutorialFlowStateMachines.java
:language: java
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
Every tracker has not only the steps given to it at construction time, but also the singleton
``ProgressTracker.UNSTARTED`` step and the ``ProgressTracker.DONE`` step. Once a tracker has become ``DONE`` its
@ -555,22 +506,6 @@ and linked ahead of time.
In future, the progress tracking framework will become a vital part of how exceptions, errors, and other faults are
surfaced to human operators for investigation and resolution.
Versioning
----------
Fibers involve persisting object-serialised stack frames to disk. Although we may do some R&D into in-place upgrades
in future, for now the upgrade process for flows is simple: you duplicate the code and rename it so it has a
new set of class names. Old versions of the flow can then drain out of the system whilst new versions are
initiated. When enough time has passed that no old versions are still waiting for anything to happen, the previous
copy of the code can be deleted.
Whilst kind of ugly, this is a very simple approach that should suffice for now.
.. warning:: Flows are not meant to live for months or years, and by implication they are not meant to implement entire deal
lifecycles. For instance, implementing the entire life cycle of an interest rate swap as a single flow - whilst
technically possible - would not be a good idea. The platform provides a job scheduler tool that can invoke
flows for this reason (see ":doc:`event-scheduling`")
Future features
---------------
@ -580,6 +515,6 @@ the features we have planned:
* Exception management, with a "flow hospital" tool to manually provide solutions to unavoidable
problems (e.g. the other side doesn't know the trade)
* Being able to interact with people, either via some sort of external ticketing system, or email, or a custom UI.
For example to implement human transaction authorisations.
For example to implement human transaction authorisations
* A standard library of flows that can be easily sub-classed by local developers in order to integrate internal
reporting logic, or anything else that might be required as part of a communications lifecycle.
reporting logic, or anything else that might be required as part of a communications lifecycle

View File

@ -15,74 +15,56 @@ A good example to examine for learning how to unit test flows is the ``ResolveTr
flow takes care of downloading and verifying transaction graphs, with all the needed dependencies. We start
with this basic skeleton:
.. container:: codeset
.. sourcecode:: kotlin
class ResolveTransactionsFlowTest {
lateinit var mockNet: MockNetwork
lateinit var a: MockNetwork.MockNode
lateinit var b: MockNetwork.MockNode
lateinit var notary: Party
@Before
fun setup() {
mockNet = MockNetwork()
val nodes = mockNet.createSomeNodes()
a = nodes.partyNodes[0]
b = nodes.partyNodes[1]
mockNet.runNetwork()
notary = a.services.getDefaultNotary()
}
@After
fun tearDown() {
mockNet.stopNodes()
}
}
.. literalinclude:: ../../core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
We create a mock network in our ``@Before`` setup method and create a couple of nodes. We also record the identity
of the notary in our test network, which will come in handy later. We also tidy up when we're done.
Next, we write a test case:
.. literalinclude:: ../../core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt
.. literalinclude:: ../../core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
We'll take a look at the ``makeTransactions`` function in a moment. For now, it's enough to know that it returns two
``SignedTransaction`` objects, the second of which spends the first. Both transactions are known by node A
but not node B.
``SignedTransaction`` objects, the second of which spends the first. Both transactions are known by MegaCorpNode but
not MiniCorpNode.
The test logic is simple enough: we create the flow, giving it node A's identity as the target to talk to.
Then we start it on node B and use the ``mockNet.runNetwork()`` method to bounce messages around until things have
The test logic is simple enough: we create the flow, giving it MegaCorpNode's identity as the target to talk to.
Then we start it on MiniCorpNode and use the ``mockNet.runNetwork()`` method to bounce messages around until things have
settled (i.e. there are no more messages waiting to be delivered). All this is done using an in memory message
routing implementation that is fast to initialise and use. Finally, we obtain the result of the flow and do
some tests on it. We also check the contents of node B's database to see that the flow had the intended effect
some tests on it. We also check the contents of MiniCorpNode's database to see that the flow had the intended effect
on the node's persistent state.
Here's what ``makeTransactions`` looks like:
.. literalinclude:: ../../core/src/test/kotlin/net/corda/core/flows/ResolveTransactionsFlowTest.kt
.. literalinclude:: ../../core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
We're using the ``DummyContract``, a simple test smart contract which stores a single number in its states, along
with ownership and issuer information. You can issue such states, exit them and re-assign ownership (move them).
It doesn't do anything else. This code simply creates a transaction that issues a dummy state (the issuer is
``MEGA_CORP``, a pre-defined unit test identity), signs it with the test notary and MegaCorp keys and then
converts the builder to the final ``SignedTransaction``. It then does so again, but this time instead of issuing
it re-assigns ownership instead. The chain of two transactions is finally committed to node A by sending them
directly to the ``a.services.recordTransaction`` method (note that this method doesn't check the transactions are
valid) inside a ``database.transaction``. All node flows run within a database transaction in the nodes themselves,
but any time we need to use the database directly from a unit test, you need to provide a database transaction as shown
here.
it re-assigns ownership instead. The chain of two transactions is finally committed to MegaCorpNode by sending them
directly to the ``megaCorpNode.services.recordTransaction`` method (note that this method doesn't check the
transactions are valid) inside a ``database.transaction``. All node flows run within a database transaction in the
nodes themselves, but any time we need to use the database directly from a unit test, you need to provide a database
transaction as shown here.
With regards to initiated flows (see :doc:`flow-state-machines` for information on initiated and initiating flows), the
full node automatically registers them by scanning the CorDapp jars. In a unit test environment this is not possible so
``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow.
And that's it: you can explore the documentation for the `MockNetwork API <api/kotlin/corda/net.corda.testing.node/-mock-network/index.html>`_
And that's it: you can explore the documentation for the
`MockNetwork API <api/kotlin/corda/net.corda.testing.node/-mock-network/index.html>`_
here.

View File

@ -74,14 +74,14 @@ IntelliJ
Download a sample project
^^^^^^^^^^^^^^^^^^^^^^^^^
1. Open a command prompt
2. Clone the CorDapp tutorial repo by running ``git clone https://github.com/corda/cordapp-tutorial``
3. Move into the cordapp-tutorial folder by running ``cd cordapp-tutorial``
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
3. Move into the cordapp-example folder by running ``cd cordapp-example``
4. Retrieve a list of all the releases by running ``git branch -a --list``
5. Check out the latest milestone release by running ``git checkout release-MX`` (where "X" is the latest milestone)
Run from the command prompt
^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. From the cordapp-tutorial folder, deploy the nodes by running ``gradlew deployNodes``
1. From the cordapp-example folder, deploy the nodes by running ``gradlew deployNodes``
2. Start the nodes by running ``call kotlin-source/build/nodes/runnodes.bat``
3. Wait until all the terminal windows display either "Webserver started up in XX.X sec" or "Node for "NodeC" started up and registered in XX.XX sec"
4. Test the CorDapp is running correctly by visiting the front end at http://localhost:10007/web/example/
@ -89,7 +89,7 @@ Run from the command prompt
Run from IntelliJ
^^^^^^^^^^^^^^^^^
1. Open IntelliJ Community Edition
2. On the splash screen, click "Open" (do NOT click "Import Project") and select the cordapp-tutorial folder
2. On the splash screen, click "Open" (do NOT click "Import Project") and select the cordapp-example folder
.. warning:: If you click "Import Project" instead of "Open", the project's run configurations will be erased!
@ -123,14 +123,14 @@ IntelliJ
Download a sample project
^^^^^^^^^^^^^^^^^^^^^^^^^
1. Open a terminal
2. Clone the CorDapp tutorial repo by running ``git clone https://github.com/corda/cordapp-tutorial``
3. Move into the cordapp-tutorial folder by running ``cd cordapp-tutorial``
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
3. Move into the cordapp-example folder by running ``cd cordapp-example``
4. Retrieve a list of all the releases by running ``git branch -a --list``
5. Check out the latest milestone release by running ``git checkout release-MX`` (where "X" is the latest milestone)
Run from the terminal
^^^^^^^^^^^^^^^^^^^^^
1. From the cordapp-tutorial folder, deploy the nodes by running ``./gradlew deployNodes``
1. From the cordapp-example folder, deploy the nodes by running ``./gradlew deployNodes``
2. Start the nodes by running ``kotlin-source/build/nodes/runnodes``. Do not click while 8 additional terminal windows start up.
3. Wait until all the terminal windows display either "Webserver started up in XX.X sec" or "Node for "NodeC" started up and registered in XX.XX sec"
4. Test the CorDapp is running correctly by visiting the front end at http://localhost:10007/web/example/
@ -138,7 +138,7 @@ Run from the terminal
Run from IntelliJ
^^^^^^^^^^^^^^^^^
1. Open IntelliJ Community Edition
2. On the splash screen, click "Open" (do NOT click "Import Project") and select the cordapp-tutorial folder
2. On the splash screen, click "Open" (do NOT click "Import Project") and select the cordapp-example folder
3. Once the project is open, click "File > Project Structure". Under "Project SDK:", set the project SDK by clicking "New...", clicking "JDK", and navigating to /Library/Java/JavaVirtualMachines/jdk1.8.0_XXX (where "XXX" is the latest minor version number). Click "OK".
4. Click "View > Tool Windows > Event Log", and click "Import Gradle project", then "OK". Wait, and click "OK" again when the "Gradle Project Data To Import" window appears
5. Wait for indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing is complete)
@ -161,7 +161,7 @@ A CorDapp template that you can use as the basis for your own CorDapps is availa
And a simple example CorDapp for you to explore basic concepts is available here:
https://github.com/corda/cordapp-tutorial.git
https://github.com/corda/cordapp-example.git
You can clone these repos to your local machine by running the command ``git clone [repo URL]``.
@ -170,8 +170,8 @@ instead by running ``git checkout release-MX`` (where “X” is the latest mile
Next steps
----------
The best way to check that everything is working fine is by running the :doc:`tutorial CorDapp <tutorial-cordapp>` and
the :doc:`samples <running-the-demos>`.
The best way to check that everything is working fine is by taking a deeper look at the
:doc:`example CorDapp <tutorial-cordapp>`.
Next, you should read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.

View File

@ -200,7 +200,7 @@ There are a number of improvements we could make to this CorDapp:
* We could add an API, to make it easier to interact with the CorDapp
We will explore some of these improvements in future tutorials. But 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
CorDapps. There's `a more fleshed-out version of the IOU CorDapp <https://github.com/corda/cordapp-example>`_ 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>`.

View File

@ -55,7 +55,7 @@ node's owner does not interact with other network nodes directly.
RPC interface
-------------
The node's owner interacts with the node via remote procedure calls (RPC). The key RPC operations the node exposes
are documented in :doc:``api-rpc``.
are documented in :doc:`api-rpc`.
The service hub
---------------

View File

@ -17,25 +17,25 @@ Introduction to oracles
-----------------------
Oracles are a key concept in the block chain/decentralised ledger space. They can be essential for many kinds of
application, because we often wish to condition a transaction on some fact being true or false, but the ledger itself
application, because we often wish to condition the validity of a transaction on some fact being true or false, but the ledger itself
has a design that is essentially functional: all transactions are *pure* and *immutable*. Phrased another way, a
smart contract cannot perform any input/output or depend on any state outside of the transaction itself. There is no
way to download a web page or interact with the user, in a smart contract. It must be this way because everyone must
be able to independently check a transaction and arrive at an identical conclusion for the ledger to maintain its
contract cannot perform any input/output or depend on any state outside of the transaction itself. For example, there is no
way to download a web page or interact with the user from within a contract. It must be this way because everyone must
be able to independently check a transaction and arrive at an identical conclusion regarding its validity for the ledger to maintain its
integrity: if a transaction could evaluate to "valid" on one computer and then "invalid" a few minutes later on a
different computer, the entire shared ledger concept wouldn't work.
But it is often essential that transactions do depend on data from the outside world, for example, verifying that an
But transaction validity does often depend on data from the outside world - verifying that an
interest rate swap is paying out correctly may require data on interest rates, verifying that a loan has reached
maturity requires knowledge about the current time, knowing which side of a bet receives the payment may require
arbitrary facts about the real world (e.g. the bankruptcy or solvency of a company or country) ... and so on.
arbitrary facts about the real world (e.g. the bankruptcy or solvency of a company or country), and so on.
We can solve this problem by introducing services that create digitally signed data structures which assert facts.
These structures can then be used as an input to a transaction and distributed with the transaction data itself. Because
the statements are themselves immutable and signed, it is impossible for an oracle to change its mind later and
invalidate transactions that were previously found to be valid. In contrast, consider what would happen if a contract
could do an HTTP request: it's possible that an answer would change after being downloaded, resulting in loss of
consensus (breaks).
consensus.
The two basic approaches
~~~~~~~~~~~~~~~~~~~~~~~~
@ -78,7 +78,7 @@ Asserting continuously varying data
Let's look at the interest rates oracle that can be found in the ``NodeInterestRates`` file. This is an example of
an oracle that uses a command because the current interest rate fix is a constantly changing fact.
The obvious way to implement such a service is like this:
The obvious way to implement such a service is this:
1. The creator of the transaction that depends on the interest rate sends it to the oracle.
2. The oracle inserts a command with the rate and signs the transaction.
@ -101,40 +101,38 @@ class that binds it to the network layer.
Here is an extract from the ``NodeInterestRates.Oracle`` class and supporting types:
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
.. sourcecode:: kotlin
/** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
class Oracle {
fun query(queries: List<FixOf>, deadline: Instant): List<Fix>
fun query(queries: List<FixOf>): List<Fix>
fun sign(ftx: FilteredTransaction, txId: SecureHash): DigitalSignature.WithKey
fun sign(ftx: FilteredTransaction): TransactionSignature
}
Because the fix contains a timestamp (the ``forDay`` field), that identifies the version of the data being requested,
there can be an arbitrary delay between a fix being requested via ``query`` and the signature being requested via ``sign``
as the Oracle can know which, potentially historical, value it is being asked to sign for. This is an important
technique for continously varying data.
The ``query`` method takes a deadline, which is a point in time the requester is willing to wait until for the necessary
data to be available. Not every oracle will need this. This can be useful where data is expected to be available on a
particular schedule and we use scheduling functionality to automatically launch the processing associated with it.
We can schedule for the expected announcement (or publish) time and give a suitable deadline at which the lack of the
information being available and the delay to processing becomes significant and may need to be escalated.
The fix contains a timestamp (the ``forDay`` field) that identifies the version of the data being requested. Since
there can be an arbitrary delay between a fix being requested via ``query`` and the signature being requested via
``sign``, this timestamp allows the Oracle to know which, potentially historical, value it is being asked to sign for. This is an
important technique for continuously varying data.
Hiding transaction data from the oracle
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Because the transaction is sent to the oracle for signing, ordinarily the oracle would be able to see the entire contents
of that transaction including the inputs, output contract states and all the commands, not just the one (in this case)
relevant command. This is an obvious privacy leak for the other participants. We currently solve this with
``FilteredTransaction``-s and the use of Merkle Trees. These reveal only the necessary parts of the transaction to the
oracle but still allow it to sign it by providing the Merkle hashes for the remaining parts. See :doc:`merkle-trees` for
more details.
relevant command. This is an obvious privacy leak for the other participants. We currently solve this using a
``FilteredTransaction``, which implements a Merkle Tree. These reveal only the necessary parts of the transaction to the
oracle but still allow it to sign it by providing the Merkle hashes for the remaining parts. See :doc:`key-concepts-oracles`
for more details.
Pay-per-play oracles
~~~~~~~~~~~~~~~~~~~~
@ -143,7 +141,7 @@ Because the signature covers the transaction, and transactions may end up being
is independently checkable. However, this approach can still be useful when the data itself costs money, because the act
of issuing the signature in the first place can be charged for (e.g. by requiring the submission of a fresh
``Cash.State`` that has been re-assigned to a key owned by the oracle service). Because the signature covers the
*transaction* and not only the *fact*, this allows for a kind of weak pseudo-DRM over data feeds. Whilst a smart
*transaction* and not only the *fact*, this allows for a kind of weak pseudo-DRM over data feeds. Whilst a
contract could in theory include a transaction parsing and signature checking library, writing a contract in this way
would be conclusive evidence of intent to disobey the rules of the service (*res ipsa loquitur*). In an environment
where parties are legally identifiable, usage of such a contract would by itself be sufficient to trigger some sort of
@ -156,24 +154,12 @@ Implement the core classes
~~~~~~~~~~~~~~~~~~~~~~~~~~
The key is to implement your oracle in a similar way to the ``NodeInterestRates.Oracle`` outline we gave above with
both ``query`` and ``sign`` methods. Typically you would want one class that encapsulates the parameters to the ``query``
method (``FixOf`` above), and a ``CommandData`` implementation (``Fix`` above) that encapsulates both an instance of
both a ``query`` and a ``sign`` method. Typically you would want one class that encapsulates the parameters to the ``query``
method (``FixOf``, above), and a ``CommandData`` implementation (``Fix``, above) that encapsulates both an instance of
that parameter class and an instance of whatever the result of the ``query`` is (``BigDecimal`` above).
The ``NodeInterestRates.Oracle`` allows querying for multiple ``Fix``-es but that is not necessary and is
provided for the convenience of callers who might need multiple and can do it all in one query request. Likewise
the *deadline* functionality is optional and can be avoided initially.
Let's see what parameters we pass to the constructor of this oracle.
.. sourcecode:: kotlin
class Oracle(val identity: Party, private val signingKey: PublicKey, val clock: Clock) = TODO()
Here we see the oracle needs to have its own identity, so it can check which transaction commands it is expected to
sign for, and also needs the PublicKey portion of its signing key. Later this PublicKey will be passed to the KeyManagementService
to identify the internal PrivateKey used for transaction signing.
The clock is used for the deadline functionality which we will not discuss further here.
The ``NodeInterestRates.Oracle`` allows querying for multiple ``Fix`` objects but that is not necessary and is
provided for the convenience of callers who need multiple fixes and want to be able to do it all in one query request.
Assuming you have a data source and can query it, it should be very easy to implement your ``query`` method and the
parameter and ``CommandData`` classes.
@ -184,16 +170,17 @@ Let's see how the ``sign`` method for ``NodeInterestRates.Oracle`` is written:
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 8
Here we can see that there are several steps:
1. Ensure that the transaction we have been sent is indeed valid and passes verification, even though we cannot see all
of it.
of it
2. Check that we only received commands as expected, and each of those commands expects us to sign for them and is of
the expected type (``Fix`` here).
the expected type (``Fix`` here)
3. Iterate over each of the commands we identified in the last step and check that the data they represent matches
exactly our data source. The final step, assuming we have got this far, is to generate a signature for the
transaction and return it.
transaction and return it
Binding to the network
~~~~~~~~~~~~~~~~~~~~~~
@ -209,6 +196,7 @@ done:
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
The Corda node scans for any class with this annotation and initialises them. The only requirement is that the class provide
a constructor with a single parameter of type ``ServiceHub``.
@ -217,9 +205,10 @@ a constructor with a single parameter of type ``ServiceHub``.
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
These two flows leverage the oracle to provide the querying and signing operations. They get reference to the oracle,
which will have already been initialised by the node, using ``ServiceHub.cordappProvider``. Both flows are annotated with
which will have already been initialised by the node, using ``ServiceHub.cordaService``. Both flows are annotated with
``@InitiatedBy``. This tells the node which initiating flow (which are discussed in the next section) they are meant to
be executed with.
@ -227,17 +216,18 @@ Providing sub-flows for querying and signing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We mentioned the client sub-flow briefly above. They are the mechanism that clients, in the form of other flows, will
interact with your oracle. Typically there will be one for querying and one for signing. Let's take a look at
use to interact with your oracle. Typically there will be one for querying and one for signing. Let's take a look at
those for ``NodeInterestRates.Oracle``.
.. literalinclude:: ../../samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
You'll note that the ``FixSignFlow`` requires a ``FilterTransaction`` instance which includes only ``Fix`` commands.
You can find a further explanation of this in :doc:`merkle-trees`. Below you will see how to build such transaction with
hidden fields.
You can find a further explanation of this in :doc:`key-concepts-oracles`. Below you will see how to build such a
transaction with hidden fields.
.. _filtering_ref:
@ -252,16 +242,16 @@ called ``RatesFixFlow``. Here's the ``call`` method of that flow.
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
As you can see, this:
1. Queries the oracle for the fact using the client sub-flow for querying from above.
2. Does some quick validation.
3. Adds the command to the transaction containing the fact to be signed for by the oracle.
4. Calls an extension point that allows clients to generate output states based on the fact from the oracle.
5. Builds filtered transaction based on filtering function extended from ``RatesFixFlow``.
6. Requests the signature from the oracle using the client sub-flow for signing from above.
7. Adds the signature returned from the oracle.
1. Queries the oracle for the fact using the client sub-flow for querying defined above
2. Does some quick validation
3. Adds the command to the transaction containing the fact to be signed for by the oracle
4. Calls an extension point that allows clients to generate output states based on the fact from the oracle
5. Builds filtered transaction based on filtering function extended from ``RatesFixFlow``
6. Requests the signature from the oracle using the client sub-flow for signing from above
Here's an example of it in action from ``FixingFlow.Fixer``.
@ -269,6 +259,7 @@ Here's an example of it in action from ``FixingFlow.Fixer``.
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
.. note::
When overriding be careful when making the sub-class an anonymous or inner class (object declarations in Kotlin),
@ -279,6 +270,6 @@ Testing
-------
When unit testing, we make use of the ``MockNetwork`` which allows us to create ``MockNode`` instances. A ``MockNode``
is a simplified node suitable for tests. One feature that isn't available (and which is not suitable in unit testing
is a simplified node suitable for tests. One feature that isn't available (and which is not suitable for unit testing
anyway) is the node's ability to scan and automatically install oracles it finds in the CorDapp jars. Instead, when
working with ``MockNode``, use the ``installCordaService`` method to manually install the oracle on the relevant node.

View File

@ -6,9 +6,10 @@ At present we have several prototype notary implementations:
1. ``SimpleNotaryService`` (single node) -- commits the provided transaction input states without any validation.
2. ``ValidatingNotaryService`` (single node) -- retrieves and validates the whole transaction history
(including the given transaction) before committing.
3. ``RaftValidatingNotaryService`` (distributed) -- functionally equivalent to ``ValidatingNotaryService``, but stores
3. ``RaftNonValidatingNotaryService`` (distributed) -- functionally equivalent to ``SimpleNotaryService``, but stores
the committed states in a distributed collection replicated and persisted in a Raft cluster. For the consensus layer
we are using the `Copycat <http://atomix.io/copycat/>`_ framework.
we are using the `Copycat <http://atomix.io/copycat/>`_ framework
4. ``RaftValidatingNotaryService`` (distributed) -- as above, but performs validation on the transactions received
To have a node run a notary service, you need to set appropriate configuration values before starting it
(see :doc:`corda-configuration-file` for reference).
@ -25,5 +26,5 @@ For ``ValidatingNotaryService``, it is:
extraAdvertisedServiceIds : [ "net.corda.notary.validating" ]
Setting up a ``RaftValidatingNotaryService`` is currently slightly more involved and is not recommended for prototyping
purposes. There is work in progress to simplify it. To see it in action, however, you can try out the :ref:`notary-demo`.
Setting up a Raft notary is currently slightly more involved and is not recommended for prototyping purposes. There is
work in progress to simplify it. To see it in action, however, you can try out the :ref:`notary-demo`.

View File

@ -68,57 +68,22 @@ RPC, which returns both a snapshot and an observable of changes. The observable
transaction the node verifies is retrieved. That transaction is checked to see if it has the expected attachment
and if so, printed out.
.. sourcecode:: kotlin
.. container:: codeset
fun recipient(rpc: CordaRPCOps) {
println("Waiting to receive transaction ...")
val stx = rpc.verifiedTransactions().second.toBlocking().first()
val wtx = stx.tx
if (wtx.attachments.isNotEmpty()) {
assertEquals(PROSPECTUS_HASH, wtx.attachments.first())
require(rpc.attachmentExists(PROSPECTUS_HASH))
println("File received - we're happy!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(wtx)}")
} else {
println("Error: no attachments found in ${wtx.id}")
}
}
.. literalinclude:: ../../samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
The sender correspondingly builds a transaction with the attachment, then calls ``FinalityFlow`` to complete the
transaction and send it to the recipient node:
.. sourcecode:: kotlin
fun sender(rpc: CordaRPCOps) {
// Get the identity key of the other side (the recipient).
val otherSide: Party = rpc.wellKnownPartyFromName("Bank B")!!
// Make sure we have the file in storage
// TODO: We should have our own demo file, not share the trader demo file
if (!rpc.attachmentExists(PROSPECTUS_HASH)) {
Thread.currentThread().contextClassLoader.getResourceAsStream("bank-of-london-cp.jar").use {
val id = rpc.uploadAttachment(it)
assertEquals(PROSPECTUS_HASH, id)
}
}
// Create a trivial transaction that just passes across the attachment - in normal cases there would be
// inputs, outputs and commands that refer to this attachment.
val ptx = TransactionBuilder(notary = null)
require(rpc.attachmentExists(PROSPECTUS_HASH))
ptx.addAttachment(PROSPECTUS_HASH)
// TODO: Add a dummy state and specify a notary, so that the tx hash is randomised each time and the demo can be repeated.
// Despite not having any states, we have to have at least one signature on the transaction
ptx.signWith(ALICE_KEY)
// Send the transaction to the other recipient
val stx = ptx.toSignedTransaction()
println("Sending ${stx.id}")
val protocolHandle = rpc.startFlow(::FinalityFlow, stx, setOf(otherSide))
protocolHandle.progress.subscribe(::println)
protocolHandle.returnValue.toBlocking().first()
}
.. container:: codeset
.. literalinclude:: ../../samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
This side is a bit more complex. Firstly it looks up its counterparty by name in the network map. Then, if the node
doesn't already have the attachment in its storage, we upload it from a JAR resource and check the hash was what

View File

@ -18,67 +18,54 @@ of a flow.
The Basic Lifecycle Of Transactions
-----------------------------------
Transactions in Corda are constructed in stages and contain a number of
elements. In particular a transactions core data structure is the
``net.corda.core.transactions.WireTransaction``, which is usually
manipulated via a
``net.corda.core.transactions.TransactionBuilder`` and contains:
Transactions in Corda contain a number of elements:
1. A set of Input state references that will be consumed by the final
accepted transaction.
accepted transaction
2. A set of Output states to create/replace the consumed states and thus
become the new latest versions of data on the ledger.
become the new latest versions of data on the ledger
3. A set of ``Attachment`` items which can contain legal documents, contract
code, or private encrypted sections as an extension beyond the native
contract states.
code, or private encrypted sections as an extension beyond the native
contract states
4. A set of ``Command`` items which give a context to the type of ledger
transition that is encoded in the transaction. Also each command has an
associated set of signer keys, which will be required to sign the
transaction.
4. A set of ``Command`` items which indicate the type of ledger
transition that is encoded in the transaction. Each command also has an
associated set of signer keys, which will be required to sign the
transaction
5. A signers list, which is populated by the ``TransactionBuilder`` to
be the union of the signers on the individual Command objects.
5. A signers list, which is the union of the signers on the individual
Command objects
6. A notary identity to specify the Notary node which is tracking the
state consumption. (If the input states are registered with different
notary nodes the flow will have to insert additional ``NotaryChange``
transactions to migrate the states across to a consistent notary node,
before being allowed to mutate any states.)
6. A notary identity to specify which notary node is tracking the
state consumption (if the transaction's input states are registered with different
notary nodes the flow will have to insert additional ``NotaryChange``
transactions to migrate the states across to a consistent notary node
before being allowed to mutate any states)
7. Optionally a timestamp that can used in the Notary to time bound the
period in which the proposed transaction stays valid.
7. Optionally a timestamp that can used by the notary to bound the
period during which the proposed transaction can be committed to the
ledger
Typically, the ``WireTransaction`` should be regarded as a proposal and
may need to be exchanged back and forth between parties before it can be
fully populated. This is an immediate consequence of the Corda privacy
model, which means that the input states are likely to be unknown to the
other node.
A transaction is built by populating a ``TransactionBuilder``. Typically,
the ``TransactionBuilder`` will need to be exchanged back and forth between
parties before it is fully populated. This is an immediate consequence of
the Corda privacy model, in which the input states are likely to be unknown
to the other node.
Once the proposed data is fully populated the flow code should freeze
the ``WireTransaction`` and form a ``SignedTransaction``. This is key to
the ledger agreement process, as once a flow has attached a nodes
signature it has stated that all details of the transaction are
acceptable to it. A flow should take care not to attach signatures to
intermediate data, which might be maliciously used to construct a
different ``SignedTransaction``. For instance in a foreign exchange
scenario we shouldn't send a ``SignedTransaction`` with only our sell
side populated as that could be used to take the money without the
expected return of the other currency. Also, it is best practice for
flows to receive back the ``DigitalSignature.WithKey`` of other parties
rather than a full ``SignedTransaction`` objects, because otherwise we
have to separately check that this is still the same
Once the builder is fully populated, the flow should freeze the ``TransactionBuilder`` by signing it to create a
``SignedTransaction``. This is key to the ledger agreement process - once a flow has attached a nodes signature to a
transaction, it has effectively stated that it accepts all the details of the transaction.
It is best practice for flows to receive back the ``TransactionSignature`` of other parties rather than a full
``SignedTransaction`` objects, because otherwise we have to separately check that this is still the same
``SignedTransaction`` and not a malicious substitute.
The final stage of committing the transaction to the ledger is to
notarise the ``SignedTransaction``, distribute this to all appropriate
parties and record the data into the ledger. These actions are best
delegated to the ``FinalityFlow``, rather than calling the individual
steps manually. However, do note that the final broadcast to the other
nodes is asynchronous, so care must be used in unit testing to
correctly await the Vault updates.
The final stage of committing the transaction to the ledger is to notarise the ``SignedTransaction``, distribute it to
all appropriate parties and record the data into the ledger. These actions are best delegated to the ``FinalityFlow``,
rather than calling the individual steps manually. However, do note that the final broadcast to the other nodes is
asynchronous, so care must be used in unit testing to correctly await the vault updates.
Gathering Inputs
----------------
@ -87,39 +74,36 @@ One of the first steps to forming a transaction is gathering the set of
input references. This process will clearly vary according to the nature
of the business process being captured by the smart contract and the
parameterised details of the request. However, it will generally involve
searching the Vault via the ``VaultService`` interface on the
searching the vault via the ``VaultService`` interface on the
``ServiceHub`` to locate the input states.
To give a few more specific details consider two simplified real world
scenarios. First, a basic foreign exchange Cash transaction. This
scenarios. First, a basic foreign exchange cash transaction. This
transaction needs to locate a set of funds to exchange. A flow
modelling this is implemented in ``FxTransactionBuildTutorial.kt``.
Second, a simple business model in which parties manually accept, or
reject each other's trade proposals which is implemented in
Second, a simple business model in which parties manually accept or
reject each other's trade proposals, which is implemented in
``WorkflowTransactionBuildTutorial.kt``. To run and explore these
examples using the IntelliJ IDE one can run/step the respective unit
examples using the IntelliJ IDE one can run/step through the respective unit
tests in ``FxTransactionBuildTutorialTest.kt`` and
``WorkflowTransactionBuildTutorialTest.kt``, which drive the flows as
part of a simulated in-memory network of nodes.
.. |nbsp| unicode:: 0xA0
:trim:
.. note:: Before creating the IntelliJ run configurations for these unit tests
go to Run -> Edit |nbsp| Configurations -> Defaults -> JUnit, add
``-javaagent:lib/quasar.jar -Dco.paralleluniverse.fibers.verifyInstrumentation``
``-javaagent:lib/quasar.jar``
to the VM options, and set Working directory to ``$PROJECT_DIR$``
so that the ``Quasar`` instrumentation is correctly configured.
For the Cash transaction lets assume the cash resources are using the
standard ``CashState`` in the ``:financial`` Gradle module. The Cash
For the cash transaction, lets assume we are using the
standard ``CashState`` in the ``:financial`` Gradle module. The ``Cash``
contract uses ``FungibleAsset`` states to model holdings of
interchangeable assets and allow the split/merge and summing of
interchangeable assets and allow the splitting, merging and summing of
states to meet a contractual obligation. We would normally use the
``Cash.generateSpend`` method to gather the required
amount of cash into a ``TransactionBuilder``, set the outputs and move
command. However, to elucidate more clearly example flow code is shown
here that will manually carry out the inputs queries by specifying relevant
amount of cash into a ``TransactionBuilder``, set the outputs and generate the ``Move``
command. However, to make things clearer, the example flow code shown
here will manually carry out the input queries by specifying relevant
query criteria filters to the ``tryLockFungibleStatesForSpending`` method
of the ``VaultService``.
@ -128,14 +112,11 @@ of the ``VaultService``.
:start-after: DOCSTART 1
:end-before: DOCEND 1
As a foreign exchange transaction we expect an exchange of two
currencies, so we will also require a set of input states from the other
counterparty. However, the Corda privacy model means we do not know the
other nodes states. Our flow must therefore negotiate with the other
node for them to carry out a similar query and populate the inputs (See
the ``ForeignExchangeFlow`` for more details of the exchange). Having
identified a set of Input ``StateRef`` items we can then create the
output as discussed below.
This is a foreign exchange transaction, so we expect another set of input states of another currency from a
counterparty. However, the Corda privacy model means we are not aware of the other nodes states. Our flow must
therefore ask the other node to carry out a similar query and return the additional inputs to the transaction (see the
``ForeignExchangeFlow`` for more details of the exchange). We now have all the required input ``StateRef`` items, and
can turn to gathering the outputs.
For the trade approval flow we need to implement a simple workflow
pattern. We start by recording the unconfirmed trade details in a state
@ -167,6 +148,7 @@ the ``VaultService`` as follows:
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 8
Generating Commands
-------------------
@ -187,7 +169,7 @@ environment the ``Contract.verify``, transaction is the only allowed to
use the content of the transaction to decide validity.
Another essential requirement for commands is that the correct set of
``CompositeKeys`` are added to the Command on the builder, which will be
``PublicKey`` objects are added to the ``Command`` on the builder, which will be
used to form the set of required signers on the final validated
transaction. These must correctly align with the expectations of the
``Contract.verify`` method, which should be written to defensively check
@ -200,7 +182,7 @@ exchange of assets.
Generating Outputs
------------------
Having located a set of ``StateAndRefs`` as the transaction inputs, the
Having located a ``StateAndRefs`` set as the transaction inputs, the
flow has to generate the output states. Typically, this is a simple call
to the Kotlin ``copy`` method to modify the few fields that will
transitioned in the transaction. The contract code may provide a
@ -210,7 +192,7 @@ usually sufficient, especially as it is expected that we wish to preserve
the ``linearId`` between state revisions, so that Vault queries can find
the latest revision.
For fungible contract states such as ``Cash`` it is common to distribute
For fungible contract states such as ``cash`` it is common to distribute
and split the total amount e.g. to produce a remaining balance output
state for the original owner when breaking up a large amount input
state. Remember that the result of a successful transaction is always to
@ -221,36 +203,39 @@ the total cash. For example from the demo code:
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
Building the WireTransaction
----------------------------
Building the SignedTransaction
------------------------------
Having gathered all the ingredients for the transaction we now need to
use a ``TransactionBuilder`` to construct the full ``WireTransaction``.
The initial ``TransactionBuilder`` should be created by calling the
``TransactionBuilder`` method. At this point the
Notary to associate with the states should be recorded. Then we keep
adding inputs, outputs, commands and attachments to fill the
transaction. Examples of this process are:
Having gathered all the components for the transaction we now need to use a ``TransactionBuilder`` to construct the
full ``SignedTransaction``. We instantiate a ``TransactionBuilder`` and provide a notary that will be associated with
the output states. Then we keep adding inputs, outputs, commands and attachments to complete the transaction.
Once the transaction is fully formed, we call ``ServiceHub.signInitialTransaction`` to sign the ``TransactionBuilder``
and convert it into a ``SignedTransaction``.
Examples of this process are:
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 8
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
Completing the SignedTransaction
--------------------------------
Having created an initial ``WireTransaction`` and converted this to an
initial ``SignedTransaction`` the process of verifying and forming a
full ``SignedTransaction`` begins and then completes with the
Having created an initial ``TransactionBuilder`` and converted this to a ``SignedTransaction``, the process of
verifying and forming a full ``SignedTransaction`` begins and then completes with the
notarisation. In practice this is a relatively stereotypical process,
because assuming the ``WireTransaction`` is correctly constructed the
because assuming the ``SignedTransaction`` is correctly constructed the
verification should be immediate. However, it is also important to
recheck the business details of any data received back from an external
node, because a malicious party could always modify the contents before
@ -263,12 +248,11 @@ of the transaction has not been altered by the remote parties.
The typical code therefore checks the received ``SignedTransaction``
using the ``verifySignaturesExcept`` method, excluding itself, the
notary and any other parties yet to apply their signature. The contents of the
``WireTransaction`` inside the ``SignedTransaction`` should be fully
notary and any other parties yet to apply their signature. The contents of the ``SignedTransaction`` should be fully
verified further by expanding with ``toLedgerTransaction`` and calling
``verify``. Further context specific and business checks should then be
made, because the ``Contract.verify`` is not allowed to access external
context. For example the flow may need to check that the parties are the
context. For example, the flow may need to check that the parties are the
right ones, or that the ``Command`` present on the transaction is as
expected for this specific flow. An example of this from the demo code is:
@ -276,6 +260,7 @@ expected for this specific flow. An example of this from the demo code is:
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 8
After verification the remote flow will return its signature to the
originator. The originator should apply that signature to the starting
@ -284,18 +269,15 @@ originator. The originator should apply that signature to the starting
Committing the Transaction
--------------------------
Once all the party signatures are applied to the SignedTransaction the
final step is notarisation. This involves calling ``NotaryFlow.Client``
to confirm the transaction, consume the inputs and return its confirming
signature. Then the flow should ensure that all nodes end with all
signatures and that they call ``ServiceHub.recordTransactions``. The
code for this is standardised in the ``FinalityFlow``, or more explicitly
an example is:
Once all the signatures are applied to the ``SignedTransaction``, the
final steps are notarisation and ensuring that all nodes record the fully-signed transaction. The
code for this is standardised in the ``FinalityFlow``:
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 8
Partially Visible Transactions
------------------------------
@ -307,12 +289,12 @@ a regulator, but does not wish to share that with the other trading
partner. The tear-off/Merkle tree support in Corda allows flows to send
portions of the full transaction to restrict visibility to remote
parties. To do this one can use the
``WireTransaction.buildFilteredTransaction`` extension method to produce
``SignedTransaction.buildFilteredTransaction`` extension method to produce
a ``FilteredTransaction``. The elements of the ``SignedTransaction``
which we wish to be hide will be replaced with their secure hash. The
overall transaction txid is still provable from the
overall transaction id is still provable from the
``FilteredTransaction`` preventing change of the private data, but we do
not expose that data to the other node directly. A full example of this
can be found in the ``NodeInterestRates`` Oracle code from the
``irs-demo`` project which interacts with the ``RatesFixFlow`` flow.
Also, refer to the :doc:`merkle-trees` documentation.
Also, refer to the :doc:`merkle-trees` documentation.

View File

@ -3,58 +3,58 @@
Using the client RPC API
========================
In this tutorial we will build a simple command line utility that connects to a node, creates some Cash transactions and
meanwhile dumps the transaction graph to the standard output. We will then put some simple visualisation on top. For an
explanation on how the RPC works see :doc:`clientrpc`.
In this tutorial we will build a simple command line utility that connects to a node, creates some cash transactions
and dumps the transaction graph to the standard output. We will then put some simple visualisation on top. For an
explanation on how RPC works in Corda see :doc:`clientrpc`.
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary
and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes
directly.
and a Alice node that can issue, move and exit cash.
Note how we configure the node to create a user that has permission to start the CashFlow.
Here's how we configure the node to create a user that has the permissions to start the ``CashIssueFlow``,
``CashPaymentFlow``, and ``CashExitFlow``:
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 1
:end-before: END 1
Now we can connect to the node itself using a valid RPC login. We login using the configured user.
Now we can connect to the node itself using a valid RPC user login and start generating transactions in a different
thread using ``generateTransactions`` (to be defined later):
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 2
:end-before: END 2
:dedent: 8
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``,
which exposes the full RPC interface of the node:
``proxy`` exposes the full RPC interface of the node:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: interface CordaRPCOps
:end-before: }
.. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made
available to RPC clients.
The one we need in order to dump the transaction graph is ``verifiedTransactions``. The type signature tells us that the
RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the
node will return the current snapshot and future updates done to it. Observables are described in further detail in
:doc:`clientrpc`
The RPC operation we need in order to dump the transaction graph is ``internalVerifiedTransactionsFeed``. The type
signature tells us that the RPC operation will return a list of transactions and an ``Observable`` stream. This is a
general pattern, we query some data and the node will return the current snapshot and future updates done to it.
Observables are described in further detail in :doc:`clientrpc`
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 3
:end-before: END 3
:dedent: 8
The graph will be defined by nodes and edges between them. Each node represents a transaction and edges represent
output-input relations. For now let's just print ``NODE <txhash>`` for the former and ``EDGE <txhash> <txhash>`` for the
latter.
The graph will be defined as follows:
* Each transaction is a vertex, represented by printing ``NODE <txhash>``
* Each input-output relationship is an edge, represented by prining ``EDGE <txhash> <txhash>``
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 4
:end-before: END 4
:dedent: 8
Now we just need to create the transactions themselves!
@ -65,17 +65,16 @@ Now we just need to create the transactions themselves!
We utilise several RPC functions here to query things like the notaries in the node cluster or our own vault. These RPC
functions also return ``Observable`` objects so that the node can send us updated values. However, we don't need updates
here and so we mark these observables as ``notUsed``. (As a rule, you should always either subscribe to an ``Observable``
or mark it as not used. Failing to do this will leak resources in the node.)
here and so we mark these observables as ``notUsed`` (as a rule, you should always either subscribe to an ``Observable``
or mark it as not used. Failing to do so will leak resources in the node).
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient
permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that
type-checks the arguments for us.
The RPC we need to initiate a cash transaction is ``startFlow`` which starts an arbitrary flow given sufficient
permissions to do so.
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while
listening on successfully created ones, which are dumped to the console. We just need to run it!:
listening on successfully created ones, which are dumped to the console. We just need to run it!
.. code-block:: text
@ -84,12 +83,13 @@ listening on successfully created ones, which are dumped to the console. We just
# Start it
./docs/source/example-code/build/install/docs/source/example-code/bin/client-rpc-tutorial Print
Now let's try to visualise the transaction graph. We will use a graph drawing library called graphstream_
Now let's try to visualise the transaction graph. We will use a graph drawing library called graphstream_.
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt
:language: kotlin
:start-after: START 5
:end-before: END 5
:dedent: 8
If we run the client with ``Visualise`` we should see a simple random graph being drawn as new transactions are being created.
@ -106,11 +106,9 @@ requests or responses with the Corda node. Here's an example of both ways you c
See more on plugins in :doc:`running-a-node`.
.. warning:: We will be replacing the use of Kryo in the serialization framework and so additional changes here are likely.
Security
--------
RPC credentials associated with a Client must match the permission set configured on the server Node.
RPC credentials associated with a Client must match the permission set configured on the server node.
This refers to both authentication (username and password) and role-based authorisation (a permissioned set of RPC operations an
authenticated user is entitled to run).
@ -161,5 +159,5 @@ You can then deploy and launch the nodes (Notary and Alice) as follows:
With regards to the start flow RPCs, there is an extra layer of security whereby the flow to be executed has to be
annotated with ``@StartableByRPC``. Flows without this annotation cannot execute using RPC.
See more on security in :doc:`secure-coding-guidelines`, node configuration in :doc:`corda-configuration-file` and
Cordformation in :doc:`running-a-node`.
See more on security in :doc:`secure-coding-guidelines`, node configuration in :doc:`corda-configuration-file` and
Cordformation in :doc:`running-a-node`.

View File

@ -32,26 +32,16 @@ This lifecycle for commercial paper is illustrated in the diagram below:
.. image:: resources/contract-cp.png
Where to put your code
----------------------
A CorDapp is a collection of contracts, state definitions, flows and other ways to extend the Corda platform.
To create one you would typically clone the CorDapp template project ("cordapp-template"), which provides an example
structure for the code. Alternatively you can just create a Java-style project as normal, with your choice of build
system (Maven, Gradle, etc), then add a dependency on ``net.corda.core:0.X`` where X is the milestone number you are
depending on. The core module defines the base classes used in this tutorial.
Starting the commercial paper class
-----------------------------------
A smart contract is a class that implements the ``Contract`` interface. This can be either implemented directly, as done
here, or by subclassing an abstract contract such as ``OnLedgerAsset``. The heart of any contract in Corda is the
``verify()`` function, which determined whether any given transaction is valid. This example shows how to write a
``verify()`` function from scratch.
``verify`` function, which determines whether a given transaction is valid. This example shows how to write a
``verify`` function from scratch.
You can see the full Kotlin version of this contract in the code as ``CommercialPaperLegacy``. The code in this
tutorial is available in both Kotlin and Java. You can quickly switch between them to get a feeling for how
Kotlin syntax works.
The code in this tutorial is available in both Kotlin and Java. You can quickly switch between them to get a feeling
for Kotlin's syntax.
.. container:: codeset
@ -72,13 +62,8 @@ Kotlin syntax works.
}
}
Every contract must have at least a ``verify()`` method.
.. note:: In the future there will be a way to bind legal contract prose to a smart contract implementation,
that may take precedence over the code in case of a dispute.
The verify method returns nothing. This is intentional: the function either completes correctly, or throws an exception,
in which case the transaction is rejected.
Every contract must have at least a ``verify`` method. The verify method returns nothing. This is intentional: the
function either completes correctly, or throws an exception, in which case the transaction is rejected.
So far, so simple. Now we need to define the commercial paper *state*, which represents the fact of ownership of a
piece of issued paper.
@ -90,111 +75,21 @@ A state is a class that stores data that is checked by the contract. A commercia
.. image:: resources/contract-cp-state.png
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
data class State(
val issuance: PartyAndReference,
override val owner: AbstractParty,
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant
) : OwnableState {
override val contract = "net.corda.finance.contracts.CommercialPaper"
override val participants = listOf(owner)
fun withoutOwner() = copy(owner = AnonymousParty(NullPublicKey))
override fun withNewOwner(newOwner: AbstractParty) = Pair(Commands.Move(), copy(owner = newOwner))
}
.. sourcecode:: java
public static class State implements OwnableState {
private PartyAndReference issuance;
private AbstractParty owner;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
public State() {
} // For serialization
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
}
@NotNull
@Override
public Pair<CommandData, OwnableState> withNewOwner(@NotNull AbstractParty newOwner) {
return new Pair<>(new Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
}
public PartyAndReference getIssuance() {
return issuance;
}
public AbstractParty getOwner() {
return owner;
}
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
public Instant getMaturityDate() {
return maturityDate;
}
@NotNull
@Override
public Contract getContract() {
return new JavaCommercialPaper();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
State state = (State) o;
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(this.owner);
}
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/State.java
:language: java
:start-after: DOCSTART 1
:end-before: DOCEND 1
We define a class that implements the ``ContractState`` interface.
The ``ContractState`` interface requires us to provide a ``getContract`` method that returns an instance of the
contract class itself. In future, this may change to support dynamic loading of contracts with versioning
and signing constraints, but for now this is how it's written.
We have four fields in our state:
* ``issuance``, a reference to a specific piece of commercial paper issued by some party.
@ -234,39 +129,17 @@ Let's define a few commands now:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Redeem : TypeOnlyCommandData(), Commands
class Issue : TypeOnlyCommandData(), Commands
}
.. sourcecode:: java
public static class Commands implements core.contract.Command {
public static class Move extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Move;
}
}
public static class Redeem extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Redeem;
}
}
public static class Issue extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Issue;
}
}
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java
:language: java
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
We define a simple grouping interface or static class, this gives us a type that all our commands have in common,
then we go ahead and create three commands: ``Move``, ``Redeem``, ``Issue``. ``TypeOnlyCommandData`` is a helpful utility
@ -287,22 +160,17 @@ run two contracts one time each: Cash and CommercialPaper.
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
override fun verify(tx: LedgerTransaction) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates(State::withoutOwner)
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
// it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
.. sourcecode:: java
@Override
public void verify(LedgerTransaction tx) {
List<InOutGroup<State, State>> groups = tx.groupStates(State.class, State::withoutOwner);
CommandWithParties<Command> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java
:language: java
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
We start by using the ``groupStates`` method, which takes a type and a function. State grouping is a way of ensuring
your contract can handle multiple unrelated states of the same type in the same transaction, which is needed for
@ -312,8 +180,6 @@ The second line does what the code suggests: it searches for a command object th
``CommercialPaper.Commands`` supertype, and either returns it, or throws an exception if there's zero or more than one
such command.
.. _state_ref:
Using state groups
------------------
@ -362,12 +228,12 @@ Here are some code examples:
.. sourcecode:: kotlin
// Type of groups is List<InOutGroup<State, Pair<PartyReference, Currency>>>
val groups = tx.groupStates() { it: Cash.State -> Pair(it.deposit, it.amount.currency) }
for ((inputs, outputs, key) in groups) {
// Either inputs or outputs could be empty.
val (deposit, currency) = key
val groups = tx.groupStates { it: Cash.State -> it.amount.token }
for ((inputs, outputs, key) in groups) {
// Either inputs or outputs could be empty.
val (deposit, currency) = key
...
...
}
.. sourcecode:: java
@ -414,101 +280,37 @@ in equals and hashCode.
Checking the requirements
-------------------------
After extracting the command and the groups, we then iterate over each group and verify it meets the required business
logic.
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 8
val timeWindow: TimeWindow? = tx.timeWindow
for ((inputs, outputs, key) in groups) {
when (command.value) {
is Commands.Move -> {
val input = inputs.single()
requireThat {
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
"the state is propagated" using (outputs.size == 1)
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
}
}
is Commands.Redeem -> {
// Redemption of the paper requires movement of on-ledger cash.
val input = inputs.single()
val received = tx.outputs.map{ it.data }.sumCashBy(input.owner)
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" using (time >= input.maturityDate)
"the received amount equals the face value" using (received == input.faceValue)
"the paper must be destroyed" using outputs.isEmpty()
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
}
}
is Commands.Issue -> {
val output = outputs.single()
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// Don't allow people to issue commercial paper under other entities identities.
"output states are issued by a command signer" using (output.issuance.party.owningKey in command.signers)
"output values sum to more than the inputs" using (output.faceValue.quantity > 0)
"the maturity date is not in the past" using (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance.
"can't reissue an existing state" by inputs.isEmpty()
}
}
else -> throw IllegalArgumentException("Unrecognised command")
}
}
.. sourcecode:: java
Timestamp time = tx.getTimestamp(); // Can be null/missing.
for (InOutGroup<State> group : groups) {
List<State> inputs = group.getInputs();
List<State> outputs = group.getOutputs();
// For now do not allow multiple pieces of CP to trade in a single transaction. Study this more!
State input = single(filterIsInstance(inputs, State.class));
checkState(cmd.getSigners().contains(input.getOwner()), "the transaction is signed by the owner of the CP");
if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Move) {
checkState(outputs.size() == 1, "the state is propagated");
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Redeem) {
TimeWindow timeWindow = tx.getTimeWindow();
Instant time = null == timeWindow
? null
: timeWindow.getUntilTime();
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
checkState(received.equals(input.getFaceValue()), "received amount equals the face value");
checkState(time != null && !time.isBefore(input.getMaturityDate(), "the paper must have matured");
checkState(outputs.isEmpty(), "the paper must be destroyed");
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Issue) {
// .. etc .. (see Kotlin for full definition)
}
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java
:language: java
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 8
This loop is the core logic of the contract.
The first line simply gets the timestamp out of the transaction. Timestamping of transactions is optional, so a time
may be missing here. We check for it being null later.
.. note:: In future timestamping may be mandatory for all transactions.
.. warning:: In the Kotlin version as long as we write a comparison with the transaction time first the compiler will
verify we didn't forget to check if it's missing. Unfortunately due to the need for smooth Java interop, this
check won't happen if we write e.g. ``someDate > time``, it has to be ``time < someDate``. So it's good practice to
always write the transaction timestamp first.
Next, we take one of three paths, depending on what the type of the command object is.
**If the command is a ``Move`` command:**
The first line (first three lines in Java) impose a requirement that there be a single piece of commercial paper in
this group. We do not allow multiple units of CP to be split or merged even if they are owned by the same owner. The
``single()`` method is a static *extension method* defined by the Kotlin standard library: given a list, it throws an
@ -520,23 +322,23 @@ behind the scenes, the code compiles to the same bytecodes.
Next, we check that the transaction was signed by the public key that's marked as the current owner of the commercial
paper. Because the platform has already verified all the digital signatures before the contract begins execution,
all we have to do is verify that the owner's public key was one of the keys that signed the transaction. The Java code
is straightforward: we are simply using the ``Preconditions.checkState`` method from Guava. The Kotlin version looks a little odd: we have a *requireThat* construct that looks like it's
built into the language. In fact *requireThat* is an ordinary function provided by the platform's contract API. Kotlin
supports the creation of *domain specific languages* through the intersection of several features of the language, and
we use it here to support the natural listing of requirements. To see what it compiles down to, look at the Java version.
Each ``"string" using (expression)`` statement inside a ``requireThat`` turns into an assertion that the given expression is
true, with an ``IllegalStateException`` being thrown that contains the string if not. It's just another way to write out a regular
assertion, but with the English-language requirement being put front and center.
is straightforward: we are simply using the ``Preconditions.checkState`` method from Guava. The Kotlin version looks a
little odd: we have a *requireThat* construct that looks like it's built into the language. In fact *requireThat* is an
ordinary function provided by the platform's contract API. Kotlin supports the creation of *domain specific languages*
through the intersection of several features of the language, and we use it here to support the natural listing of
requirements. To see what it compiles down to, look at the Java version. Each ``"string" using (expression)`` statement
inside a ``requireThat`` turns into an assertion that the given expression is true, with an ``IllegalStateException``
being thrown that contains the string if not. It's just another way to write out a regular assertion, but with the
English-language requirement being put front and center.
Next, we take one of two paths, depending on what the type of the command object is.
Next, we simply verify that the output state is actually present: a move is not allowed to delete the CP from the ledger.
The grouping logic already ensured that the details are identical and haven't been changed, save for the public key of
the owner.
If the command is a ``Move`` command, then we simply verify that the output state is actually present: a move is not
allowed to delete the CP from the ledger. The grouping logic already ensured that the details are identical and haven't
been changed, save for the public key of the owner.
**If the command is a ``Redeem`` command, then the requirements are more complex:**
If the command is a ``Redeem`` command, then the requirements are more complex:
1. We want to see that the face value of the CP is being moved as a cash claim against some party, that is, the
1. We still check there is a CP input state.
2. We want to see that the face value of the CP is being moved as a cash claim against some party, that is, the
issuer of the CP is really paying back the face value.
2. The transaction must be happening after the maturity date.
3. The commercial paper must *not* be propagated by this transaction: it must be deleted, by the group having no
@ -551,8 +353,9 @@ represented in the outputs! So we can see that this contract imposes a limitatio
transaction: you are not allowed to move currencies in the same transaction that the CP does not involve. This
limitation could be addressed with better APIs, if it were to be a real limitation.
Finally, we support an ``Issue`` command, to create new instances of commercial paper on the ledger. It likewise
enforces various invariants upon the issuance.
**Finally, we support an ``Issue`` command, to create new instances of commercial paper on the ledger.**
It likewise enforces various invariants upon the issuance, such as, there must be one output CP state, for instance.
This contract is simple and does not implement all the business logic a real commercial paper lifecycle
management program would. For instance, there is no logic requiring a signature from the issuer for redemption:
@ -577,7 +380,6 @@ error message.
Testing contracts with this domain specific language is covered in the separate tutorial, :doc:`tutorial-test-dsl`.
Adding a generation API to your contract
----------------------------------------
@ -602,13 +404,11 @@ a method to wrap up the issuance process:
.. container:: codeset
.. sourcecode:: kotlin
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party, faceValue, maturityDate)
return TransactionBuilder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
}
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 4
We take a reference that points to the issuing party (i.e. the caller) and which can contain any internal
bookkeeping/reference numbers that we may require. The reference field is an ideal place to put (for example) a
@ -625,12 +425,33 @@ outputs and commands to it and is designed to be passed around, potentially betw
The function we define creates a ``CommercialPaper.State`` object that mostly just uses the arguments we were given,
but it fills out the owner field of the state to be the same public key as the issuing party.
We then combine the ``CommercialPaper.State`` object with a reference to the ``CommercialPaper`` contract, which is
defined inside the contract itself
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 8
:end-before: DOCEND 8
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java
:language: java
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
This value, which is the fully qualified class name of the contract, tells the Corda platform where to find the contract
code that should be used to validate a transaction containing an output state of this contract type. Typically the contract
code will be included in the transaction as an attachment (see :doc:`tutorial-attachments`).
The returned partial transaction has a ``Command`` object as a parameter. This is a container for any object
that implements the ``CommandData`` interface, along with a list of keys that are expected to sign this transaction. In this case,
issuance requires that the issuing party sign, so we put the key of the party there.
The ``TransactionBuilder`` has a convenience ``withItems`` method that takes a variable argument list. You can pass in
any ``StateAndRef`` (input), ``ContractState`` (output) or ``Command`` objects and it'll build up the transaction
any ``StateAndRef`` (input), ``StateAndContract`` (output) or ``Command`` objects and it'll build up the transaction
for you.
There's one final thing to be aware of: we ask the caller to select a *notary* that controls this state and
@ -643,13 +464,11 @@ What about moving the paper, i.e. reassigning ownership to someone else?
.. container:: codeset
.. sourcecode:: kotlin
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
tx.addInputState(paper)
tx.addOutputState(paper.state.data.withOwner(newOwner))
tx.addCommand(Command(Commands.Move(), paper.state.data.owner.owningKey))
}
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 6
:end-before: DOCEND 6
:dedent: 4
Here, the method takes a pre-existing ``TransactionBuilder`` and adds to it. This is correct because typically
you will want to combine a sale of CP atomically with the movement of some other asset, such as cash. So both
@ -668,15 +487,11 @@ Finally, we can do redemption.
.. container:: codeset
.. sourcecode:: kotlin
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault.
Cash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner)
tx.addInputState(paper)
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
}
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt
:language: kotlin
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 4
Here we can see an example of composing contracts together. When an owner wishes to redeem the commercial paper, the
issuer (i.e. the caller) must gather cash from its vault and send the face value to the owner of the paper.
@ -747,15 +562,14 @@ Encumbrances
All contract states may be *encumbered* by up to one other state, which we call an **encumbrance**.
The encumbrance state, if present, forces additional controls over the encumbered state, since the encumbrance state contract
will also be verified during the execution of the transaction. For example, a contract state could be encumbered
with a time-lock contract state; the state is then only processable in a transaction that verifies that the time
specified in the encumbrance time-lock has passed.
The encumbrance state, if present, forces additional controls over the encumbered state, since the encumbrance state
contract will also be verified during the execution of the transaction. For example, a contract state could be
encumbered with a time-lock contract state; the state is then only processable in a transaction that verifies that the
time specified in the encumbrance time-lock has passed.
The encumbered state refers to its encumbrance by index, and the referred encumbrance state
is an output state in a particular position on the same transaction that created the encumbered state. Note that an
encumbered state that is being consumed must have its encumbrance consumed in the same transaction, otherwise the
transaction is not valid.
The encumbered state refers to its encumbrance by index, and the referred encumbrance state is an output state in a
particular position on the same transaction that created the encumbered state. Note that an encumbered state that is
being consumed must have its encumbrance consumed in the same transaction, otherwise the transaction is not valid.
The encumbrance reference is optional in the ``ContractState`` interface:

View File

@ -4,14 +4,14 @@ Integration testing
Integration testing involves bringing up nodes locally and testing
invariants about them by starting flows and inspecting their state.
In this tutorial we will bring up three nodes Alice, Bob and a
Notary. Alice will issue Cash to Bob, then Bob will send this Cash
In this tutorial we will bring up three nodes - Alice, Bob and a
notary. Alice will issue cash to Bob, then Bob will send this cash
back to Alice. We will see how to test some simple deterministic and
nondeterministic invariants in the meantime.
(Note that this example where Alice is self-issuing Cash is purely for
demonstration purposes, in reality Cash would be issued by a bank and
subsequently passed around.)
.. note:: This example where Alice is self-issuing cash is purely for
demonstration purposes, in reality, cash would be issued by a bank
and subsequently passed around.
In order to spawn nodes we will use the Driver DSL. This DSL allows
one to start up node processes from code. It manages a network map
@ -21,53 +21,55 @@ service and safe shutting down of nodes in the background.
:language: kotlin
:start-after: START 1
:end-before: END 1
:dedent: 8
The above code creates a ``User`` permissioned to start the
``CashFlow`` protocol. It then starts up Alice and Bob with this user,
allowing us to later connect to the nodes.
The above code starts three nodes:
Then the notary is started up. Note that we need to add
``ValidatingNotaryService`` as an advertised service in order for this
node to serve notary functionality. This is also where flows added in
plugins should be specified. Note also that we won't connect to the
notary directly, so there's no need to pass in the test ``User``.
* Alice, who has user permissions to start the ``CashIssueFlow`` and
``CashPaymentFlow`` flows
* Bob, who only has user permissions to start the ``CashPaymentFlow``
* A notary that offers a ``ValidatingNotaryService``. We won't connect
to the notary directly, so there's no need to provide a ``User``
The ``startNode`` function returns a future that completes once the
node is fully started. This allows starting of the nodes to be
parallel. We wait on these futures as we need the information
returned; their respective ``NodeHandles`` s. After getting the handles we
wait for both parties to register with the network map to ensure we don't
have race conditions with network map registration.
returned; their respective ``NodeHandles`` s.
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
:language: kotlin
:start-after: START 2
:end-before: END 2
:dedent: 12
Next we connect to Alice and Bob respectively from the test process
using the test user we created. Then we establish RPC links that allow
us to start flows and query state.
After getting the handles we wait for both parties to register with
the network map to ensure we don't have race conditions with network
map registration. Next we connect to Alice and Bob respectively from
the test process using the test user we created. Then we establish RPC
links that allow us to start flows and query state.
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
:language: kotlin
:start-after: START 3
:end-before: END 3
:dedent: 12
We will be interested in changes to Alice's and Bob's vault, so we
query a stream of vault updates from each.
Now that we're all set up we can finally get some Cash action going!
Now that we're all set up we can finally get some cash action going!
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
:language: kotlin
:start-after: START 4
:end-before: END 4
:dedent: 12
The first loop creates 10 threads, each starting a ``CashFlow`` flow
on the Alice node. We specify that we want to issue ``i`` dollars to
Bob, using the Notary as the notary responsible for notarising the
Bob, setting our notary as the notary responsible for notarising the
created states. Note that no notarisation will occur yet as we're not
spending any states, only entering new ones to the ledger.
spending any states, only creating new ones on the ledger.
We started the flows from different threads for the sake of the
tutorial, to demonstrate how to test non-determinism, which is what
@ -76,20 +78,22 @@ the ``expectEvents`` block does.
The Expect DSL allows ordering constraints to be checked on a stream
of events. The above code specifies that we are expecting 10 updates
to be emitted on the ``bobVaultUpdates`` stream in unspecified order
(this is what the ``parallel`` construct does). We specify a
(this is what the ``parallel`` construct does). We specify an
(otherwise optional) ``match`` predicate to identify specific updates
we are interested in, which we then print.
If we run the code written so far we should see 4 nodes starting up
(Alice,Bob,Notary + implicit Network Map service), then 10 logs of Bob
receiving 1,2,...10 dollars from Alice in some unspecified order.
(Alice, Bob, the notary and an implicit Network Map service), then
10 logs of Bob receiving 1,2,...10 dollars from Alice in some unspecified
order.
Next we want Bob to send this Cash back to Alice.
Next we want Bob to send this cash back to Alice.
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
:language: kotlin
:start-after: START 5
:end-before: END 5
:dedent: 12
This time we'll do it sequentially. We make Bob pay 1,2,..10 dollars
to Alice in order. We make sure that a the ``CashFlow`` has finished
@ -109,8 +113,7 @@ To run the complete test you can open
``example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt``
from IntelliJ and run the test, or alternatively use gradle:
.. sourcecode:: bash
# Run example-code integration tests
./gradlew docs/source/example-code:integrationTest -i
./gradlew docs/source/example-code:integrationTest -i

View File

@ -1,80 +1,53 @@
Transaction tear-offs
=====================
Example of usage
----------------
Lets focus on a code example. We want to construct a transaction with commands containing interest rate fix data as in:
:doc:`oracles`. After construction of a partial transaction, with included ``Fix`` commands in it, we want to send it
to the Oracle for checking and signing. To do so we need to specify which parts of the transaction are going to be
revealed. That can be done by constructing filtering function over fields of ``WireTransaction`` of type ``(Any) ->
Boolean``.
Suppose we want to construct a transaction that includes commands containing interest rate fix data as in
:doc:`oracles`. Before sending the transaction to the oracle to obtain its signature, we need to filter out every part
of the transaction except for the ``Fix`` commands.
To do so, we need to create a filtering function that specifies which fields of the transaction should be included.
Each field will only be included if the filtering function returns `true` when the field is passed in as input.
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/tearoffs/TutorialTearOffs.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
val partialTx = ...
val oracle: Party = ...
fun filtering(elem: Any): Boolean {
return when (elem) {
is Command -> oracleParty.owningKey in elem.signers && elem.value is Fix
else -> false
}
}
Assuming that we already assembled partialTx with some commands and know the identity of Oracle service, we construct
filtering function over commands - ``filtering``. It performs type checking and filters only ``Fix`` commands as in
IRSDemo example. Then we can construct ``FilteredTransaction``:
We can now use our filtering function to construct a ``FilteredTransaction``:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/tearoffs/TutorialTearOffs.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
val wtx: WireTransaction = partialTx.toWireTransaction()
val ftx: FilteredTransaction = wtx.buildFilteredTransaction(filtering)
In the Oracle example this step takes place in ``RatesFixFlow`` by overriding ``filtering`` function, see:
In the Oracle example this step takes place in ``RatesFixFlow`` by overriding the ``filtering`` function. See
:ref:`filtering_ref`.
``FilteredTransaction`` holds ``filteredLeaves`` (data that we wanted to reveal) and Merkle branch for them.
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/tearoffs/TutorialTearOffs.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
// Direct accsess to included commands, inputs, outputs, attachments etc.
val cmds: List<Command> = ftx.filteredLeaves.commands
val ins: List<StateRef> = ftx.filteredLeaves.inputs
val timeWindow: TimeWindow? = ftx.filteredLeaves.timeWindow
...
The following code snippet is taken from ``NodeInterestRates.kt`` and implements a signing part of an Oracle.
.. literalinclude:: ../../samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
Above code snippet is taken from ``NodeInterestRates.kt`` file and implements a signing part of an Oracle. You can
check only leaves using ``leaves.checkWithFun { check(it) }`` and then verify obtained ``FilteredTransaction`` to see
if data from ``PartialMerkleTree`` belongs to ``WireTransaction`` with provided ``id``. All you need is the root hash
of the full transaction:
.. container:: codeset
.. sourcecode:: kotlin
if (!ftx.verify(merkleRoot)){
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
Or combine the two steps together:
.. container:: codeset
.. sourcecode:: kotlin
ftx.verifyWithFunction(merkleRoot, ::check)
:dedent: 8
.. note:: The way the ``FilteredTransaction`` is constructed ensures that after signing of the root hash it's impossible to add or remove
leaves. However, it can happen that having transaction with multiple commands one party reveals only subset of them to the Oracle.
As signing is done now over the Merkle root hash, the service signs all commands of given type, even though it didn't see
all of them. This issue will be handled after implementing partial signatures.
all of them. This issue will be handled after implementing partial signatures.

View File

@ -52,27 +52,17 @@ We will start with defining helper function that returns a ``CommercialPaper`` s
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days
)
.. sourcecode:: java
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State(
getMEGA_CORP().ref(defaultRef),
getMEGA_CORP(),
issuedBy(DOLLARS(1000), getMEGA_CORP().ref(defaultRef)),
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
);
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 1
:end-before: DOCEND 1
:dedent: 4
It's a ``CommercialPaper`` issued by ``MEGA_CORP`` with face value of $1000 and maturity date in 7 days.
@ -87,7 +77,7 @@ Let's add a ``CommercialPaper`` transaction:
val inState = getPaper()
ledger {
transaction {
input(inState)
input(CommercialPaper.CP_PROGRAM_ID) { inState }
}
}
}
@ -129,65 +119,33 @@ last line of ``transaction``:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
@Test
fun simpleCP() {
val inState = getPaper()
ledger {
transaction {
input(inState)
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleCP() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 2
:end-before: DOCEND 2
:dedent: 4
Let's take a look at a transaction that fails.
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
@Test
fun simpleCPMove() {
val inState = getPaper()
ledger {
transaction {
input(inState)
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleCPMove() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 3
:end-before: DOCEND 3
:dedent: 4
When run, that code produces the following error:
@ -206,71 +164,33 @@ However we can specify that this is an intended behaviour by changing ``this.ver
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 4
@Test
fun simpleCPMoveFails() {
val inState = getPaper()
ledger {
transaction {
input(inState)
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this `fails with` "the state is propagated"
}
}
}
.. sourcecode:: java
@Test
public void simpleCPMoveFails() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.failsWith("the state is propagated");
});
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 4
We can continue to build the transaction until it ``verifies``:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 4
@Test
fun simpleCPMoveSuccess() {
val inState = getPaper()
ledger {
transaction {
input(inState)
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this `fails with` "the state is propagated"
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { inState `owned by` ALICE_PUBKEY }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.failsWith("the state is propagated");
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inState.withOwner(getALICE_PUBKEY()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 4
``output`` specifies that we want the input state to be transferred to ``ALICE`` and ``command`` adds the
``Move`` command itself, signed by the current owner of the input state, ``MEGA_CORP_PUBKEY``.
@ -283,45 +203,17 @@ What should we do if we wanted to test what happens when the wrong party signs t
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun `simple issuance with tweak`() {
ledger {
transaction {
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
tweak {
command(DUMMY_PUBKEY_1) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleIssuanceWithTweak() {
ledger(l -> {
l.transaction(tx -> {
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.tweak(tw -> {
tw.command(getDUMMY_PUBKEY_1(), new JavaCommercialPaper.Commands.Issue());
tw.timestamp(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
});
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 6
:end-before: DOCEND 6
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 6
:end-before: DOCEND 6
:dedent: 4
``tweak`` creates a local copy of the transaction. This makes possible to locally "ruin" the transaction while not
modifying the original one, allowing testing of different error conditions.
@ -332,39 +224,17 @@ ledger with a single transaction:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 4
@Test
fun `simple issuance with tweak and top level transaction`() {
transaction {
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
tweak {
command(DUMMY_PUBKEY_1) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
}
.. sourcecode:: java
@Test
public void simpleIssuanceWithTweakTopLevelTx() {
transaction(tx -> {
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.tweak(tw -> {
tw.command(getDUMMY_PUBKEY_1(), new JavaCommercialPaper.Commands.Issue());
tw.timestamp(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
});
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 4
Chaining transactions
---------------------
@ -373,72 +243,17 @@ Now that we know how to define a single transaction, let's look at how to define
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun `chain commercial paper`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
output(Cash.CP_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.CP_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void chainCommercialPaper() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.CP_PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE_PUBKEY(), null));
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.CP_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP_PUBKEY(), null));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE_PUBKEY()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 8
:end-before: DOCEND 8
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 8
:end-before: DOCEND 8
:dedent: 4
In this example we declare that ``ALICE`` has $900 but we don't care where from. For this we can use
``unverifiedTransaction``. Note how we don't need to specify ``this.verifies()``.
@ -455,182 +270,31 @@ To do so let's create a simple example that uses the same input twice:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 9
:end-before: DOCEND 9
:dedent: 4
@Test
fun `chain commercial paper double spend`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
output(Cash.CP_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.CP_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CommercialPaper.CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>() `owned by` BOB_PUBKEY }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
this.fails()
}
}
.. sourcecode:: java
@Test
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.CP_PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE_PUBKEY(), null));
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.CP_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP_PUBKEY(), null));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE_PUBKEY()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
tx.output(CommercialPaper.CP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB_PUBKEY()));
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 9
:end-before: DOCEND 9
:dedent: 4
The transactions ``verifies()`` individually, however the state was spent twice! That's why we need the global ledger
verification (``this.fails()`` at the end). As in previous examples we can use ``tweak`` to create a local copy of the whole ledger:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 10
:end-before: DOCEND 10
:dedent: 4
@Test
fun `chain commercial tweak`() {
val issuer = MEGA_CORP.ref(123)
ledger {
unverifiedTransaction {
output(Cash.CP_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.CP_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
tweak {
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CommercialPaper.CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>() `owned by` BOB_PUBKEY }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
this.fails()
}
this.verifies()
}
}
.. sourcecode:: java
@Test
public void chainCommercialPaperTweak() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.CP_PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE_PUBKEY(), null));
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.CP_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP_PUBKEY(), null));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE_PUBKEY()));
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.tweak(lw -> {
lw.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
tx.output(CommercialPaper.CP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB_PUBKEY()));
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
lw.fails();
return Unit.INSTANCE;
});
l.verifies();
return Unit.INSTANCE;
});
}
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 10
:end-before: DOCEND 10
:dedent: 4

View File

@ -15,7 +15,6 @@ Tutorials
flow-state-machines
flow-testing
running-a-notary
using-a-notary
oracles
tutorial-tear-offs
tutorial-attachments

View File

@ -1,138 +0,0 @@
Using a notary service
----------------------
This tutorial describes how to assign a notary to a newly issued state, and how to get a transaction notarised by
obtaining a signature of the required notary. It assumes some familiarity with *flows* and how to write them, as described
in :doc:`flow-state-machines`.
Assigning a notary
==================
The first step is to choose a notary and obtain its identity. Identities of all notaries on the network are kept by
the :ref:`network-map-service`. The network map cache exposes two methods for obtaining a notary:
.. sourcecode:: kotlin
/**
* Gets a notary identity by the given name.
*/
fun getNotary(name: String): Party?
/**
* Returns a notary identity advertised by any of the nodes on the network (chosen at random)
*
* @param type Limits the result to notaries of the specified type (optional)
*/
fun getAnyNotary(type: ServiceType? = null): Party?
Currently notaries can only be differentiated by name and type, but in the future the network map service will be
able to provide more metadata, such as location or legal identities of the nodes operating it.
Now, let's say we want to issue an asset and assign it to a notary named "Notary A".
The first step is to obtain the notary identity -- ``Party``:
.. sourcecode:: kotlin
val ourNotary: Party = serviceHub.networkMapCache.getNotary("Notary A")
Then we initialise the transaction builder:
.. sourcecode:: kotlin
val builder: TransactionBuilder = TransactionBuilder(notary = ourNotary)
For any output state we add to this transaction builder, ``ourNotary`` will be assigned as its notary.
Next we create a state object and assign ourselves as the owner. For this example we'll use a
``DummyContract.State``, which is a simple state that just maintains an integer and can change ownership.
.. sourcecode:: kotlin
val myIdentity = serviceHub.chooseIdentity()
val state = DummyContract.SingleOwnerState(magicNumber = 42, owner = myIdentity.owningKey)
Then we add the state as the transaction output along with the relevant command. The state will automatically be assigned
to our previously specified "Notary A".
.. sourcecode:: kotlin
builder.addOutputState(state)
val createCommand = DummyContract.Commands.Create()
builder.addCommand(Command(createCommand, myIdentity))
We then sign the transaction, build and record it to our transaction storage:
.. sourcecode:: kotlin
val mySigningKey: PublicKey = serviceHub.chooseIdentity().owningKey
val issueTransaction = serviceHub.toSignedTransaction(issueTransaction, mySigningKey)
serviceHub.recordTransactions(issueTransaction)
The transaction is recorded and we now have a state (asset) in possession that we can transfer to someone else. Note
that the issuing transaction does not need to be notarised, as it doesn't consume any input states.
Notarising a transaction
========================
Following our example for the previous section, let's say we now want to transfer our issued state to Alice.
First we obtain a reference to the state, which will be the input to our "move" transaction:
.. sourcecode:: kotlin
val stateRef = StateRef(txhash = issueTransaction.id, index = 0)
Then we create a new state -- a copy of our state but with the owner set to Alice. This is a bit more involved so
we just use a helper that handles it for us. We also assume that we already have the ``Party`` for Alice, ``aliceParty``.
.. sourcecode:: kotlin
val inputState = StateAndRef(state, stateRef)
val moveTransactionBuilder = DummyContract.withNewOwnerAndAmount(inputState, newOwner = aliceParty.owningKey)
The ``DummyContract.withNewOwnerAndAmount()`` method will a new transaction builder with our old state as the input, a new state
with Alice as the owner, and a relevant contract command for "move".
Again we sign the transaction, and build it:
.. sourcecode:: kotlin
// We build it and add our default identity signature without checking if all signatures are present,
// Note we know that the notary signature is missing, so thie SignedTransaction is still partial.
val moveTransaction = serviceHub.toSignedTransaction(moveTransactionBuilder)
Next we need to obtain a signature from the notary for the transaction to be valid. Prior to signing, the notary will
commit our old (input) state so it cannot be used again.
To manually obtain a signature from a notary we can run the ``NotaryFlow.Client`` flow. The flow will work out
which notary needs to be called based on the input states (and the timestamp command, if it's present).
.. sourcecode:: kotlin
// The subFlow() helper is available within the context of a Flow
val notarySignature: DigitalSignature = subFlow(NotaryFlow.Client(moveTransaction))
.. note:: If our input state has already been consumed in another transaction, then ``NotaryFlow`` with throw a ``NotaryException``
containing the conflict details:
.. sourcecode:: kotlin
/** Specifies the consuming transaction for the conflicting input state */
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
* Specifies the transaction id, the position of the consumed state in the inputs, and
* the caller identity requesting the commit
*/
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
Conflict handling and resolution is currently the responsibility of the flow author.
Note that instead of calling the notary directly, we would normally call ``FinalityFlow`` passing in the ``SignedTransaction``
(including signatures from the participants) and a list of participants to notify. The flow will request a notary signature
if needed, record the notarised transaction, and then send a copy of the transaction to all participants for them to store.
``FinalityFlow`` delegates to ``NotaryFlow.Client`` followed by ``BroadcastTransactionFlow`` to do the
actual work of notarising and broadcasting the transaction. For example:
.. sourcecode:: kotlin
subFlow(FinalityFlow(moveTransaction, setOf(aliceParty))

View File

@ -31,7 +31,7 @@ import static net.corda.core.contracts.ContractsDSL.requireThat;
*/
@SuppressWarnings("unused")
public class JavaCommercialPaper implements Contract {
static final String JCP_PROGRAM_ID = "net.corda.finance.contracts.JavaCommercialPaper";
public static final String JCP_PROGRAM_ID = "net.corda.finance.contracts.JavaCommercialPaper";
@SuppressWarnings("unused")
public static class State implements OwnableState, ICommercialPaperState {

View File

@ -29,12 +29,16 @@ import java.util.*
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DOCSTART 1
/** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
@CordaSerializable
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
// DOCEND 1
// DOCSTART 2
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
// DOCEND 2
/** Represents a textual expression of e.g. a formula */
@CordaSerializable

View File

@ -74,6 +74,7 @@ fun main(args: Array<String>) {
}
/** An in memory test zip attachment of at least numOfClearBytes size, will be used. */
// DOCSTART 2
fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0)
val executor = Executors.newScheduledThreadPool(2)
@ -104,6 +105,7 @@ private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.
val stx = flowHandle.returnValue.getOrThrow()
println("Sent ${stx.id}")
}
// DOCEND 2
@StartableByRPC
class AttachmentDemoFlow(private val otherSide: Party,
@ -131,6 +133,7 @@ class AttachmentDemoFlow(private val otherSide: Party,
}
}
// DOCSTART 1
fun recipient(rpc: CordaRPCOps) {
println("Waiting to receive transaction ...")
val stx = rpc.internalVerifiedTransactionsFeed().updates.toBlocking().first()
@ -170,6 +173,7 @@ fun recipient(rpc: CordaRPCOps) {
println("Error: no attachments found in ${wtx.id}")
}
}
// DOCEND 1
private fun printHelp(parser: OptionParser) {
println("""

View File

@ -609,6 +609,7 @@ class InterestRateSwap : Contract {
override val participants: List<AbstractParty>
get() = listOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
// DOCSTART 1
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
val nextFixingOf = nextFixingOf() ?: return null
@ -616,6 +617,7 @@ class InterestRateSwap : Contract {
val instant = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name, source = floatingLeg.indexSource, date = nextFixingOf.forDay).fromTime!!
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant)
}
// DOCEND 1
override fun generateAgreement(notary: Party): TransactionBuilder {
return InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, oracle, notary)