diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index e5eb960ac5..d1217909da 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -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> ) : FlowLogic() { + // 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() { @Suspendable override fun call(): Void? { + //DOCEND 2 serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef) return null } diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 8eb0a52a07..6f51f5425e 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -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 + // DOCEND 1 + /** Tracks changes to the network map cache. */ val changed: Observable /** 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 diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index c5762a136e..23aeb495f2 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -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 @@ -53,6 +54,8 @@ class ResolveTransactionsFlowTest { mockNet.stopNodes() unsetCordappPackages() } +// DOCEND 3 + // DOCSTART 1 @Test diff --git a/docs/source/_static/corda-cheat-sheet.pdf b/docs/source/_static/corda-cheat-sheet.pdf index cc00820e04..b7c3b7d1cb 100644 Binary files a/docs/source/_static/corda-cheat-sheet.pdf and b/docs/source/_static/corda-cheat-sheet.pdf differ diff --git a/docs/source/api-index.rst b/docs/source/api-index.rst index c1060bf964..68597ff72a 100644 --- a/docs/source/api-index.rst +++ b/docs/source/api-index.rst @@ -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 `. diff --git a/docs/source/api-rpc.rst b/docs/source/api-rpc.rst index 8c7d79d9a5..442c675a36 100644 --- a/docs/source/api-rpc.rst +++ b/docs/source/api-rpc.rst @@ -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`` diff --git a/docs/source/api-service-hub.rst b/docs/source/api-service-hub.rst index e6804f8cca..edd4874c40 100644 --- a/docs/source/api-service-hub.rst +++ b/docs/source/api-service-hub.rst @@ -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``. \ No newline at end of file +* ``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`` \ No newline at end of file diff --git a/docs/source/contract-upgrade.rst b/docs/source/contract-upgrade.rst index a00b6b0870..13322bdc4e 100644 --- a/docs/source/contract-upgrade.rst +++ b/docs/source/contract-upgrade.rst @@ -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> - ) : FlowLogic() - - /** - * 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(<>, 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 diff --git a/docs/source/corda-repo-layout.rst b/docs/source/corda-repo-layout.rst index 9c8fc9cb1f..bbec123ab4 100644 --- a/docs/source/corda-repo-layout.rst +++ b/docs/source/corda-repo-layout.rst @@ -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 diff --git a/docs/source/event-scheduling.rst b/docs/source/event-scheduling.rst index b5df1bcf1c..3ba100a3ef 100644 --- a/docs/source/event-scheduling.rst +++ b/docs/source/event-scheduling.rst @@ -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 diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index e4de7b4864..07ef6d6983 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -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(), startFlowPermission() @@ -104,7 +104,7 @@ class IntegrationTestingTutorial { } ) } + // END 5 } } -} -// END 5 +} \ No newline at end of file diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index d4aca54d28..ef403a7be7 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -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 diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java new file mode 100644 index 0000000000..8159838082 --- /dev/null +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/CommercialPaper.java @@ -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> groups = tx.groupStates(State.class, State::withoutOwner); + CommandWithParties cmd = requireSingleCommand(tx.getCommands(), Commands.class); + // DOCEND 3 + + // DOCSTART 4 + TimeWindow timeWindow = tx.getTimeWindow(); + + for (InOutGroup group : groups) { + List inputs = group.getInputs(); + List 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> 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 +} \ No newline at end of file diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/State.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/State.java new file mode 100644 index 0000000000..295fba8700 --- /dev/null +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/contract/State.java @@ -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> faceValue; + private Instant maturityDate; + + public State() { + } // For serialization + + public State(PartyAndReference issuance, AbstractParty owner, Amount> 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> 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 getParticipants() { + return ImmutableList.of(this.owner); + } +} +// DOCEND 1 \ No newline at end of file diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/TutorialFlowStateMachines.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/TutorialFlowStateMachines.java new file mode 100644 index 0000000000..ac7f387ee7 --- /dev/null +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/flowstatemachines/TutorialFlowStateMachines.java @@ -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 +} diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java new file mode 100644 index 0000000000..694cfadb16 --- /dev/null +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java @@ -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 +} \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt index 8c4bbaa3db..bf6b46be6e 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt @@ -48,7 +48,7 @@ fun main(args: Array) { startFlowPermission(), startFlowPermission())) - 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) { } } waitForAllNodesToFinish() + // END 5 } - } -// END 5 // START 6 fun generateTransactions(proxy: CordaRPCOps) { diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 8c8fef7054..0fe3079ba7 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -23,612 +23,622 @@ import net.corda.core.utilities.seconds import net.corda.core.utilities.unwrap import net.corda.finance.contracts.asset.Cash import net.corda.testing.ALICE_PUBKEY -import net.corda.testing.contracts.DUMMY_PROGRAM_ID import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import java.security.PublicKey import java.time.Instant +import kotlin.reflect.jvm.jvmName -// We group our two flows inside a singleton object to indicate that they work -// together. -object FlowCookbook { - // ``InitiatorFlow`` is our first flow, and will communicate with - // ``ResponderFlow``, below. - // We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be - // started directly by the node. - @InitiatingFlow - // We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the - // node's owner to start the flow via RPC. - @StartableByRPC - // Every flow must subclass ``FlowLogic``. The generic indicates the - // flow's return type. - class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty: Party, val regulator: Party) : FlowLogic() { +import net.corda.testing.contracts.DUMMY_PROGRAM_ID - /**--------------------------------- - * WIRING UP THE PROGRESS TRACKER * - ---------------------------------**/ - // Giving our flow a progress tracker allows us to see the flow's - // progress visually in our node's CRaSH shell. - // DOCSTART 17 - companion object { - object ID_OTHER_NODES : Step("Identifying other nodes on the network.") - object SENDING_AND_RECEIVING_DATA : Step("Sending data between parties.") - object EXTRACTING_VAULT_STATES : Step("Extracting states from the vault.") - object OTHER_TX_COMPONENTS : Step("Gathering a transaction's other components.") - object TX_BUILDING : Step("Building a transaction.") - object TX_SIGNING : Step("Signing a transaction.") - object TX_VERIFICATION : Step("Verifying a transaction.") - object SIGS_GATHERING : Step("Gathering a transaction's signatures.") { - // Wiring up a child progress tracker allows us to see the - // subflow's progress steps in our flow's progress tracker. - override fun childProgressTracker() = CollectSignaturesFlow.tracker() - } +// ``InitiatorFlow`` is our first flow, and will communicate with +// ``ResponderFlow``, below. +// We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be +// started directly by the node. +@InitiatingFlow +// We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the +// node's owner to start the flow via RPC. +@StartableByRPC +// Every flow must subclass ``FlowLogic``. The generic indicates the +// flow's return type. +class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty: Party, val regulator: Party) : FlowLogic() { - object VERIFYING_SIGS : Step("Verifying a transaction's signatures.") - object FINALISATION : Step("Finalising a transaction.") { - override fun childProgressTracker() = FinalityFlow.tracker() - } - - fun tracker() = ProgressTracker( - ID_OTHER_NODES, - SENDING_AND_RECEIVING_DATA, - EXTRACTING_VAULT_STATES, - OTHER_TX_COMPONENTS, - TX_BUILDING, - TX_SIGNING, - TX_VERIFICATION, - SIGS_GATHERING, - VERIFYING_SIGS, - FINALISATION - ) + /**--------------------------------- + * WIRING UP THE PROGRESS TRACKER * + ---------------------------------**/ + // Giving our flow a progress tracker allows us to see the flow's + // progress visually in our node's CRaSH shell. + // DOCSTART 17 + companion object { + object ID_OTHER_NODES : Step("Identifying other nodes on the network.") + object SENDING_AND_RECEIVING_DATA : Step("Sending data between parties.") + object EXTRACTING_VAULT_STATES : Step("Extracting states from the vault.") + object OTHER_TX_COMPONENTS : Step("Gathering a transaction's other components.") + object TX_BUILDING : Step("Building a transaction.") + object TX_SIGNING : Step("Signing a transaction.") + object TX_VERIFICATION : Step("Verifying a transaction.") + object SIGS_GATHERING : Step("Gathering a transaction's signatures.") { + // Wiring up a child progress tracker allows us to see the + // subflow's progress steps in our flow's progress tracker. + override fun childProgressTracker() = CollectSignaturesFlow.tracker() } - // DOCEND 17 - override val progressTracker: ProgressTracker = tracker() - - @Suppress("RemoveExplicitTypeArguments") - @Suspendable - override fun call() { - // We'll be using a dummy public key for demonstration purposes. - // These are built in to Corda, and are generally used for writing - // tests. - val dummyPubKey: PublicKey = ALICE_PUBKEY - - /**-------------------------- - * IDENTIFYING OTHER NODES * - --------------------------**/ - // DOCSTART 18 - progressTracker.currentStep = ID_OTHER_NODES - // DOCEND 18 - - // 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 - // We retrieve the notary from the network map. - // DOCSTART 1 - val specificNotary: Party = serviceHub.networkMapCache.getNotary(CordaX500Name(organisation = "Notary Service", locality = "London", country = "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. - val firstNotary: Party = serviceHub.networkMapCache.notaryIdentities.first() - // DOCEND 1 - - // We may also need to identify a specific counterparty. We - // do so using identity service. - // DOCSTART 2 - val namedCounterparty: Party = serviceHub.identityService.wellKnownPartyFromX500Name(CordaX500Name(organisation = "NodeA", locality = "London", country = "UK")) ?: - throw IllegalArgumentException("Couldn't find counterparty for NodeA in identity service") - val keyedCounterparty: Party = serviceHub.identityService.partyFromKey(dummyPubKey) ?: - throw IllegalArgumentException("Couldn't find counterparty with key: $dummyPubKey in identity service") - // DOCEND 2 - - // DOCSTART initiateFlow - val counterpartySession = initiateFlow(counterparty) - // DOCEND initiateFlow - - /**----------------------------- - * SENDING AND RECEIVING DATA * - -----------------------------**/ - progressTracker.currentStep = SENDING_AND_RECEIVING_DATA - - // 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 - // to messages from this flow. - // 2. Start the flow they have registered to respond to this flow, - // and run the flow until the first call to ``receive``, at - // which point they process the message. - // In other words, we are assuming that the counterparty is - // registered to respond to this flow, and has a corresponding - // ``receive`` call. - // DOCSTART 4 - counterpartySession.send(Any()) - // DOCEND 4 - - // We can wait to receive arbitrary data of a specific type from a - // counterparty. Again, this implies a corresponding ``send`` call - // in the counterparty's flow. A few scenarios: - // - We never receive a message back. In the current design, the - // flow is paused until the node's owner kills the flow. - // - Instead of sending a message back, the counterparty throws a - // ``FlowException``. This exception is propagated back to us, - // and we can use the error message to establish what happened. - // - We receive a message back, but it's of the wrong type. In - // this case, a ``FlowException`` is thrown. - // - We receive back a message of the correct type. All is good. - // - // Upon calling ``receive()`` (or ``sendAndReceive()``), the - // ``FlowLogic`` is suspended until it receives a response. - // - // We receive the data wrapped in an ``UntrustworthyData`` - // instance. This is a reminder that the data we receive may not - // be what it appears to be! We must unwrap the - // ``UntrustworthyData`` using a lambda. - // DOCSTART 5 - val packet1: UntrustworthyData = counterpartySession.receive() - val int: Int = packet1.unwrap { data -> - // Perform checking on the object received. - // T O D O: Check the received object. - // Return the object. - data - } - // DOCEND 5 - - // We can also use a single call to send data to a counterparty - // and wait to receive data of a specific type back. The type of - // data sent doesn't need to match the type of the data received - // back. - // DOCSTART 7 - val packet2: UntrustworthyData = counterpartySession.sendAndReceive("You can send and receive any class!") - val boolean: Boolean = packet2.unwrap { data -> - // Perform checking on the object received. - // T O D O: Check the received object. - // Return the object. - data - } - // DOCEND 7 - - // We're not limited to sending to and receiving from a single - // counterparty. A flow can send messages to as many parties as it - // likes, and each party can invoke a different response flow. - // DOCSTART 6 - val regulatorSession = initiateFlow(regulator) - regulatorSession.send(Any()) - val packet3: UntrustworthyData = regulatorSession.receive() - // DOCEND 6 - - /**----------------------------------- - * EXTRACTING STATES FROM THE VAULT * - -----------------------------------**/ - progressTracker.currentStep = EXTRACTING_VAULT_STATES - - // Let's assume there are already some ``DummyState``s in our - // node's vault, stored there as a result of running past flows, - // and we want to consume them in a transaction. There are many - // ways to extract these states from our vault. - - // For example, we would extract any unconsumed ``DummyState``s - // from our vault as follows: - val criteria: VaultQueryCriteria = VaultQueryCriteria() // default is UNCONSUMED - val results: Page = serviceHub.vaultService.queryBy(criteria) - val dummyStates: List> = results.states - - // For a full list of the available ways of extracting states from - // the vault, see the Vault Query docs page. - - // When building a transaction, input states are passed in as - // ``StateRef`` instances, which pair the hash of the transaction - // that generated the state with the state's index in the outputs - // of that transaction. In practice, we'd pass the transaction hash - // or the ``StateRef`` as a parameter to the flow, or extract the - // ``StateRef`` from our vault. - // DOCSTART 20 - val ourStateRef: StateRef = StateRef(SecureHash.sha256("DummyTransactionHash"), 0) - // DOCEND 20 - // A ``StateAndRef`` pairs a ``StateRef`` with the state it points to. - // DOCSTART 21 - val ourStateAndRef: StateAndRef = serviceHub.toStateAndRef(ourStateRef) - // DOCEND 21 - - /**----------------------------------------- - * GATHERING OTHER TRANSACTION COMPONENTS * - -----------------------------------------**/ - progressTracker.currentStep = OTHER_TX_COMPONENTS - - // Output states are constructed from scratch. - // DOCSTART 22 - val ourOutputState: DummyState = DummyState() - // DOCEND 22 - // Or as copies of other states with some properties changed. - // DOCSTART 23 - val ourOtherOutputState: DummyState = ourOutputState.copy(magicNumber = 77) - // DOCEND 23 - - // We then need to pair our output state with a contract. - // DOCSTART 47 - val contractName: String = "net.corda.testing.contracts.DummyContract" - val ourOutput: StateAndContract = StateAndContract(ourOutputState, contractName) - // DOCEND 47 - - // Commands pair a ``CommandData`` instance with a list of - // public keys. To be valid, the transaction requires a signature - // matching every public key in all of the transaction's commands. - // DOCSTART 24 - val commandData: DummyContract.Commands.Create = DummyContract.Commands.Create() - val ourPubKey: PublicKey = serviceHub.myInfo.legalIdentitiesAndCerts.first().owningKey - val counterpartyPubKey: PublicKey = counterparty.owningKey - val requiredSigners: List = listOf(ourPubKey, counterpartyPubKey) - val ourCommand: Command = Command(commandData, requiredSigners) - // DOCEND 24 - - // ``CommandData`` can either be: - // 1. Of type ``TypeOnlyCommandData``, in which case it only - // serves to attach signers to the transaction and possibly - // fork the contract's verification logic. - val typeOnlyCommandData: TypeOnlyCommandData = DummyContract.Commands.Create() - // 2. Include additional data which can be used by the contract - // during verification, alongside fulfilling the roles above. - val commandDataWithData: CommandData = Cash.Commands.Issue() - - // Attachments are identified by their hash. - // The attachment with the corresponding hash must have been - // uploaded ahead of time via the node's RPC interface. - // DOCSTART 25 - val ourAttachment: SecureHash = SecureHash.sha256("DummyAttachment") - // DOCEND 25 - - // Time windows can have a start and end time, or be open at either end. - // DOCSTART 26 - val ourTimeWindow: TimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX) - val ourAfter: TimeWindow = TimeWindow.fromOnly(Instant.MIN) - val ourBefore: TimeWindow = TimeWindow.untilOnly(Instant.MAX) - // DOCEND 26 - - // We can also define a time window as an ``Instant`` +/- a time - // tolerance (e.g. 30 seconds): - // DOCSTART 42 - val ourTimeWindow2: TimeWindow = TimeWindow.withTolerance(serviceHub.clock.instant(), 30.seconds) - // DOCEND 42 - // Or as a start-time plus a duration: - // DOCSTART 43 - val ourTimeWindow3: TimeWindow = TimeWindow.fromStartAndDuration(serviceHub.clock.instant(), 30.seconds) - // DOCEND 43 - - /**----------------------- - * TRANSACTION BUILDING * - -----------------------**/ - progressTracker.currentStep = TX_BUILDING - - // If our transaction has input states or a time-window, we must instantiate it with a - // notary. - // DOCSTART 19 - val txBuilder: TransactionBuilder = TransactionBuilder(specificNotary) - // DOCEND 19 - - // Otherwise, we can choose to instantiate it without one: - // DOCSTART 46 - val txBuilderNoNotary: TransactionBuilder = TransactionBuilder() - // DOCEND 46 - - // We add items to the transaction builder using ``TransactionBuilder.withItems``: - // DOCSTART 27 - txBuilder.withItems( - // Inputs, as ``StateAndRef``s that reference the outputs of previous transactions - ourStateAndRef, - // Outputs, as ``StateAndContract``s - ourOutput, - // Commands, as ``Command``s - ourCommand, - // Attachments, as ``SecureHash``es - ourAttachment, - // A time-window, as ``TimeWindow`` - ourTimeWindow - ) - // DOCEND 27 - - // We can also add items using methods for the individual components. - - // The individual methods for adding input states and attachments: - // DOCSTART 28 - txBuilder.addInputState(ourStateAndRef) - txBuilder.addAttachment(ourAttachment) - // DOCEND 28 - - // An output state can be added as a ``ContractState``, contract class name and notary. - // DOCSTART 49 - txBuilder.addOutputState(ourOutputState, DUMMY_PROGRAM_ID, specificNotary) - // DOCEND 49 - // We can also leave the notary field blank, in which case the transaction's default - // notary is used. - // DOCSTART 50 - txBuilder.addOutputState(ourOutputState, DUMMY_PROGRAM_ID) - // DOCEND 50 - // Or we can add the output state as a ``TransactionState``, which already specifies - // the output's contract and notary. - // DOCSTART 51 - val txState: TransactionState = TransactionState(ourOutputState, DUMMY_PROGRAM_ID, specificNotary) - // DOCEND 51 - - // Commands can be added as ``Command``s. - // DOCSTART 52 - txBuilder.addCommand(ourCommand) - // DOCEND 52 - // Or as ``CommandData`` and a ``vararg PublicKey``. - // DOCSTART 53 - txBuilder.addCommand(commandData, ourPubKey, counterpartyPubKey) - // DOCEND 53 - - // We can set a time-window directly. - // DOCSTART 44 - txBuilder.setTimeWindow(ourTimeWindow) - // DOCEND 44 - // Or as a start time plus a duration (e.g. 45 seconds). - // DOCSTART 45 - txBuilder.setTimeWindow(serviceHub.clock.instant(), 45.seconds) - // DOCEND 45 - - /**---------------------- - * TRANSACTION SIGNING * - ----------------------**/ - progressTracker.currentStep = TX_SIGNING - - // We finalise the transaction by signing it, converting it into a - // ``SignedTransaction``. - // DOCSTART 29 - val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(txBuilder) - // DOCEND 29 - // We can also sign the transaction using a different public key: - // DOCSTART 30 - val otherKey: PublicKey = serviceHub.keyManagementService.freshKey() - val onceSignedTx2: SignedTransaction = serviceHub.signInitialTransaction(txBuilder, otherKey) - // DOCEND 30 - - // If instead this was a ``SignedTransaction`` that we'd received - // from a counterparty and we needed to sign it, we would add our - // signature using: - // DOCSTART 38 - val twiceSignedTx: SignedTransaction = serviceHub.addSignature(onceSignedTx) - // DOCEND 38 - // Or, if we wanted to use a different public key: - val otherKey2: PublicKey = serviceHub.keyManagementService.freshKey() - // DOCSTART 39 - val twiceSignedTx2: SignedTransaction = serviceHub.addSignature(onceSignedTx, otherKey2) - // DOCEND 39 - - // We can also generate a signature over the transaction without - // adding it to the transaction itself. We may do this when - // sending just the signature in a flow instead of returning the - // entire transaction with our signature. This way, the receiving - // node does not need to check we haven't changed anything in the - // transaction. - // DOCSTART 40 - val sig: TransactionSignature = serviceHub.createSignature(onceSignedTx) - // DOCEND 40 - // And again, if we wanted to use a different public key: - // DOCSTART 41 - val sig2: TransactionSignature = serviceHub.createSignature(onceSignedTx, otherKey2) - // DOCEND 41 - - // In practice, however, the process of gathering every signature - // but the first can be automated using ``CollectSignaturesFlow``. - // See the "Gathering Signatures" section below. - - /**--------------------------- - * TRANSACTION VERIFICATION * - ---------------------------**/ - progressTracker.currentStep = TX_VERIFICATION - - // Verifying a transaction will also verify every transaction in - // the transaction's dependency chain, which will require - // transaction data access on counterparty's node. The - // ``SendTransactionFlow`` can be used to automate the sending and - // data vending process. The ``SendTransactionFlow`` will listen - // for data request until the transaction is resolved and verified - // on the other side: - // DOCSTART 12 - subFlow(SendTransactionFlow(counterpartySession, twiceSignedTx)) - - // Optional request verification to further restrict data access. - subFlow(object : SendTransactionFlow(counterpartySession, twiceSignedTx) { - override fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) { - // Extra request verification. - } - }) - // DOCEND 12 - - // We can receive the transaction using ``ReceiveTransactionFlow``, - // which will automatically download all the dependencies and verify - // the transaction - // DOCSTART 13 - val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterpartySession)) - // DOCEND 13 - - // We can also send and receive a `StateAndRef` dependency chain - // and automatically resolve its dependencies. - // DOCSTART 14 - subFlow(SendStateAndRefFlow(counterpartySession, dummyStates)) - - // On the receive side ... - val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow(counterpartySession)) - // DOCEND 14 - - // We can now verify the transaction to ensure that it satisfies - // the contracts of all the transaction's input and output states. - // DOCSTART 33 - twiceSignedTx.verify(serviceHub) - // DOCEND 33 - - // We'll often want to perform our own additional verification - // too. Just because a transaction is valid based on the contract - // rules and requires our signature doesn't mean we have to - // sign it! We need to make sure the transaction represents an - // agreement we actually want to enter into. - - // To do this, we need to convert our ``SignedTransaction`` - // into a ``LedgerTransaction``. This will use our ServiceHub - // to resolve the transaction's inputs and attachments into - // actual objects, rather than just references. - // DOCSTART 32 - val ledgerTx: LedgerTransaction = twiceSignedTx.toLedgerTransaction(serviceHub) - // DOCEND 32 - - // We can now perform our additional verification. - // DOCSTART 34 - val outputState: DummyState = ledgerTx.outputsOfType().single() - if (outputState.magicNumber == 777) { - // ``FlowException`` is a special exception type. It will be - // propagated back to any counterparty flows waiting for a - // message from this flow, notifying them that the flow has - // failed. - throw FlowException("We expected a magic number of 777.") - } - // DOCEND 34 - - // Of course, if you are not a required signer on the transaction, - // you have no power to decide whether it is valid or not. If it - // requires signatures from all the required signers and is - // contractually valid, it's a valid ledger update. - - /**----------------------- - * GATHERING SIGNATURES * - -----------------------**/ - progressTracker.currentStep = SIGS_GATHERING - - // The list of parties who need to sign a transaction is dictated - // by the transaction's commands. Once we've signed a transaction - // ourselves, we can automatically gather the signatures of the - // other required signers using ``CollectSignaturesFlow``. - // The responder flow will need to call ``SignTransactionFlow``. - // DOCSTART 15 - val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, setOf(counterpartySession, regulatorSession), SIGS_GATHERING.childProgressTracker())) - // DOCEND 15 - - /**----------------------- - * VERIFYING SIGNATURES * - -----------------------**/ - progressTracker.currentStep = VERIFYING_SIGS - - // We can verify that a transaction has all the required - // signatures, and that they're all valid, by running: - // DOCSTART 35 - fullySignedTx.verifyRequiredSignatures() - // DOCEND 35 - - // If the transaction is only partially signed, we have to pass in - // a list of the public keys corresponding to the missing - // signatures, explicitly telling the system not to check them. - // DOCSTART 36 - onceSignedTx.verifySignaturesExcept(counterpartyPubKey) - // DOCEND 36 - - // We can also choose to only check the signatures that are - // present. BE VERY CAREFUL - this function provides no guarantees - // that the signatures are correct, or that none are missing. - // DOCSTART 37 - twiceSignedTx.checkSignaturesAreValid() - // DOCEND 37 - - /**----------------------------- - * FINALISING THE TRANSACTION * - -----------------------------**/ - progressTracker.currentStep = FINALISATION - - // We notarise the transaction and get it recorded in the vault of - // the participants of all the transaction's states. - // DOCSTART 9 - val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())) - // DOCEND 9 - // We can also choose to send it to additional parties who aren't one - // of the state's participants. - // DOCSTART 10 - val additionalParties: Set = setOf(regulator) - val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker())) - // DOCEND 10 - - // DOCSTART FlowSession porting - send(regulator, Any()) // Old API - // becomes - val session = initiateFlow(regulator) - session.send(Any()) - // DOCEND FlowSession porting + object VERIFYING_SIGS : Step("Verifying a transaction's signatures.") + object FINALISATION : Step("Finalising a transaction.") { + override fun childProgressTracker() = FinalityFlow.tracker() } + + fun tracker() = ProgressTracker( + ID_OTHER_NODES, + SENDING_AND_RECEIVING_DATA, + EXTRACTING_VAULT_STATES, + OTHER_TX_COMPONENTS, + TX_BUILDING, + TX_SIGNING, + TX_VERIFICATION, + SIGS_GATHERING, + VERIFYING_SIGS, + FINALISATION + ) } + // DOCEND 17 - // ``ResponderFlow`` is our second flow, and will communicate with - // ``InitiatorFlow``. - // We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it - // can only be started in response to a message from its initiating flow. - // That's ``InitiatorFlow`` in this case. - // Each node also has several flow pairs registered by default - see - // ``AbstractNode.installCoreFlows``. - @InitiatedBy(InitiatorFlow::class) - class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic() { + override val progressTracker: ProgressTracker = tracker() - companion object { - object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.") - object SIGNING : Step("Responding to CollectSignaturesFlow.") - object FINALISATION : Step("Finalising a transaction.") + @Suppress("RemoveExplicitTypeArguments") + @Suspendable + override fun call() { + // We'll be using a dummy public key for demonstration purposes. + // These are built in to Corda, and are generally used for writing + // tests. + val dummyPubKey: PublicKey = ALICE_PUBKEY - fun tracker() = ProgressTracker( - RECEIVING_AND_SENDING_DATA, - SIGNING, - FINALISATION - ) + /**-------------------------- + * IDENTIFYING OTHER NODES * + --------------------------**/ + // DOCSTART 18 + progressTracker.currentStep = ID_OTHER_NODES + // DOCEND 18 + + // 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 + // We retrieve the notary from the network map. + // DOCSTART 1 + val notaryName: CordaX500Name = CordaX500Name( + organisation = "Notary Service", + locality = "London", + country = "GB") + val specificNotary: Party = serviceHub.networkMapCache.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. + val firstNotary: Party = serviceHub.networkMapCache.notaryIdentities.first() + // DOCEND 1 + + // We may also need to identify a specific counterparty. We do so + // using the identity service. + // DOCSTART 2 + val counterpartyName: CordaX500Name = CordaX500Name( + organisation = "NodeA", + locality = "London", + country = "GB") + val namedCounterparty: Party = serviceHub.identityService.wellKnownPartyFromX500Name(counterpartyName) ?: + throw IllegalArgumentException("Couldn't find counterparty for NodeA in identity service") + val keyedCounterparty: Party = serviceHub.identityService.partyFromKey(dummyPubKey) ?: + throw IllegalArgumentException("Couldn't find counterparty with key: $dummyPubKey in identity service") + // DOCEND 2 + + /**----------------------------- + * SENDING AND RECEIVING DATA * + -----------------------------**/ + progressTracker.currentStep = 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 + val counterpartySession: FlowSession = 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 + // to messages from this flow. + // 2. Start the flow they have registered to respond to this flow, + // and run the flow until the first call to ``receive``, at + // which point they process the message. + // In other words, we are assuming that the counterparty is + // registered to respond to this flow, and has a corresponding + // ``receive`` call. + // DOCSTART 4 + counterpartySession.send(Any()) + // DOCEND 4 + + // We can wait to receive arbitrary data of a specific type from a + // counterparty. Again, this implies a corresponding ``send`` call + // in the counterparty's flow. A few scenarios: + // - We never receive a message back. In the current design, the + // flow is paused until the node's owner kills the flow. + // - Instead of sending a message back, the counterparty throws a + // ``FlowException``. This exception is propagated back to us, + // and we can use the error message to establish what happened. + // - We receive a message back, but it's of the wrong type. In + // this case, a ``FlowException`` is thrown. + // - We receive back a message of the correct type. All is good. + // + // Upon calling ``receive()`` (or ``sendAndReceive()``), the + // ``FlowLogic`` is suspended until it receives a response. + // + // We receive the data wrapped in an ``UntrustworthyData`` + // instance. This is a reminder that the data we receive may not + // be what it appears to be! We must unwrap the + // ``UntrustworthyData`` using a lambda. + // DOCSTART 5 + val packet1: UntrustworthyData = counterpartySession.receive() + val int: Int = packet1.unwrap { data -> + // Perform checking on the object received. + // T O D O: Check the received object. + // Return the object. + data } + // DOCEND 5 - override val progressTracker: ProgressTracker = tracker() + // We can also use a single call to send data to a counterparty + // and wait to receive data of a specific type back. The type of + // data sent doesn't need to match the type of the data received + // back. + // DOCSTART 7 + val packet2: UntrustworthyData = counterpartySession.sendAndReceive("You can send and receive any class!") + val boolean: Boolean = packet2.unwrap { data -> + // Perform checking on the object received. + // T O D O: Check the received object. + // Return the object. + data + } + // DOCEND 7 - @Suspendable - override fun call() { - // The ``ResponderFlow` has all the same APIs available. It looks - // up network information, sends and receives data, and constructs - // transactions in exactly the same way. + // We're not limited to sending to and receiving from a single + // counterparty. A flow can send messages to as many parties as it + // likes, and each party can invoke a different response flow. + // DOCSTART 6 + val regulatorSession: FlowSession = initiateFlow(regulator) + regulatorSession.send(Any()) + val packet3: UntrustworthyData = regulatorSession.receive() + // DOCEND 6 - /**----------------------------- - * SENDING AND RECEIVING DATA * - -----------------------------**/ - progressTracker.currentStep = RECEIVING_AND_SENDING_DATA + /**----------------------------------- + * EXTRACTING STATES FROM THE VAULT * + -----------------------------------**/ + progressTracker.currentStep = EXTRACTING_VAULT_STATES - // We need to respond to the messages sent by the initiator: - // 1. They sent us an ``Any`` instance - // 2. They waited to receive an ``Integer`` instance back - // 3. They sent a ``String`` instance and waited to receive a - // ``Boolean`` instance back - // Our side of the flow must mirror these calls. - // DOCSTART 8 - val any: Any = counterpartySession.receive().unwrap { data -> data } - val string: String = counterpartySession.sendAndReceive(99).unwrap { data -> data } - counterpartySession.send(true) - // DOCEND 8 + // Let's assume there are already some ``DummyState``s in our + // node's vault, stored there as a result of running past flows, + // and we want to consume them in a transaction. There are many + // ways to extract these states from our vault. - /**---------------------------------------- - * RESPONDING TO COLLECT_SIGNATURES_FLOW * - ----------------------------------------**/ - progressTracker.currentStep = SIGNING + // For example, we would extract any unconsumed ``DummyState``s + // from our vault as follows: + val criteria: VaultQueryCriteria = VaultQueryCriteria() // default is UNCONSUMED + val results: Page = serviceHub.vaultService.queryBy(criteria) + val dummyStates: List> = results.states - // The responder will often need to respond to a call to - // ``CollectSignaturesFlow``. It does so my invoking its own - // ``SignTransactionFlow`` subclass. - // DOCSTART 16 - val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterpartySession) { - override fun checkTransaction(stx: SignedTransaction) = requireThat { - // Any additional checking we see fit... - val outputState = stx.tx.outputsOfType().single() - assert(outputState.magicNumber == 777) - } + // For a full list of the available ways of extracting states from + // the vault, see the Vault Query docs page. + + // When building a transaction, input states are passed in as + // ``StateRef`` instances, which pair the hash of the transaction + // that generated the state with the state's index in the outputs + // of that transaction. In practice, we'd pass the transaction hash + // or the ``StateRef`` as a parameter to the flow, or extract the + // ``StateRef`` from our vault. + // DOCSTART 20 + val ourStateRef: StateRef = StateRef(SecureHash.sha256("DummyTransactionHash"), 0) + // DOCEND 20 + // A ``StateAndRef`` pairs a ``StateRef`` with the state it points to. + // DOCSTART 21 + val ourStateAndRef: StateAndRef = serviceHub.toStateAndRef(ourStateRef) + // DOCEND 21 + + /**----------------------------------------- + * GATHERING OTHER TRANSACTION COMPONENTS * + -----------------------------------------**/ + progressTracker.currentStep = OTHER_TX_COMPONENTS + + // Output states are constructed from scratch. + // DOCSTART 22 + val ourOutputState: DummyState = DummyState() + // DOCEND 22 + // Or as copies of other states with some properties changed. + // DOCSTART 23 + val ourOtherOutputState: DummyState = ourOutputState.copy(magicNumber = 77) + // DOCEND 23 + + // We then need to pair our output state with a contract. + // DOCSTART 47 + val ourOutput: StateAndContract = StateAndContract(ourOutputState, DUMMY_PROGRAM_ID) + // DOCEND 47 + + // Commands pair a ``CommandData`` instance with a list of + // public keys. To be valid, the transaction requires a signature + // matching every public key in all of the transaction's commands. + // DOCSTART 24 + val commandData: DummyContract.Commands.Create = DummyContract.Commands.Create() + val ourPubKey: PublicKey = serviceHub.myInfo.legalIdentitiesAndCerts.first().owningKey + val counterpartyPubKey: PublicKey = counterparty.owningKey + val requiredSigners: List = listOf(ourPubKey, counterpartyPubKey) + val ourCommand: Command = Command(commandData, requiredSigners) + // DOCEND 24 + + // ``CommandData`` can either be: + // 1. Of type ``TypeOnlyCommandData``, in which case it only + // serves to attach signers to the transaction and possibly + // fork the contract's verification logic. + val typeOnlyCommandData: TypeOnlyCommandData = DummyContract.Commands.Create() + // 2. Include additional data which can be used by the contract + // during verification, alongside fulfilling the roles above. + val commandDataWithData: CommandData = Cash.Commands.Issue() + + // Attachments are identified by their hash. + // The attachment with the corresponding hash must have been + // uploaded ahead of time via the node's RPC interface. + // DOCSTART 25 + val ourAttachment: SecureHash = SecureHash.sha256("DummyAttachment") + // DOCEND 25 + + // Time windows can have a start and end time, or be open at either end. + // DOCSTART 26 + val ourTimeWindow: TimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX) + val ourAfter: TimeWindow = TimeWindow.fromOnly(Instant.MIN) + val ourBefore: TimeWindow = TimeWindow.untilOnly(Instant.MAX) + // DOCEND 26 + + // We can also define a time window as an ``Instant`` +/- a time + // tolerance (e.g. 30 seconds): + // DOCSTART 42 + val ourTimeWindow2: TimeWindow = TimeWindow.withTolerance(serviceHub.clock.instant(), 30.seconds) + // DOCEND 42 + // Or as a start-time plus a duration: + // DOCSTART 43 + val ourTimeWindow3: TimeWindow = TimeWindow.fromStartAndDuration(serviceHub.clock.instant(), 30.seconds) + // DOCEND 43 + + /**----------------------- + * TRANSACTION BUILDING * + -----------------------**/ + progressTracker.currentStep = TX_BUILDING + + // If our transaction has input states or a time-window, we must instantiate it with a + // notary. + // DOCSTART 19 + val txBuilder: TransactionBuilder = TransactionBuilder(specificNotary) + // DOCEND 19 + + // Otherwise, we can choose to instantiate it without one: + // DOCSTART 46 + val txBuilderNoNotary: TransactionBuilder = TransactionBuilder() + // DOCEND 46 + + // We add items to the transaction builder using ``TransactionBuilder.withItems``: + // DOCSTART 27 + txBuilder.withItems( + // Inputs, as ``StateAndRef``s that reference the outputs of previous transactions + ourStateAndRef, + // Outputs, as ``StateAndContract``s + ourOutput, + // Commands, as ``Command``s + ourCommand, + // Attachments, as ``SecureHash``es + ourAttachment, + // A time-window, as ``TimeWindow`` + ourTimeWindow + ) + // DOCEND 27 + + // We can also add items using methods for the individual components. + + // The individual methods for adding input states and attachments: + // DOCSTART 28 + txBuilder.addInputState(ourStateAndRef) + txBuilder.addAttachment(ourAttachment) + // DOCEND 28 + + // An output state can be added as a ``ContractState``, contract class name and notary. + // DOCSTART 49 + txBuilder.addOutputState(ourOutputState, DUMMY_PROGRAM_ID, specificNotary) + // DOCEND 49 + // We can also leave the notary field blank, in which case the transaction's default + // notary is used. + // DOCSTART 50 + txBuilder.addOutputState(ourOutputState, DUMMY_PROGRAM_ID) + // DOCEND 50 + // Or we can add the output state as a ``TransactionState``, which already specifies + // the output's contract and notary. + // DOCSTART 51 + val txState: TransactionState = TransactionState(ourOutputState, DUMMY_PROGRAM_ID, specificNotary) + // DOCEND 51 + + // Commands can be added as ``Command``s. + // DOCSTART 52 + txBuilder.addCommand(ourCommand) + // DOCEND 52 + // Or as ``CommandData`` and a ``vararg PublicKey``. + // DOCSTART 53 + txBuilder.addCommand(commandData, ourPubKey, counterpartyPubKey) + // DOCEND 53 + + // We can set a time-window directly. + // DOCSTART 44 + txBuilder.setTimeWindow(ourTimeWindow) + // DOCEND 44 + // Or as a start time plus a duration (e.g. 45 seconds). + // DOCSTART 45 + txBuilder.setTimeWindow(serviceHub.clock.instant(), 45.seconds) + // DOCEND 45 + + /**---------------------- + * TRANSACTION SIGNING * + ----------------------**/ + progressTracker.currentStep = TX_SIGNING + + // We finalise the transaction by signing it, converting it into a + // ``SignedTransaction``. + // DOCSTART 29 + val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(txBuilder) + // DOCEND 29 + // We can also sign the transaction using a different public key: + // DOCSTART 30 + val otherKey: PublicKey = serviceHub.keyManagementService.freshKey() + val onceSignedTx2: SignedTransaction = serviceHub.signInitialTransaction(txBuilder, otherKey) + // DOCEND 30 + + // If instead this was a ``SignedTransaction`` that we'd received + // from a counterparty and we needed to sign it, we would add our + // signature using: + // DOCSTART 38 + val twiceSignedTx: SignedTransaction = serviceHub.addSignature(onceSignedTx) + // DOCEND 38 + // Or, if we wanted to use a different public key: + val otherKey2: PublicKey = serviceHub.keyManagementService.freshKey() + // DOCSTART 39 + val twiceSignedTx2: SignedTransaction = serviceHub.addSignature(onceSignedTx, otherKey2) + // DOCEND 39 + + // We can also generate a signature over the transaction without + // adding it to the transaction itself. We may do this when + // sending just the signature in a flow instead of returning the + // entire transaction with our signature. This way, the receiving + // node does not need to check we haven't changed anything in the + // transaction. + // DOCSTART 40 + val sig: TransactionSignature = serviceHub.createSignature(onceSignedTx) + // DOCEND 40 + // And again, if we wanted to use a different public key: + // DOCSTART 41 + val sig2: TransactionSignature = serviceHub.createSignature(onceSignedTx, otherKey2) + // DOCEND 41 + + // In practice, however, the process of gathering every signature + // but the first can be automated using ``CollectSignaturesFlow``. + // See the "Gathering Signatures" section below. + + /**--------------------------- + * TRANSACTION VERIFICATION * + ---------------------------**/ + progressTracker.currentStep = TX_VERIFICATION + + // Verifying a transaction will also verify every transaction in + // the transaction's dependency chain, which will require + // transaction data access on counterparty's node. The + // ``SendTransactionFlow`` can be used to automate the sending and + // data vending process. The ``SendTransactionFlow`` will listen + // for data request until the transaction is resolved and verified + // on the other side: + // DOCSTART 12 + subFlow(SendTransactionFlow(counterpartySession, twiceSignedTx)) + + // Optional request verification to further restrict data access. + subFlow(object : SendTransactionFlow(counterpartySession, twiceSignedTx) { + override fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) { + // Extra request verification. } + }) + // DOCEND 12 - subFlow(signTransactionFlow) - // DOCEND 16 + // We can receive the transaction using ``ReceiveTransactionFlow``, + // which will automatically download all the dependencies and verify + // the transaction + // DOCSTART 13 + val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterpartySession)) + // DOCEND 13 - /**----------------------------- - * FINALISING THE TRANSACTION * - -----------------------------**/ - progressTracker.currentStep = FINALISATION + // We can also send and receive a `StateAndRef` dependency chain + // and automatically resolve its dependencies. + // DOCSTART 14 + subFlow(SendStateAndRefFlow(counterpartySession, dummyStates)) - // Nothing to do here! As long as some other party calls - // ``FinalityFlow``, the recording of the transaction on our node - // we be handled automatically. + // On the receive side ... + val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow(counterpartySession)) + // DOCEND 14 + + // We can now verify the transaction to ensure that it satisfies + // the contracts of all the transaction's input and output states. + // DOCSTART 33 + twiceSignedTx.verify(serviceHub) + // DOCEND 33 + + // We'll often want to perform our own additional verification + // too. Just because a transaction is valid based on the contract + // rules and requires our signature doesn't mean we have to + // sign it! We need to make sure the transaction represents an + // agreement we actually want to enter into. + + // To do this, we need to convert our ``SignedTransaction`` + // into a ``LedgerTransaction``. This will use our ServiceHub + // to resolve the transaction's inputs and attachments into + // actual objects, rather than just references. + // DOCSTART 32 + val ledgerTx: LedgerTransaction = twiceSignedTx.toLedgerTransaction(serviceHub) + // DOCEND 32 + + // We can now perform our additional verification. + // DOCSTART 34 + val outputState: DummyState = ledgerTx.outputsOfType().single() + if (outputState.magicNumber == 777) { + // ``FlowException`` is a special exception type. It will be + // propagated back to any counterparty flows waiting for a + // message from this flow, notifying them that the flow has + // failed. + throw FlowException("We expected a magic number of 777.") } + // DOCEND 34 + + // Of course, if you are not a required signer on the transaction, + // you have no power to decide whether it is valid or not. If it + // requires signatures from all the required signers and is + // contractually valid, it's a valid ledger update. + + /**----------------------- + * GATHERING SIGNATURES * + -----------------------**/ + progressTracker.currentStep = SIGS_GATHERING + + // The list of parties who need to sign a transaction is dictated + // by the transaction's commands. Once we've signed a transaction + // ourselves, we can automatically gather the signatures of the + // other required signers using ``CollectSignaturesFlow``. + // The responder flow will need to call ``SignTransactionFlow``. + // DOCSTART 15 + val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, setOf(counterpartySession, regulatorSession), SIGS_GATHERING.childProgressTracker())) + // DOCEND 15 + + /**----------------------- + * VERIFYING SIGNATURES * + -----------------------**/ + progressTracker.currentStep = VERIFYING_SIGS + + // We can verify that a transaction has all the required + // signatures, and that they're all valid, by running: + // DOCSTART 35 + fullySignedTx.verifyRequiredSignatures() + // DOCEND 35 + + // If the transaction is only partially signed, we have to pass in + // a list of the public keys corresponding to the missing + // signatures, explicitly telling the system not to check them. + // DOCSTART 36 + onceSignedTx.verifySignaturesExcept(counterpartyPubKey) + // DOCEND 36 + + // We can also choose to only check the signatures that are + // present. BE VERY CAREFUL - this function provides no guarantees + // that the signatures are correct, or that none are missing. + // DOCSTART 37 + twiceSignedTx.checkSignaturesAreValid() + // DOCEND 37 + + /**----------------------------- + * FINALISING THE TRANSACTION * + -----------------------------**/ + progressTracker.currentStep = FINALISATION + + // We notarise the transaction and get it recorded in the vault of + // the participants of all the transaction's states. + // DOCSTART 9 + val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())) + // DOCEND 9 + // We can also choose to send it to additional parties who aren't one + // of the state's participants. + // DOCSTART 10 + val additionalParties: Set = setOf(regulator) + val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker())) + // DOCEND 10 + + // DOCSTART FlowSession porting + send(regulator, Any()) // Old API + // becomes + val session = initiateFlow(regulator) + session.send(Any()) + // DOCEND FlowSession porting + } +} + +// ``ResponderFlow`` is our second flow, and will communicate with +// ``InitiatorFlow``. +// We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it +// can only be started in response to a message from its initiating flow. +// That's ``InitiatorFlow`` in this case. +// Each node also has several flow pairs registered by default - see +// ``AbstractNode.installCoreFlows``. +@InitiatedBy(InitiatorFlow::class) +class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic() { + + companion object { + object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.") + object SIGNING : Step("Responding to CollectSignaturesFlow.") + object FINALISATION : Step("Finalising a transaction.") + + fun tracker() = ProgressTracker( + RECEIVING_AND_SENDING_DATA, + SIGNING, + FINALISATION + ) + } + + override val progressTracker: ProgressTracker = tracker() + + @Suspendable + override fun call() { + // The ``ResponderFlow` has all the same APIs available. It looks + // up network information, sends and receives data, and constructs + // transactions in exactly the same way. + + /**----------------------------- + * SENDING AND RECEIVING DATA * + -----------------------------**/ + progressTracker.currentStep = RECEIVING_AND_SENDING_DATA + + // We need to respond to the messages sent by the initiator: + // 1. They sent us an ``Any`` instance + // 2. They waited to receive an ``Integer`` instance back + // 3. They sent a ``String`` instance and waited to receive a + // ``Boolean`` instance back + // Our side of the flow must mirror these calls. + // DOCSTART 8 + val any: Any = counterpartySession.receive().unwrap { data -> data } + val string: String = counterpartySession.sendAndReceive(99).unwrap { data -> data } + counterpartySession.send(true) + // DOCEND 8 + + /**---------------------------------------- + * RESPONDING TO COLLECT_SIGNATURES_FLOW * + ----------------------------------------**/ + progressTracker.currentStep = SIGNING + + // The responder will often need to respond to a call to + // ``CollectSignaturesFlow``. It does so my invoking its own + // ``SignTransactionFlow`` subclass. + // DOCSTART 16 + val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterpartySession) { + override fun checkTransaction(stx: SignedTransaction) = requireThat { + // Any additional checking we see fit... + val outputState = stx.tx.outputsOfType().single() + assert(outputState.magicNumber == 777) + } + } + + subFlow(signTransactionFlow) + // DOCEND 16 + + /**----------------------------- + * FINALISING THE TRANSACTION * + -----------------------------**/ + progressTracker.currentStep = FINALISATION + + // Nothing to do here! As long as some other party calls + // ``FinalityFlow``, the recording of the transaction on our node + // we be handled automatically. } } diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt new file mode 100644 index 0000000000..c9d054ea0d --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/contract/TutorialContract.kt @@ -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() + // 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>, 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, 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, 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>, + 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 \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowStateMachines.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowStateMachines.kt new file mode 100644 index 0000000000..0131077a8f --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/flowstatemachines/TutorialFlowStateMachines.kt @@ -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) : 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, + val payToIdentity: PartyAndCertificate + ) + + open class Seller(private val otherSideSession: FlowSession, + private val assetToSell: StateAndRef, + private val price: Amount, + private val myParty: PartyAndCertificate, + override val progressTracker: ProgressTracker = TwoPartyTradeFlow.Seller.tracker()) : FlowLogic() { + + 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, + private val typeToBuy: Class, + private val anonymous: Boolean) : FlowLogic() { + + @Suspendable + override fun call(): SignedTransaction { + TODO() + } + } +} +// DOCEND 1 \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/tearoffs/TutorialTearOffs.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/tearoffs/TutorialTearOffs.kt new file mode 100644 index 0000000000..c560c4f3f4 --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/tearoffs/TutorialTearOffs.kt @@ -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) { + // Typealias to make the example coherent. + val oracle = ALICE + val stx = Any() as SignedTransaction + + // DOCSTART 1 + val filtering = Predicate { + 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> = ftx.commands + val ins: List = 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.") + } +} \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt new file mode 100644 index 0000000000..9abf3e6546 --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -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().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().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().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().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().withOwner(BOB) } + command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() } + this.verifies() + } + this.fails() + } + + this.verifies() + } + } + // DOCEND 10 +} \ No newline at end of file diff --git a/docs/source/flow-state-machines.rst b/docs/source/flow-state-machines.rst index b21e0a8160..c0946f07a6 100644 --- a/docs/source/flow-state-machines.rst +++ b/docs/source/flow-state-machines.rst @@ -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) : 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, - val price: Amount, - val sellerOwnerKey: PublicKey - ) - - open class Seller(val otherParty: Party, - val notaryNode: NodeInfo, - val assetToSell: StateAndRef, - val price: Amount, - val myKey: PublicKey, - override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic() { - @Suspendable - override fun call(): SignedTransaction { - TODO() - } - } - - open class Buyer(val otherParty: Party, - val notary: Party, - val acceptablePrice: Amount, - val typeToBuy: Class) : FlowLogic() { - @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`` - a pointer to the ledger entry that represents the thing being sold. -- ``price: Amount`` - 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`` - a pointer to the ledger entry that represents the thing being sold +- ``price: Amount`` - 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`` - 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`` - 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. \ No newline at end of file + reporting logic, or anything else that might be required as part of a communications lifecycle \ No newline at end of file diff --git a/docs/source/flow-testing.rst b/docs/source/flow-testing.rst index 1d53696b61..939da0769e 100644 --- a/docs/source/flow-testing.rst +++ b/docs/source/flow-testing.rst @@ -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 `_ +And that's it: you can explore the documentation for the +`MockNetwork API `_ here. diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index 8ed5c9b02e..cd0d448ebb 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -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 ` and -the :doc:`samples `. +The best way to check that everything is working fine is by taking a deeper look at the +:doc:`example CorDapp `. Next, you should read through :doc:`Corda Key Concepts ` to understand how Corda works. diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 416d3e6022..7bf00e4771 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -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 `_ with an +CorDapps. There's `a more fleshed-out version of the IOU CorDapp `_ with an API and web front-end, and a set of example CorDapps in `the main Corda repo `_, under ``samples``. An explanation of how to run these samples :doc:`here `. diff --git a/docs/source/key-concepts-node.rst b/docs/source/key-concepts-node.rst index aa6517249a..537d0aebc4 100644 --- a/docs/source/key-concepts-node.rst +++ b/docs/source/key-concepts-node.rst @@ -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 --------------- diff --git a/docs/source/oracles.rst b/docs/source/oracles.rst index c8c6de060a..802e9f33c0 100644 --- a/docs/source/oracles.rst +++ b/docs/source/oracles.rst @@ -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, deadline: Instant): List + fun query(queries: List): List - 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. \ No newline at end of file diff --git a/docs/source/running-a-notary.rst b/docs/source/running-a-notary.rst index 81ade65043..32b62359a8 100644 --- a/docs/source/running-a-notary.rst +++ b/docs/source/running-a-notary.rst @@ -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 `_ framework. + we are using the `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`. diff --git a/docs/source/tutorial-attachments.rst b/docs/source/tutorial-attachments.rst index 09a2736b38..7047cfb959 100644 --- a/docs/source/tutorial-attachments.rst +++ b/docs/source/tutorial-attachments.rst @@ -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 diff --git a/docs/source/tutorial-building-transactions.rst b/docs/source/tutorial-building-transactions.rst index b8c25bf04a..d7011e57f4 100644 --- a/docs/source/tutorial-building-transactions.rst +++ b/docs/source/tutorial-building-transactions.rst @@ -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 transaction’s 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 node’s -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 node’s 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 let’s assume the cash resources are using the -standard ``CashState`` in the ``:financial`` Gradle module. The Cash +For the cash transaction, let’s 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 node’s 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 node’s 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. \ No newline at end of file diff --git a/docs/source/tutorial-clientrpc-api.rst b/docs/source/tutorial-clientrpc-api.rst index 0181e0778b..aa49889b4f 100644 --- a/docs/source/tutorial-clientrpc-api.rst +++ b/docs/source/tutorial-clientrpc-api.rst @@ -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 `` for the former and ``EDGE `` for the -latter. +The graph will be defined as follows: + +* Each transaction is a vertex, represented by printing ``NODE `` +* Each input-output relationship is an edge, represented by prining ``EDGE `` .. 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`. \ No newline at end of file diff --git a/docs/source/tutorial-contract.rst b/docs/source/tutorial-contract.rst index 2aca2177f7..11e6ef0755 100644 --- a/docs/source/tutorial-contract.rst +++ b/docs/source/tutorial-contract.rst @@ -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>, - 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> faceValue; - private Instant maturityDate; - - public State() { - } // For serialization - - public State(PartyAndReference issuance, PublicKey owner, Amount> 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 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> 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 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() - - .. sourcecode:: java - - @Override - public void verify(LedgerTransaction tx) { - List> groups = tx.groupStates(State.class, State::withoutOwner); - CommandWithParties 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>> - 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 group : groups) { - List inputs = group.getInputs(); - List 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> 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>, 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, 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, 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: diff --git a/docs/source/tutorial-integration-testing.rst b/docs/source/tutorial-integration-testing.rst index 0cbed18a15..f2e410d347 100644 --- a/docs/source/tutorial-integration-testing.rst +++ b/docs/source/tutorial-integration-testing.rst @@ -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 \ No newline at end of file diff --git a/docs/source/tutorial-tear-offs.rst b/docs/source/tutorial-tear-offs.rst index 5187aaac42..989a4ae20b 100644 --- a/docs/source/tutorial-tear-offs.rst +++ b/docs/source/tutorial-tear-offs.rst @@ -1,80 +1,53 @@ Transaction tear-offs ===================== -Example of usage ----------------- -Let’s 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 = ftx.filteredLeaves.commands - val ins: List = 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. \ No newline at end of file diff --git a/docs/source/tutorial-test-dsl.rst b/docs/source/tutorial-test-dsl.rst index 2eb870c781..8bfbab79b0 100644 --- a/docs/source/tutorial-test-dsl.rst +++ b/docs/source/tutorial-test-dsl.rst @@ -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() `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() `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() `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() `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() `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 \ No newline at end of file diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst index f9ffad2dd3..c0b1a5af14 100644 --- a/docs/source/tutorials-index.rst +++ b/docs/source/tutorials-index.rst @@ -15,7 +15,6 @@ Tutorials flow-state-machines flow-testing running-a-notary - using-a-notary oracles tutorial-tear-offs tutorial-attachments diff --git a/docs/source/using-a-notary.rst b/docs/source/using-a-notary.rst deleted file mode 100644 index d37d05809d..0000000000 --- a/docs/source/using-a-notary.rst +++ /dev/null @@ -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) - - /** - * 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)) diff --git a/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java b/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java index 3838fb4feb..c84fc42f25 100644 --- a/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java +++ b/finance/src/main/java/net/corda/finance/contracts/JavaCommercialPaper.java @@ -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 { diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt b/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt index 13248bd58e..1079beeccc 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt @@ -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 diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index a6fbb33bd5..33a41f15de 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -74,6 +74,7 @@ fun main(args: Array) { } /** 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(""" diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt index 8070d86f29..097a33c5e8 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt @@ -609,6 +609,7 @@ class InterestRateSwap : Contract { override val participants: List 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)