mirror of
https://github.com/corda/corda.git
synced 2025-06-15 21:58:17 +00:00
Merge open master
This commit is contained in:
@ -22,9 +22,7 @@ The ``Contract`` interface is defined as follows:
|
||||
|
||||
Where:
|
||||
|
||||
* ``verify(tx: LedgerTransaction)`` determines whether transactions involving states which reference this
|
||||
contract type are valid
|
||||
* ``legalContractReference`` is the hash of the legal prose contract that ``verify`` seeks to express in code
|
||||
* ``verify(tx: LedgerTransaction)`` determines whether transactions involving states which reference this contract type are valid
|
||||
|
||||
verify()
|
||||
--------
|
||||
@ -187,8 +185,6 @@ execution of ``verify()``:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("X contract hash")
|
||||
}
|
||||
|
||||
.. sourcecode:: java
|
||||
@ -209,9 +205,6 @@ execution of ``verify()``:
|
||||
// Transfer verification logic.
|
||||
}
|
||||
}
|
||||
|
||||
private final SecureHash legalContractReference = SecureHash.sha256("X contract hash");
|
||||
@Override public final SecureHash getLegalContractReference() { return legalContractReference; }
|
||||
}
|
||||
|
||||
Grouping states
|
||||
@ -297,13 +290,5 @@ We can now verify these groups individually:
|
||||
Legal prose
|
||||
-----------
|
||||
|
||||
Current, ``legalContractReference`` is simply the SHA-256 hash of a contract:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 2
|
||||
:end-before: DOCEND 2
|
||||
|
||||
In the future, a contract's legal prose will be included as an attachment instead.
|
||||
Currently, a ``Contract`` subtype may refer to the legal prose it implements via a ``LegalProseReference`` annotation.
|
||||
In the future, a contract's legal prose will be included as an attachment.
|
||||
|
@ -90,7 +90,7 @@ in order to initialise the ORM layer.
|
||||
Several examples of entities and mappings are provided in the codebase, including ``Cash.State`` and
|
||||
``CommercialPaper.State``. For example, here's the first version of the cash schema.
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt
|
||||
:language: kotlin
|
||||
|
||||
Identity mapping
|
||||
|
@ -129,7 +129,7 @@ For example, here is a relatively complex state definition, for a state represen
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 1
|
||||
:end-before: DOCEND 1
|
||||
|
@ -11,12 +11,11 @@ API: Transactions
|
||||
|
||||
Transaction workflow
|
||||
--------------------
|
||||
There are four states the transaction can occupy:
|
||||
At any time, a transaction can occupy one of three states:
|
||||
|
||||
* ``TransactionBuilder``, a builder for a transaction in construction
|
||||
* ``WireTransaction``, an immutable transaction
|
||||
* ``TransactionBuilder``, a builder for an in-construction transaction
|
||||
* ``SignedTransaction``, an immutable transaction with 1+ associated signatures
|
||||
* ``LedgerTransaction``, a transaction that can be checked for validity
|
||||
* ``LedgerTransaction``, an immutable transaction that can be checked for validity
|
||||
|
||||
Here are the possible transitions between transaction states:
|
||||
|
||||
@ -26,8 +25,8 @@ TransactionBuilder
|
||||
------------------
|
||||
Creating a builder
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
The first step when creating a transaction is to instantiate a ``TransactionBuilder``. We can create a builder for each
|
||||
transaction type as follows:
|
||||
The first step when creating a new transaction is to instantiate a ``TransactionBuilder``. We create a builder for a
|
||||
transaction as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -342,7 +341,7 @@ SignedTransaction
|
||||
-----------------
|
||||
A ``SignedTransaction`` is a combination of:
|
||||
|
||||
* An immutable ``WireTransaction``
|
||||
* An immutable transaction
|
||||
* A list of signatures over that transaction
|
||||
|
||||
.. container:: codeset
|
||||
@ -357,45 +356,9 @@ transaction's signatures.
|
||||
|
||||
Verifying the transaction's contents
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't
|
||||
currently have in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow
|
||||
called ``ReceiveTransactionFlow``. See :doc:`api-flows` for more details.
|
||||
|
||||
When verifying a ``SignedTransaction``, we don't verify the ``SignedTransaction`` *per se*, but rather the
|
||||
``WireTransaction`` it contains. We extract this ``WireTransaction`` as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 31
|
||||
:end-before: DOCEND 31
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 31
|
||||
:end-before: DOCEND 31
|
||||
:dedent: 12
|
||||
|
||||
However, this still isn't enough. The ``WireTransaction`` holds its inputs as ``StateRef`` instances, and its
|
||||
attachments as hashes. These do not provide enough information to properly validate the transaction's contents. To
|
||||
resolve these into actual ``ContractState`` and ``Attachment`` instances, we need to use the ``ServiceHub`` to convert
|
||||
the ``WireTransaction`` into a ``LedgerTransaction``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't currently have
|
||||
in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow called
|
||||
``ReceiveTransactionFlow``. See :doc:`api-flows` for more details.
|
||||
|
||||
We can now *verify* the transaction to ensure that it satisfies the contracts of all the transaction's input and output
|
||||
states:
|
||||
@ -414,8 +377,27 @@ states:
|
||||
:end-before: DOCEND 33
|
||||
:dedent: 12
|
||||
|
||||
We will generally also want to conduct some additional validation of the transaction, beyond what is provided for in
|
||||
the contract. Here's an example of how we might do this:
|
||||
We can also conduct additional validation of the transaction, beyond what is performed by its contracts. However, the
|
||||
``SignedTransaction`` holds its inputs as ``StateRef`` instances, and its attachments as hashes. These do not provide
|
||||
enough information to properly validate the transaction's contents. To resolve these into actual ``ContractState`` and
|
||||
``Attachment`` instances, we need to use the ``ServiceHub`` to convert the ``SignedTransaction`` into a
|
||||
``LedgerTransaction``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
|
||||
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||
:language: java
|
||||
:start-after: DOCSTART 32
|
||||
:end-before: DOCEND 32
|
||||
:dedent: 12
|
||||
|
||||
We can now perform additional verification. Here's a simple example:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -558,8 +540,8 @@ Or using another one of our public keys, as follows:
|
||||
|
||||
Notarising and recording
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See
|
||||
:doc:`api-flows` for more details.
|
||||
Notarising and recording a transaction is handled by a built-in flow called ``FinalityFlow``. See :doc:`api-flows` for
|
||||
more details.
|
||||
|
||||
Notary-change transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -70,6 +70,15 @@ There are four implementations of this interface which can be chained together t
|
||||
|
||||
.. note:: It is a requirement to register any custom contract schemas to be used in Vault Custom queries in the associated `CordaPluginRegistry` configuration for the respective CorDapp using the ``requiredSchemas`` configuration field (which specifies a set of `MappedSchema`)
|
||||
|
||||
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators, as also illustrated above.
|
||||
|
||||
All ``QueryCriteria`` implementations provide an explicitly specifiable set of common attributes:
|
||||
|
||||
1. State status attribute (``Vault.StateStatus``), which defaults to filtering on UNCONSUMED states.
|
||||
When chaining several criterias using AND / OR, the last value of this attribute will override any previous.
|
||||
2. Contract state types (``<Set<Class<out ContractState>>``), which will contain at minimum one type (by default this will be ``ContractState`` which resolves to all types).
|
||||
When chaining several criteria using AND / OR, all specified contract state types are combined into a single set.
|
||||
|
||||
An example of a custom query is illustrated here:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
||||
@ -77,10 +86,6 @@ An example of a custom query is illustrated here:
|
||||
:start-after: DOCSTART VaultQueryExample20
|
||||
:end-before: DOCEND VaultQueryExample20
|
||||
|
||||
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators, as also illustrated above.
|
||||
|
||||
All ``QueryCriteria`` implementations provide an explicitly specifiable ``StateStatus`` attribute which defaults to filtering on UNCONSUMED states.
|
||||
|
||||
.. note:: Custom contract states that implement the ``Queryable`` interface may now extend common schemas types ``FungiblePersistentState`` or, ``LinearPersistentState``. Previously, all custom contracts extended the root ``PersistentState`` class and defined repeated mappings of ``FungibleAsset`` and ``LinearState`` attributes. See ``SampleCashSchemaV2`` and ``DummyLinearStateSchemaV2`` as examples.
|
||||
|
||||
Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java.
|
||||
@ -317,22 +322,22 @@ Query for consumed deal states or linear ids, specify a paging specification and
|
||||
|
||||
Aggregations on cash using various functions:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt
|
||||
:language: kotlin
|
||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
||||
:language: java
|
||||
:start-after: DOCSTART VaultJavaQueryExample21
|
||||
:end-before: DOCEND VaultJavaQueryExample21
|
||||
|
||||
Aggregations on cash grouped by currency for various functions:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt
|
||||
:language: kotlin
|
||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
||||
:language: java
|
||||
:start-after: DOCSTART VaultJavaQueryExample22
|
||||
:end-before: DOCEND VaultJavaQueryExample22
|
||||
|
||||
Sum aggregation on cash grouped by issuer party and currency and sorted by sum:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryJavaTests.kt
|
||||
:language: kotlin
|
||||
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
|
||||
:language: java
|
||||
:start-after: DOCSTART VaultJavaQueryExample23
|
||||
:end-before: DOCEND VaultJavaQueryExample23
|
||||
|
||||
|
@ -6,6 +6,15 @@ from the previous milestone release.
|
||||
|
||||
UNRELEASED
|
||||
----------
|
||||
* Vault query common attributes (state status and contract state types) are now handled correctly when using composite
|
||||
criteria specifications. State status is overridable. Contract states types are aggregatable.
|
||||
|
||||
* Cash selection algorithm is now pluggable (with H2 being the default implementation)
|
||||
|
||||
* Removed usage of Requery ORM library (repalced with JPA/Hibernate)
|
||||
|
||||
* Vault Query performance improvement (replaced expensive per query SQL statement to obtain concrete state types
|
||||
with single query on start-up followed by dynamic updates using vault state observable))
|
||||
|
||||
* Vault Query fix: filter by multiple issuer names in ``FungibleAssetQueryCriteria``
|
||||
|
||||
@ -40,6 +49,25 @@ UNRELEASED
|
||||
If you specifically need well known identities, use the network map, which is the authoritative source of current well
|
||||
known identities.
|
||||
|
||||
* Currency-related API in ``net.corda.core.contracts.ContractsDSL`` has moved to ```net.corda.finance.CurrencyUtils`.
|
||||
|
||||
* Remove `IssuerFlow` as it allowed nodes to request arbitrary amounts of cash to be issued from any remote node. Use
|
||||
`CashIssueFlow` instead.
|
||||
|
||||
* Some utility/extension functions (``sumOrThrow``, ``sumOrNull``, ``sumOrZero`` on ``Amount`` and ``Commodity``)
|
||||
have moved to be static methods on the classes themselves. This improves the API for Java users who no longer
|
||||
have to see or known about file-level FooKt style classes generated by the Kotlin compile, but means that IntelliJ
|
||||
no longer auto-suggests these extension functions in completion unless you add import lines for them yourself
|
||||
(this is Kotlin IDE bug KT-15286).
|
||||
|
||||
* ``:finance`` module now acting as a CorDapp with regard to flow registration, schemas and serializable types.
|
||||
|
||||
* ``WebServerPluginRegistry`` now has a ``customizeJSONSerialization`` which can be overridden to extend the REST JSON
|
||||
serializers. In particular the IRS demos must now register the ``BusinessCalendar`` serializers.
|
||||
|
||||
* Moved ``:finance`` gradle project files into a ``net.corda.finance`` package namespace.
|
||||
This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files.
|
||||
|
||||
Milestone 14
|
||||
------------
|
||||
|
||||
|
@ -60,7 +60,7 @@ Currently, users need special permissions to start flows via RPC. These permissi
|
||||
]
|
||||
|
||||
.. note:: Currently, the node's web server has super-user access, meaning that it can run any RPC operation without
|
||||
logging in. This will be changed in a future release.
|
||||
logging in. This will be changed in a future release.
|
||||
|
||||
Observables
|
||||
-----------
|
||||
|
@ -23,6 +23,11 @@ Cash shares a common superclass, ``OnLedgerAsset``, with the Commodity contract.
|
||||
assets which can be issued, moved and exited on chain, with the subclasses handling asset-specific data types and
|
||||
behaviour.
|
||||
|
||||
.. note:: Corda supports a pluggable cash selection algorithm by implementing the ``CashSelection`` interface.
|
||||
The default implementation uses an H2 specific query that can be overridden for different database providers.
|
||||
Please see ``CashSelectionH2Impl`` and its associated declaration in
|
||||
``META-INF\services\net.corda.finance.contracts.asset.CashSelection``
|
||||
|
||||
Commodity
|
||||
---------
|
||||
|
||||
|
@ -20,7 +20,6 @@ The Corda repository comprises the following folders:
|
||||
* **lib** contains some dependencies
|
||||
* **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
|
||||
* **node-schemas** contains entity classes used to represent relational database tables
|
||||
* **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
|
||||
mock network) implementation
|
||||
|
@ -64,7 +64,7 @@ applicationDistribution.into("bin") {
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
rpcUsers = [
|
||||
['username' : "user",
|
||||
'password' : "password",
|
||||
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
|
||||
'permissions' : ["StartFlow.net.corda.finance.flows.CashFlow"]]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,22 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.vaultTrackBy
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.flows.CashPaymentFlow
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.expect
|
||||
import net.corda.testing.expectEvents
|
||||
import net.corda.testing.parallel
|
||||
import net.corda.testing.sequence
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class IntegrationTestingTutorial {
|
||||
@ -33,7 +25,8 @@ class IntegrationTestingTutorial {
|
||||
// START 1
|
||||
driver {
|
||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||
startFlowPermission<CashIssueFlow>()
|
||||
startFlowPermission<CashIssueFlow>(),
|
||||
startFlowPermission<CashPaymentFlow>()
|
||||
))
|
||||
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
|
||||
startFlowPermission<CashPaymentFlow>()
|
||||
@ -63,18 +56,21 @@ class IntegrationTestingTutorial {
|
||||
|
||||
// START 4
|
||||
val issueRef = OpaqueBytes.of(0)
|
||||
val futures = Stack<CordaFuture<*>>()
|
||||
(1..10).map { i ->
|
||||
thread {
|
||||
futures.push(aliceProxy.startFlow(::CashIssueFlow,
|
||||
i.DOLLARS,
|
||||
issueRef,
|
||||
bob.nodeInfo.legalIdentity,
|
||||
notary.nodeInfo.notaryIdentity
|
||||
).returnValue)
|
||||
}
|
||||
}.forEach(Thread::join) // Ensure the stack of futures is populated.
|
||||
futures.forEach { it.getOrThrow() }
|
||||
aliceProxy.startFlow(::CashIssueFlow,
|
||||
i.DOLLARS,
|
||||
issueRef,
|
||||
notary.nodeInfo.notaryIdentity
|
||||
).returnValue
|
||||
}.transpose().getOrThrow()
|
||||
// We wait for all of the issuances to run before we start making payments
|
||||
(1..10).map { i ->
|
||||
aliceProxy.startFlow(::CashPaymentFlow,
|
||||
i.DOLLARS,
|
||||
bob.nodeInfo.legalIdentity,
|
||||
true
|
||||
).returnValue
|
||||
}.transpose().getOrThrow()
|
||||
|
||||
bobVaultUpdates.expectEvents {
|
||||
parallel(
|
||||
|
@ -3,7 +3,6 @@ package net.corda.docs;
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.corda.contracts.asset.Cash;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.crypto.SecureHash;
|
||||
import net.corda.core.crypto.TransactionSignature;
|
||||
@ -21,6 +20,7 @@ import net.corda.core.transactions.WireTransaction;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
import net.corda.core.utilities.ProgressTracker.Step;
|
||||
import net.corda.core.utilities.UntrustworthyData;
|
||||
import net.corda.finance.contracts.asset.Cash;
|
||||
import net.corda.testing.contracts.DummyContract;
|
||||
import net.corda.testing.contracts.DummyState;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@ -280,7 +281,7 @@ public class FlowCookbookJava {
|
||||
TypeOnlyCommandData typeOnlyCommandData = new DummyContract.Commands.Create();
|
||||
// 2. Include additional data which can be used by the contract
|
||||
// during verification, alongside fulfilling the roles above
|
||||
CommandData commandDataWithData = new Cash.Commands.Issue(12345678);
|
||||
CommandData commandDataWithData = new Cash.Commands.Issue();
|
||||
|
||||
// Attachments are identified by their hash.
|
||||
// The attachment with the corresponding hash must have been
|
||||
@ -394,15 +395,18 @@ public class FlowCookbookJava {
|
||||
----------------------------*/
|
||||
progressTracker.setCurrentStep(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:
|
||||
// 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(new SendTransactionFlow(counterparty, twiceSignedTx));
|
||||
|
||||
// Optional request verification to further restrict data access.
|
||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx){
|
||||
subFlow(new SendTransactionFlow(counterparty, twiceSignedTx) {
|
||||
@Override
|
||||
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
|
||||
// Extra request verification.
|
||||
@ -425,41 +429,43 @@ public class FlowCookbookJava {
|
||||
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterparty));
|
||||
// DOCEND 14
|
||||
|
||||
// A ``SignedTransaction`` is a pairing of a ``WireTransaction``
|
||||
// with signatures over this ``WireTransaction``. We don't verify
|
||||
// a signed transaction per se, but rather the ``WireTransaction``
|
||||
// it contains.
|
||||
// DOCSTART 31
|
||||
WireTransaction wireTx = twiceSignedTx.getTx();
|
||||
// DOCEND 31
|
||||
// Before we can verify the transaction, we need the
|
||||
// ``ServiceHub`` to use our node's local storage to resolve the
|
||||
// transaction's inputs and attachments into actual objects,
|
||||
// rather than just references. We do this by converting the
|
||||
// ``WireTransaction`` into a ``LedgerTransaction``.
|
||||
// DOCSTART 32
|
||||
LedgerTransaction ledgerTx = wireTx.toLedgerTransaction(getServiceHub());
|
||||
// DOCEND 32
|
||||
// We can now verify the transaction.
|
||||
// DOCSTART 33
|
||||
ledgerTx.verify();
|
||||
// DOCEND 33
|
||||
try {
|
||||
|
||||
// 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.
|
||||
// DOCSTART 34
|
||||
DummyState outputState = (DummyState) wireTx.getOutputs().get(0).getData();
|
||||
if (outputState.getMagicNumber() != 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 new FlowException("We expected a magic number of 777.");
|
||||
// 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(getServiceHub());
|
||||
// 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
|
||||
LedgerTransaction ledgerTx = twiceSignedTx.toLedgerTransaction(getServiceHub());
|
||||
// DOCEND 32
|
||||
|
||||
// We can now perform our additional verification.
|
||||
// DOCSTART 34
|
||||
DummyState outputState = ledgerTx.outputsOfType(DummyState.class).get(0);
|
||||
if (outputState.getMagicNumber() != 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 new FlowException("We expected a magic number of 777.");
|
||||
}
|
||||
// DOCEND 34
|
||||
|
||||
} catch (GeneralSecurityException e) {
|
||||
// Handle this as required.
|
||||
}
|
||||
// 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
|
||||
|
@ -1,9 +1,6 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.client.rpc.notUsed
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.vaultQueryBy
|
||||
@ -13,10 +10,12 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationCustomization
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.flows.CashExitFlow
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.flows.CashPaymentFlow
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashExitFlow
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.testing.ALICE
|
||||
@ -128,7 +127,7 @@ fun generateTransactions(proxy: CordaRPCOps) {
|
||||
proxy.startFlow(::CashPaymentFlow, Amount(quantity, USD), me)
|
||||
} else {
|
||||
val quantity = Math.abs(random.nextLong() % 1000)
|
||||
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, me, notary)
|
||||
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, notary)
|
||||
ownedQuantity += quantity
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.flows.*
|
||||
import net.corda.finance.flows.AbstractCashFlow
|
||||
import net.corda.finance.flows.CashException
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import java.util.*
|
||||
|
||||
// DOCSTART CustomVaultQuery
|
||||
@ -132,8 +135,7 @@ object TopupIssuerFlow {
|
||||
val notaryParty = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
|
||||
// invoke Cash subflow to issue Asset
|
||||
progressTracker.currentStep = ISSUING
|
||||
val issueRecipient = serviceHub.myInfo.legalIdentity
|
||||
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, issueRecipient, notaryParty, anonymous = false)
|
||||
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, notaryParty)
|
||||
val issueTx = subFlow(issueCashFlow)
|
||||
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
|
||||
// short-circuit when issuing to self
|
||||
|
@ -3,7 +3,6 @@
|
||||
package net.corda.docs
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
@ -17,12 +16,12 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.ProgressTracker.Step
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
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.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
@ -262,8 +261,8 @@ object FlowCookbook {
|
||||
// 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(nonce = 12345678)
|
||||
// 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
|
||||
@ -379,10 +378,13 @@ object FlowCookbook {
|
||||
---------------------------**/
|
||||
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:
|
||||
// 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(counterparty, twiceSignedTx))
|
||||
|
||||
@ -401,7 +403,8 @@ object FlowCookbook {
|
||||
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterparty))
|
||||
// DOCEND 13
|
||||
|
||||
// We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies.
|
||||
// We can also send and receive a `StateAndRef` dependency chain
|
||||
// and automatically resolve its dependencies.
|
||||
// DOCSTART 14
|
||||
subFlow(SendStateAndRefFlow(counterparty, dummyStates))
|
||||
|
||||
@ -409,24 +412,10 @@ object FlowCookbook {
|
||||
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterparty))
|
||||
// DOCEND 14
|
||||
|
||||
// A ``SignedTransaction`` is a pairing of a ``WireTransaction``
|
||||
// with signatures over this ``WireTransaction``. We don't verify
|
||||
// a signed transaction per se, but rather the ``WireTransaction``
|
||||
// it contains.
|
||||
// DOCSTART 31
|
||||
val wireTx: WireTransaction = twiceSignedTx.tx
|
||||
// DOCEND 31
|
||||
// Before we can verify the transaction, we need the
|
||||
// ``ServiceHub`` to use our node's local storage to resolve the
|
||||
// transaction's inputs and attachments into actual objects,
|
||||
// rather than just references. We do this by converting the
|
||||
// ``WireTransaction`` into a ``LedgerTransaction``.
|
||||
// DOCSTART 32
|
||||
val ledgerTx: LedgerTransaction = wireTx.toLedgerTransaction(serviceHub)
|
||||
// DOCEND 32
|
||||
// We can now verify the transaction.
|
||||
// We can now verify the transaction to ensure that it satisfies
|
||||
// the contracts of all the transaction's input and output states.
|
||||
// DOCSTART 33
|
||||
ledgerTx.verify()
|
||||
twiceSignedTx.verify(serviceHub)
|
||||
// DOCEND 33
|
||||
|
||||
// We'll often want to perform our own additional verification
|
||||
@ -434,8 +423,18 @@ object FlowCookbook {
|
||||
// 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 = wireTx.outputsOfType<DummyState>().single()
|
||||
val outputState: DummyState = ledgerTx.outputsOfType<DummyState>().single()
|
||||
if (outputState.magicNumber == 777) {
|
||||
// ``FlowException`` is a special exception type. It will be
|
||||
// propagated back to any counterparty flows waiting for a
|
||||
|
@ -1,17 +1,14 @@
|
||||
package net.corda.docs
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.withoutIssuer
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
@ -20,7 +17,8 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.schemas.CashSchemaV1
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import java.util.*
|
||||
|
||||
@CordaSerializable
|
||||
@ -42,8 +40,8 @@ private fun gatherOurInputs(serviceHub: ServiceHub,
|
||||
val ourParties = ourKeys.map { serviceHub.identityService.partyFromKey(it) ?: throw IllegalStateException("Unable to resolve party from key") }
|
||||
val fungibleCriteria = QueryCriteria.FungibleAssetQueryCriteria(owner = ourParties)
|
||||
|
||||
val notaryName = if (notary != null) notary.name else serviceHub.networkMapCache.getAnyNotary()!!.name
|
||||
val vaultCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(notaryName = listOf(notaryName))
|
||||
val notaries = notary ?: serviceHub.networkMapCache.getAnyNotary()
|
||||
val vaultCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(notary = listOf(notaries as AbstractParty))
|
||||
|
||||
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.equal(amountRequired.token.product.currencyCode) }
|
||||
val cashCriteria = QueryCriteria.VaultCustomQueryCriteria(logicalExpression)
|
||||
|
@ -2,7 +2,6 @@ package net.corda.docs
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
@ -11,8 +10,6 @@ import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -35,7 +32,7 @@ enum class WorkflowState {
|
||||
* Minimal contract to encode a simple workflow with one initial state and two possible eventual states.
|
||||
* It is assumed one party unilaterally submits and the other manually retrieves the deal and completes it.
|
||||
*/
|
||||
data class TradeApprovalContract(override val legalContractReference: SecureHash = SecureHash.sha256("Example of workflow type transaction")) : Contract {
|
||||
data class TradeApprovalContract(private val blank: Void? = null) : Contract {
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Issue : TypeOnlyCommandData(), Commands // Record receipt of deal details
|
||||
|
@ -1,11 +1,12 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.contracts.getCashBalances
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.finance.*
|
||||
import net.corda.finance.contracts.getCashBalances
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
@ -65,9 +66,7 @@ class CustomVaultQueryTest {
|
||||
// Use NodeA as issuer and create some dollars
|
||||
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(amountToIssue,
|
||||
OpaqueBytes.of(0x01),
|
||||
nodeA.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity,
|
||||
false))
|
||||
notaryNode.info.notaryIdentity))
|
||||
// Wait for the flow to stop and print
|
||||
flowHandle1.resultFuture.getOrThrow()
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.contracts.getCashBalances
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.finance.*
|
||||
import net.corda.finance.contracts.getCashBalances
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
@ -46,9 +46,7 @@ class FxTransactionBuildTutorialTest {
|
||||
// Use NodeA as issuer and create some dollars
|
||||
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000),
|
||||
OpaqueBytes.of(0x01),
|
||||
nodeA.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity,
|
||||
false))
|
||||
notaryNode.info.notaryIdentity))
|
||||
// Wait for the flow to stop and print
|
||||
flowHandle1.resultFuture.getOrThrow()
|
||||
printBalances()
|
||||
@ -56,9 +54,7 @@ class FxTransactionBuildTutorialTest {
|
||||
// Using NodeB as Issuer create some pounds.
|
||||
val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000),
|
||||
OpaqueBytes.of(0x01),
|
||||
nodeB.info.legalIdentity,
|
||||
notaryNode.info.notaryIdentity,
|
||||
false))
|
||||
notaryNode.info.notaryIdentity))
|
||||
// Wait for flow to come to an end and print
|
||||
flowHandle2.resultFuture.getOrThrow()
|
||||
printBalances()
|
||||
|
@ -94,7 +94,7 @@ Our flow has two parties (B and S for buyer and seller) and will proceed as foll
|
||||
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/flows/TwoPartyTradeFlow.kt``.
|
||||
You can find the implementation of this flow in the file ``finance/src/main/kotlin/net/corda/finance/TwoPartyTradeFlow.kt``.
|
||||
|
||||
Assuming no malicious termination, they both end the flow being in possession of a valid, signed transaction that
|
||||
represents an atomic asset swap.
|
||||
@ -235,7 +235,7 @@ Let's implement the ``Seller.call`` method. This will be run when the flow is in
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 4
|
||||
:end-before: DOCEND 4
|
||||
@ -269,7 +269,7 @@ OK, let's do the same for the buyer side:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 1
|
||||
:end-before: DOCEND 1
|
||||
@ -428,7 +428,7 @@ override ``checkTransaction()`` to add our own custom validation logic:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 5
|
||||
:end-before: DOCEND 5
|
||||
@ -504,7 +504,7 @@ A flow might declare some steps with code inside the flow class like this:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 2
|
||||
:end-before: DOCEND 2
|
||||
@ -545,7 +545,7 @@ steps by overriding the ``Step`` class like this:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/flows/TwoPartyTradeFlow.kt
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 3
|
||||
:end-before: DOCEND 3
|
||||
|
@ -37,17 +37,11 @@ Just as every Corda state must implement the ``ContractState`` interface, every
|
||||
// Implements the contract constraints in code.
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun verify(tx: LedgerTransaction)
|
||||
|
||||
// Expresses the contract constraints as legal prose.
|
||||
val legalContractReference: SecureHash
|
||||
}
|
||||
|
||||
You can read about function declarations in Kotlin `here <https://kotlinlang.org/docs/reference/functions.html>`_.
|
||||
|
||||
We can see that ``Contract`` expresses its constraints in two ways:
|
||||
|
||||
* In legal prose, through a hash referencing a legal contract that expresses the contract's constraints in legal prose
|
||||
* In code, through a ``verify`` function that takes a transaction as input, and:
|
||||
We can see that ``Contract`` expresses its constraints through a ``verify`` function that takes a transaction as input, and:
|
||||
|
||||
* Throws an ``IllegalArgumentException`` if it rejects the transaction proposal
|
||||
* Returns silently if it accepts the transaction proposal
|
||||
@ -67,7 +61,7 @@ transfer them or redeem them for cash. One way to enforce this behaviour would b
|
||||
|
||||
* Its value must be non-negative
|
||||
* The lender and the borrower cannot be the same entity
|
||||
* The IOU's borrower must sign the transaction
|
||||
* The IOU's lender must sign the transaction
|
||||
|
||||
We can picture this transaction as follows:
|
||||
|
||||
@ -79,18 +73,19 @@ Defining IOUContract
|
||||
--------------------
|
||||
|
||||
Let's write a contract that enforces these constraints. We'll do this by modifying either ``TemplateContract.java`` or
|
||||
``TemplateContract.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
||||
``App.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
package com.iou
|
||||
...
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
|
||||
open class IOUContract : Contract {
|
||||
...
|
||||
|
||||
class IOUContract : Contract {
|
||||
// Our Create command.
|
||||
class Create : CommandData
|
||||
|
||||
@ -109,19 +104,16 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi
|
||||
|
||||
// Constraints on the signers.
|
||||
"There must only be one signer." using (command.signers.toSet().size == 1)
|
||||
"The signer must be the borrower." using (command.signers.contains(out.borrower.owningKey))
|
||||
"The signer must be the lender." using (command.signers.contains(out.lender.owningKey))
|
||||
}
|
||||
}
|
||||
|
||||
// The legal contract reference - we'll leave this as a dummy hash for now.
|
||||
override val legalContractReference = SecureHash.zeroHash
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
package com.iou;
|
||||
package com.template.contract;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.template.state.IOUState;
|
||||
import net.corda.core.contracts.AuthenticatedObject;
|
||||
import net.corda.core.contracts.CommandData;
|
||||
import net.corda.core.contracts.Contract;
|
||||
@ -146,25 +138,23 @@ Let's write a contract that enforces these constraints. We'll do this by modifyi
|
||||
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1);
|
||||
|
||||
// IOU-specific constraints.
|
||||
final IOUState out = (IOUState) tx.getOutputs().getData().get(0);
|
||||
final IOUState out = (IOUState) tx.getOutputs().get(0).getData();
|
||||
final Party lender = out.getLender();
|
||||
final Party borrower = out.getBorrower();
|
||||
check.using("The IOU's value must be non-negative.",out.getValue() > 0);
|
||||
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
|
||||
|
||||
// Constraints on the signers.
|
||||
check.using("There must only be one signer.", ImmutableSet.of(command.getSigners()).size() == 1);
|
||||
check.using("The signer must be the borrower.", command.getSigners().contains(borrower.getOwningKey()));
|
||||
check.using("There must only be one signer.", command.getSigners().size() == 1);
|
||||
check.using("The signer must be the lender.", command.getSigners().contains(lender.getOwningKey()));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// The legal contract reference - we'll leave this as a dummy hash for now.
|
||||
private final SecureHash legalContractReference = SecureHash.Companion.getZeroHash();
|
||||
@Override public final SecureHash getLegalContractReference() { return legalContractReference; }
|
||||
}
|
||||
|
||||
If you're following along in Java, you'll also need to rename ``TemplateContract.java`` to ``IOUContract.java``.
|
||||
|
||||
Let's walk through this code step by step.
|
||||
|
||||
The Create command
|
||||
@ -175,7 +165,7 @@ The first thing we add to our contract is a *command*. Commands serve two functi
|
||||
example, a transaction proposing the creation of an IOU could have to satisfy different constraints to one redeeming
|
||||
an IOU
|
||||
* They allow us to define the required signers for the transaction. For example, IOU creation might require signatures
|
||||
from the borrower alone, whereas the transfer of an IOU might require signatures from both the IOU's borrower and lender
|
||||
from the lender only, whereas the transfer of an IOU might require signatures from both the IOU's borrower and lender
|
||||
|
||||
Our contract has one command, a ``Create`` command. All commands must implement the ``CommandData`` interface.
|
||||
|
||||
@ -215,7 +205,7 @@ following are true:
|
||||
* The transaction has inputs
|
||||
* The transaction doesn't have exactly one output
|
||||
* The IOU itself is invalid
|
||||
* The transaction doesn't require the borrower's signature
|
||||
* The transaction doesn't require the lender's signature
|
||||
|
||||
Command constraints
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@ -254,7 +244,7 @@ other statements - in this case, we're extracting the transaction's single ``IOU
|
||||
|
||||
Signer constraints
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Finally, we require the borrower's signature on the transaction. A transaction's required signers is equal to the union
|
||||
Finally, we require the lender's signature on the transaction. A transaction's required signers is equal to the union
|
||||
of all the signers listed on the commands. We therefore extract the signers from the ``Create`` command we
|
||||
retrieved earlier.
|
||||
|
||||
@ -266,7 +256,7 @@ We've now written an ``IOUContract`` constraining the evolution of each ``IOUSta
|
||||
* Creating an ``IOUState`` requires an issuance transaction with no inputs, a single ``IOUState`` output, and a
|
||||
``Create`` command
|
||||
* The ``IOUState`` created by the issuance transaction must have a non-negative value, and the lender and borrower
|
||||
must be different entities.
|
||||
must be different entities
|
||||
|
||||
Before we move on, make sure you go back and modify ``IOUState`` to point to the new ``IOUContract`` class.
|
||||
|
||||
|
@ -33,24 +33,20 @@ FlowLogic
|
||||
Flows are implemented as ``FlowLogic`` subclasses. You define the steps taken by the flow by overriding
|
||||
``FlowLogic.call``.
|
||||
|
||||
We'll write our flow in either ``TemplateFlow.java`` or ``TemplateFlow.kt``. Overwrite the existing template code with
|
||||
the following:
|
||||
We'll write our flow in either ``TemplateFlow.java`` or ``App.kt``. Overwrite both the existing flows in the template
|
||||
with the following:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
package com.iou
|
||||
...
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.flows.*
|
||||
|
||||
...
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
@ -79,7 +75,7 @@ the following:
|
||||
txBuilder.verify(serviceHub)
|
||||
|
||||
// Signing the transaction.
|
||||
val signedTx = serviceHub.toSignedTransaction(txBuilder)
|
||||
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(FinalityFlow(signedTx))
|
||||
@ -88,19 +84,17 @@ the following:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
package com.iou;
|
||||
package com.template.flow;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.template.contract.IOUContract;
|
||||
import com.template.state.IOUState;
|
||||
import net.corda.core.contracts.Command;
|
||||
import net.corda.core.flows.FlowException;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.InitiatingFlow;
|
||||
import net.corda.core.flows.StartableByRPC;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.transactions.TransactionBuilder;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
import net.corda.flows.FinalityFlow;
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
@ -118,6 +112,11 @@ the following:
|
||||
this.otherParty = otherParty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressTracker getProgressTracker() {
|
||||
return progressTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* The flow logic is encapsulated within the call() method.
|
||||
*/
|
||||
@ -128,7 +127,7 @@ the following:
|
||||
final Party me = getServiceHub().getMyInfo().getLegalIdentity();
|
||||
final Party notary = getServiceHub().getNetworkMapCache().getAnyNotary(null);
|
||||
|
||||
// We create a transaction builder
|
||||
// We create a transaction builder.
|
||||
final TransactionBuilder txBuilder = new TransactionBuilder();
|
||||
txBuilder.setNotary(notary);
|
||||
|
||||
@ -141,7 +140,7 @@ the following:
|
||||
txBuilder.verify(getServiceHub());
|
||||
|
||||
// Signing the transaction.
|
||||
final SignedTransaction signedTx = getServiceHub().toSignedTransaction(txBuilder);
|
||||
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(new FinalityFlow(signedTx));
|
||||
@ -150,6 +149,8 @@ the following:
|
||||
}
|
||||
}
|
||||
|
||||
If you're following along in Java, you'll also need to rename ``TemplateFlow.java`` to ``IOUFlow.java``.
|
||||
|
||||
We now have our own ``FlowLogic`` subclass that overrides ``FlowLogic.call``. There's a few things to note:
|
||||
|
||||
* ``FlowLogic.call`` has a return type that matches the type parameter passed to ``FlowLogic`` - this is type returned
|
||||
@ -163,6 +164,9 @@ We now have our own ``FlowLogic`` subclass that overrides ``FlowLogic.call``. Th
|
||||
* ``@InitiatingFlow`` means that this flow can be started directly by the node
|
||||
* ``StartableByRPC`` allows the node owner to start this flow via an RPC call
|
||||
|
||||
* We override the progress tracker, even though we are not providing any progress tracker steps yet. The progress
|
||||
tracker is required for the node shell to establish when the flow has ended
|
||||
|
||||
Let's walk through the steps of ``FlowLogic.call`` one-by-one:
|
||||
|
||||
Retrieving participant information
|
||||
|
@ -9,29 +9,32 @@ Running our CorDapp
|
||||
|
||||
Now that we've written a CorDapp, it's time to test it by running it on some real Corda nodes.
|
||||
|
||||
Clean up
|
||||
--------
|
||||
Before running our node, delete the ``client/TemplateClient.java`` (for Java) or ``client/TemplateClient.kt`` (for
|
||||
Kotlin) file. We won't be using it, and it will cause build errors unless we remove it.
|
||||
|
||||
Deploying our CorDapp
|
||||
---------------------
|
||||
Let's take a look at the nodes we're going to deploy. Open the project's build file under ``java-source/build.gradle``
|
||||
or ``kotlin-source/build.gradle`` and scroll down to the ``task deployNodes`` section. This section defines four
|
||||
nodes - the Controller, and NodeA, NodeB and NodeC:
|
||||
Let's take a look at the nodes we're going to deploy. Open the project's ``build.gradle`` file and scroll down to the
|
||||
``task deployNodes`` section. This section defines three nodes - the Controller, NodeA, and NodeB:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
directory "./build/nodes"
|
||||
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=GB"
|
||||
networkMap "CN=Controller,O=R3,OU=corda,L=London,C=UK"
|
||||
node {
|
||||
name "CN=Controller,O=R3,OU=corda,L=London,C=GB"
|
||||
name "CN=Controller,O=R3,OU=corda,L=London,C=UK"
|
||||
advertisedServices = ["corda.notary.validating"]
|
||||
p2pPort 10002
|
||||
rpcPort 10003
|
||||
webPort 10004
|
||||
cordapps = []
|
||||
}
|
||||
node {
|
||||
name "CN=NodeA,O=NodeA,L=London,C=GB"
|
||||
name "CN=NodeA,O=NodeA,L=London,C=UK"
|
||||
advertisedServices = []
|
||||
p2pPort 10005
|
||||
rpcPort 10006
|
||||
@ -48,15 +51,6 @@ nodes - the Controller, and NodeA, NodeB and NodeC:
|
||||
cordapps = []
|
||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
|
||||
}
|
||||
node {
|
||||
name "CN=NodeC,O=NodeC,L=Paris,C=FR"
|
||||
advertisedServices = []
|
||||
p2pPort 10011
|
||||
rpcPort 10012
|
||||
webPort 10013
|
||||
cordapps = []
|
||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
|
||||
}
|
||||
}
|
||||
|
||||
We have three standard nodes, plus a special Controller node that is running the network map service, and is also
|
||||
@ -80,8 +74,8 @@ We can do that now by running the following commands from the root of the projec
|
||||
|
||||
Running the nodes
|
||||
-----------------
|
||||
Running ``deployNodes`` will build the nodes under both ``java-source/build/nodes`` and ``kotlin-source/build/nodes``.
|
||||
If we navigate to one of these folders, we'll see four node folder. Each node folder has the following structure:
|
||||
Running ``deployNodes`` will build the nodes under ``build/nodes``. If we navigate to one of these folders, we'll see
|
||||
the three node folders. Each node folder has the following structure:
|
||||
|
||||
.. code:: python
|
||||
|
||||
@ -97,17 +91,11 @@ Let's start the nodes by running the following commands from the root of the pro
|
||||
|
||||
.. code:: python
|
||||
|
||||
// On Windows for a Java CorDapp
|
||||
java-source/build/nodes/runnodes.bat
|
||||
// On Windows
|
||||
build/nodes/runnodes.bat
|
||||
|
||||
// On Windows for a Kotlin CorDapp
|
||||
kotlin-source/build/nodes/runnodes.bat
|
||||
|
||||
// On Mac for a Java CorDapp
|
||||
java-source/build/nodes/runnodes
|
||||
|
||||
// On Mac for a Kotlin CorDapp
|
||||
kotlin-source/build/nodes/runnodes
|
||||
// On Mac
|
||||
build/nodes/runnodes
|
||||
|
||||
This will start a terminal window for each node, and an additional terminal window for each node's webserver - eight
|
||||
terminal windows in all. Give each node a moment to start - you'll know it's ready when its terminal windows displays
|
||||
@ -138,10 +126,8 @@ We want to create an IOU of 100 with Node B. We start the ``IOUFlow`` by typing:
|
||||
|
||||
start IOUFlow iouValue: 99, otherParty: "NodeB"
|
||||
|
||||
Node A and Node B will automatically agree an IOU.
|
||||
|
||||
If the flow worked, it should have led to the recording of a new IOU in the vaults of both Node A and Node B. Equally
|
||||
importantly, Node C - although it sits on the same network - should not be aware of this transaction.
|
||||
Node A and Node B will automatically agree an IOU. If the flow worked, it should have led to the recording of a new IOU
|
||||
in the vaults of both Node A and Node B.
|
||||
|
||||
We can check the flow has worked by using an RPC operation to check the contents of each node's vault. Typing ``run``
|
||||
will display a list of the available commands. We can examine the contents of a node's vault by running:
|
||||
@ -166,8 +152,7 @@ The vaults of Node A and Node B should both display the following output:
|
||||
value: 99
|
||||
lender: "CN=NodeA,O=NodeA,L=London,C=GB"
|
||||
borrower: "CN=NodeB,O=NodeB,L=New York,C=US"
|
||||
contract:
|
||||
legalContractReference: "559322B95BCF7913E3113962DC3F3CBD71C818C66977721580C045DC41C813A5"
|
||||
contract: {}
|
||||
participants:
|
||||
- "CN=NodeA,O=NodeA,L=London,C=GB"
|
||||
- "CN=NodeB,O=NodeB,L=New York,C=US"
|
||||
@ -178,13 +163,6 @@ The vaults of Node A and Node B should both display the following output:
|
||||
index: 0
|
||||
second: "(observable)"
|
||||
|
||||
But the vault of Node C should output nothing!
|
||||
|
||||
.. code:: python
|
||||
|
||||
first: []
|
||||
second: "(observable)"
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Like all CorDapps, our
|
||||
|
@ -64,31 +64,26 @@ you wish to add them later, its as simple as adding an additional property to yo
|
||||
|
||||
Defining IOUState
|
||||
-----------------
|
||||
Let's open ``TemplateState.java`` (for Java) or ``TemplateState.kt`` (for Kotlin) and update ``TemplateState`` to
|
||||
Let's open ``TemplateState.java`` (for Java) or ``App.kt`` (for Kotlin) and update ``TemplateState`` to
|
||||
define an ``IOUState``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
package com.iou
|
||||
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.identity.Party
|
||||
|
||||
class IOUState(val value: Int,
|
||||
val lender: Party,
|
||||
val borrower: Party,
|
||||
override val contract: TemplateContract) : ContractState {
|
||||
|
||||
val borrower: Party) : ContractState {
|
||||
override val contract = TemplateContract()
|
||||
override val participants get() = listOf(lender, borrower)
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
package com.iou;
|
||||
package com.template.state;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.template.contract.TemplateContract;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.Party;
|
||||
@ -99,7 +94,7 @@ define an ``IOUState``:
|
||||
private final int value;
|
||||
private final Party lender;
|
||||
private final Party borrower;
|
||||
private final IOUContract contract = new IOUContract();
|
||||
private final TemplateContract contract = new TemplateContract();
|
||||
|
||||
public IOUState(int value, Party lender, Party borrower) {
|
||||
this.value = value;
|
||||
@ -121,7 +116,7 @@ define an ``IOUState``:
|
||||
|
||||
@Override
|
||||
// TODO: Once we've defined IOUContract, come back and update this.
|
||||
public IOUContract getContract() {
|
||||
public TemplateContract getContract() {
|
||||
return contract;
|
||||
}
|
||||
|
||||
@ -131,6 +126,8 @@ define an ``IOUState``:
|
||||
}
|
||||
}
|
||||
|
||||
If you're following along in Java, you'll also need to rename ``TemplateState.java`` to ``IOUState.java``.
|
||||
|
||||
We've made the following changes:
|
||||
|
||||
* We've renamed ``TemplateState`` to ``IOUState``
|
||||
|
@ -25,11 +25,11 @@ Open a terminal window in the directory where you want to download the CorDapp t
|
||||
.. code-block:: text
|
||||
|
||||
# Clone the template from GitHub:
|
||||
git clone https://github.com/corda/cordapp-template-java.git ; cd cordapp-template
|
||||
git clone https://github.com/corda/cordapp-template-java.git ; cd cordapp-template-java
|
||||
|
||||
*or*
|
||||
|
||||
git clone https://github.com/corda/cordapp-template-kotlin.git ; cd cordapp-template
|
||||
git clone https://github.com/corda/cordapp-template-kotlin.git ; cd cordapp-template-kotlin
|
||||
|
||||
# Retrieve a list of the stable Milestone branches using:
|
||||
git branch -a --list *release-M*
|
||||
@ -39,35 +39,26 @@ Open a terminal window in the directory where you want to download the CorDapp t
|
||||
|
||||
Template structure
|
||||
------------------
|
||||
We can write our CorDapp in either Java or Kotlin, and will be providing the code in both languages throughout. If
|
||||
you want to write the CorDapp in Java, you'll be modifying the files under ``java-source``. If you prefer to use
|
||||
Kotlin, you'll be modifying the files under ``kotlin-source``.
|
||||
|
||||
To implement our IOU CorDapp, we'll only need to modify three files:
|
||||
We can write our CorDapp in either Java or Kotlin, and will be providing the code in both languages throughout. To
|
||||
implement our IOU CorDapp in Java, we'll need to modify three files. For Kotlin, we'll simply be modifying the
|
||||
``App.kt`` file:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
// 1. The state
|
||||
java-source/src/main/java/com/template/state/TemplateState.java
|
||||
src/main/java/com/template/state/TemplateState.java
|
||||
|
||||
// 2. The contract
|
||||
java-source/src/main/java/com/template/contract/TemplateContract.java
|
||||
src/main/java/com/template/contract/TemplateContract.java
|
||||
|
||||
// 3. The flow
|
||||
java-source/src/main/java/com/template/flow/TemplateFlow.java
|
||||
src/main/java/com/template/flow/TemplateFlow.java
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
// 1. The state
|
||||
kotlin-source/src/main/kotlin/com/template/state/TemplateState.kt
|
||||
|
||||
// 2. The contract
|
||||
kotlin-source/src/main/kotlin/com/template/contract/TemplateContract.kt
|
||||
|
||||
// 3. The flow
|
||||
kotlin-source/src/main/kotlin/com/template/flow/TemplateFlow.kt
|
||||
src/main/kotlin/com/template/App.kt
|
||||
|
||||
Progress so far
|
||||
---------------
|
||||
|
@ -8,6 +8,8 @@ Unreleased
|
||||
|
||||
* Merged handling of well known and confidential identities in the identity service.
|
||||
|
||||
* Remove `IssuerFlow` as it allowed nodes to request arbitrary amounts of cash to be issued from any remote node.
|
||||
|
||||
Milestone 14
|
||||
------------
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 208 KiB |
Binary file not shown.
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 199 KiB |
@ -41,7 +41,7 @@ To run from the command line in Windows:
|
||||
|
||||
1. Run ``gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples\trader-demo\build\nodes``
|
||||
2. Run ``samples\trader-demo\build\nodes\runnodes`` to open up four new terminals with the four nodes
|
||||
3. Run ``gradlew samples:trader-demo:runBuyer`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node
|
||||
3. Run ``gradlew samples:trader-demo:runBank`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node
|
||||
4. Run ``gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
|
||||
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
|
||||
to your terminal.
|
||||
|
@ -11,23 +11,33 @@ Remember that each state references a contract. The contract imposes constraints
|
||||
If the transaction does not obey the constraints of all the contracts of all its states, it cannot become a valid
|
||||
ledger update.
|
||||
|
||||
We need to modify our contract so that the lender's signature is required in any IOU creation transaction. This will
|
||||
only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final line of the
|
||||
``requireThat`` block as follows:
|
||||
We need to modify our contract so that the borrower's signature is required in any IOU creation transaction. This will
|
||||
only require changing a single line of code. In ``IOUContract.java``/``IOUContract.kt``, update the final two lines of
|
||||
the ``requireThat`` block as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
// Constraints on the signers.
|
||||
"There must be two signers." using (command.signers.toSet().size == 2)
|
||||
"The borrower and lender must be signers." using (command.signers.containsAll(listOf(
|
||||
out.borrower.owningKey, out.lender.owningKey)))
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
...
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
...
|
||||
|
||||
// Constraints on the signers.
|
||||
check.using("There must be two signers.", command.getSigners().size() == 2);
|
||||
check.using("The borrower and lender must be signers.", command.getSigners().containsAll(
|
||||
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey())));
|
||||
|
||||
Progress so far
|
||||
---------------
|
||||
Our contract now imposes an additional constraint - the lender must also sign an IOU creation transaction. Next, we
|
||||
need to update ``IOUFlow`` so that it actually gathers the counterparty's signature as part of the flow.
|
||||
Our contract now imposes an additional constraint - the borrower must also sign an IOU creation transaction. Next, we
|
||||
need to update ``IOUFlow`` so that it actually gathers the borrower's signature as part of the flow.
|
@ -9,17 +9,17 @@ Updating the flow
|
||||
|
||||
To update the flow, we'll need to do two things:
|
||||
|
||||
* Update the borrower's side of the flow to request the lender's signature
|
||||
* Create a flow for the lender to run in response to a signature request from the borrower
|
||||
* Update the lender's side of the flow to request the borrower's signature
|
||||
* Create a flow for the borrower to run in response to a signature request from the lender
|
||||
|
||||
Updating the borrower's flow
|
||||
----------------------------
|
||||
Updating the lender's flow
|
||||
--------------------------
|
||||
In the original CorDapp, we automated the process of notarising a transaction and recording it in every party's vault
|
||||
by invoking a built-in flow called ``FinalityFlow`` as a subflow. We're going to use another pre-defined flow, called
|
||||
``CollectSignaturesFlow``, to gather the lender's signature.
|
||||
``CollectSignaturesFlow``, to gather the borrower's signature.
|
||||
|
||||
We also need to add the lender's public key to the transaction's command, making the lender one of the required signers
|
||||
on the transaction.
|
||||
We also need to add the borrower's public key to the transaction's command, making the borrower one of the required
|
||||
signers on the transaction.
|
||||
|
||||
In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
||||
|
||||
@ -27,6 +27,8 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
...
|
||||
|
||||
// We add the items to the builder.
|
||||
val state = IOUState(iouValue, me, otherParty)
|
||||
val cmd = Command(IOUContract.Create(), listOf(me.owningKey, otherParty.owningKey))
|
||||
@ -36,16 +38,24 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
||||
txBuilder.verify(serviceHub)
|
||||
|
||||
// Signing the transaction.
|
||||
val signedTx = serviceHub.toSignedTransaction(txBuilder)
|
||||
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
||||
|
||||
// Obtaining the counterparty's signature
|
||||
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx))
|
||||
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, CollectSignaturesFlow.tracker()))
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(FinalityFlow(fullySignedTx))
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
...
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
...
|
||||
|
||||
// We add the items to the builder.
|
||||
IOUState state = new IOUState(iouValue, me, otherParty);
|
||||
List<PublicKey> requiredSigners = ImmutableList.of(me.getOwningKey(), otherParty.getOwningKey());
|
||||
@ -56,65 +66,42 @@ In ``IOUFlow.java``/``IOUFlow.kt``, update ``IOUFlow.call`` as follows:
|
||||
txBuilder.verify(getServiceHub());
|
||||
|
||||
// Signing the transaction.
|
||||
final SignedTransaction signedTx = getServiceHub().toSignedTransaction(txBuilder);
|
||||
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
|
||||
|
||||
// Obtaining the counterparty's signature
|
||||
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, null));
|
||||
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(signedTx, CollectSignaturesFlow.Companion.tracker()));
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(new FinalityFlow(fullySignedTx));
|
||||
|
||||
To make the lender a required signer, we simply add the lender's public key to the list of signers on the command.
|
||||
return null;
|
||||
|
||||
To make the borrower a required signer, we simply add the borrower's public key to the list of signers on the command.
|
||||
|
||||
``CollectSignaturesFlow``, meanwhile, takes a transaction signed by the flow initiator, and returns a transaction
|
||||
signed by all the transaction's other required signers. We then pass this fully-signed transaction into
|
||||
``FinalityFlow``.
|
||||
|
||||
The lender's flow
|
||||
-----------------
|
||||
Reorganising our class
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Before we define the lender's flow, let's reorganise ``IOUFlow.java``/``IOUFlow.kt`` a little bit:
|
||||
|
||||
* Rename ``IOUFlow`` to ``Initiator``
|
||||
* In Java, make the ``Initiator`` class static, rename its constructor to match the new name, and move the definition
|
||||
inside an enclosing ``IOUFlow`` class
|
||||
* In Kotlin, move the definition of ``Initiator`` class inside an enclosing ``IOUFlow`` singleton object
|
||||
|
||||
We will end up with the following structure:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
object IOUFlow {
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class Initiator(val iouValue: Int,
|
||||
val otherParty: Party) : FlowLogic<Unit>() {
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
public class IOUFlow {
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
public static class Initiator extends FlowLogic<Void> {
|
||||
|
||||
Writing the lender's flow
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Creating the borrower's flow
|
||||
----------------------------
|
||||
We're now ready to write the lender's flow, which will respond to the borrower's attempt to gather our signature.
|
||||
|
||||
Inside the ``IOUFlow`` class/singleton object, add the following class:
|
||||
In a new ``IOUFlowResponder.java`` file in Java, or within the ``App.kt`` file in Kotlin, add the following class:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
@InitiatedBy(Initiator::class)
|
||||
class Acceptor(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
...
|
||||
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
...
|
||||
|
||||
@InitiatedBy(IOUFlow::class)
|
||||
class IOUFlowResponder(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherParty, SignTransactionFlow.tracker()) {
|
||||
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||
val output = stx.tx.outputs.single().data
|
||||
"This must be an IOU transaction." using (output is IOUState)
|
||||
@ -129,12 +116,26 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
@InitiatedBy(Initiator.class)
|
||||
public static class Acceptor extends FlowLogic<Void> {
|
||||
package com.template.flow;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.template.state.IOUState;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.flows.FlowException;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.InitiatedBy;
|
||||
import net.corda.core.flows.SignTransactionFlow;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
@InitiatedBy(IOUFlow.class)
|
||||
public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
private final Party otherParty;
|
||||
|
||||
public Acceptor(Party otherParty) {
|
||||
public IOUFlowResponder(Party otherParty) {
|
||||
this.otherParty = otherParty;
|
||||
}
|
||||
|
||||
@ -142,8 +143,8 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
class signTxFlow extends SignTransactionFlow {
|
||||
private signTxFlow(Party otherParty) {
|
||||
super(otherParty, null);
|
||||
private signTxFlow(Party otherParty, ProgressTracker progressTracker) {
|
||||
super(otherParty, progressTracker);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -158,18 +159,19 @@ Inside the ``IOUFlow`` class/singleton object, add the following class:
|
||||
}
|
||||
}
|
||||
|
||||
subFlow(new signTxFlow(otherParty));
|
||||
subFlow(new signTxFlow(otherParty, SignTransactionFlow.Companion.tracker()));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
As with the ``Initiator``, our ``Acceptor`` flow is a ``FlowLogic`` subclass where we've overridden ``FlowLogic.call``.
|
||||
As with the ``IOUFlow``, our ``IOUFlowResponder`` flow is a ``FlowLogic`` subclass where we've overridden
|
||||
``FlowLogic.call``.
|
||||
|
||||
The flow is annotated with ``InitiatedBy(Initiator.class)``, which means that your node will invoke ``Acceptor.call``
|
||||
when it receives a message from a instance of ``Initiator`` running on another node. What will this message from the
|
||||
``Initiator`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that we'll be sent a
|
||||
``SignedTransaction``, and are expected to send back our signature over that transaction.
|
||||
The flow is annotated with ``InitiatedBy(IOUFlow.class)``, which means that your node will invoke
|
||||
``IOUFlowResponder.call`` when it receives a message from a instance of ``Initiator`` running on another node. What
|
||||
will this message from the ``IOUFlow`` be? If we look at the definition of ``CollectSignaturesFlow``, we can see that
|
||||
we'll be sent a ``SignedTransaction``, and are expected to send back our signature over that transaction.
|
||||
|
||||
We could handle this manually. However, there is also a pre-defined flow called ``SignTransactionFlow`` that can handle
|
||||
this process for us automatically. ``SignTransactionFlow`` is an abstract class, and we must subclass it and override
|
||||
@ -179,7 +181,7 @@ Once we've defined the subclass, we invoke it using ``FlowLogic.subFlow``, and t
|
||||
and the lender's flow is conducted automatically.
|
||||
|
||||
CheckTransactions
|
||||
~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^
|
||||
``SignTransactionFlow`` will automatically verify the transaction and its signatures before signing it. However, just
|
||||
because a transaction is valid doesn't mean we necessarily want to sign. What if we don't want to deal with the
|
||||
counterparty in question, or the value is too high, or we're not happy with the transaction's structure?
|
||||
@ -200,4 +202,4 @@ We can now run our updated CorDapp, using the instructions :doc:`here <hello-wor
|
||||
|
||||
Our CorDapp now requires agreement from both the lender and the borrower before an IOU can be created on the ledger.
|
||||
This prevents either the lender or the borrower from unilaterally updating the ledger in a way that only benefits
|
||||
themselves.
|
||||
themselves.
|
@ -6,5 +6,4 @@ Two-party flows
|
||||
|
||||
tut-two-party-introduction
|
||||
tut-two-party-contract
|
||||
tut-two-party-flow
|
||||
tut-two-party-running
|
||||
tut-two-party-flow
|
@ -12,14 +12,14 @@ elements:
|
||||
* An ``IOUContract``, controlling the evolution of IOUs over time
|
||||
* An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
|
||||
|
||||
However, in our original CorDapp, only the IOU's borrower was required to sign transactions issuing IOUs. The lender
|
||||
However, in our original CorDapp, only the IOU's lender was required to sign transactions issuing IOUs. The borrower
|
||||
had no say in whether the issuance of the IOU was a valid ledger update or not.
|
||||
|
||||
In this tutorial, we'll update our code so that the borrower requires the lender's agreement before they can issue an
|
||||
In this tutorial, we'll update our code so that the lender requires the borrower's agreement before they can issue an
|
||||
IOU onto the ledger. We'll need to make two changes:
|
||||
|
||||
* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the lender's
|
||||
signature (as well as the borrower's) to become valid ledger updates
|
||||
* The ``IOUFlow`` will need to be updated to allow for the gathering of the lender's signature
|
||||
* The ``IOUContract`` will need to be updated so that transactions involving an ``IOUState`` will require the borrower's
|
||||
signature (as well as the lender's) to become valid ledger updates
|
||||
* The ``IOUFlow`` will need to be updated to allow for the gathering of the borrower's signature
|
||||
|
||||
We'll start by updating the contract.
|
@ -1,28 +0,0 @@
|
||||
Running our CorDapp
|
||||
===================
|
||||
|
||||
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Like all CorDapps, our
|
||||
CorDapp is made up of three key parts:
|
||||
|
||||
* The ``IOUState``, representing IOUs on the ledger
|
||||
* The ``IOUContract``, controlling the evolution of IOUs over time
|
||||
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger.
|
||||
|
||||
Together, these three parts completely determine how IOUs are created and evolved on the ledger.
|
||||
|
||||
Next steps
|
||||
----------
|
||||
You should now be ready to develop your own CorDapps. There's
|
||||
`a more fleshed-out version of the IOU CorDapp <https://github.com/corda/cordapp-tutorial>`_
|
||||
with an API and web front-end, and a set of example CorDapps in
|
||||
`the main Corda repo <https://github.com/corda/corda>`_, under ``samples``. An explanation of how to run these
|
||||
samples :doc:`here <running-the-demos>`.
|
||||
|
||||
As you write CorDapps, you can learn more about the API available :doc:`here <api>`.
|
||||
|
||||
If you get stuck at any point, please reach out on `Slack <https://slack.corda.net/>`_,
|
||||
`Discourse <https://discourse.corda.net/>`_, or `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_.
|
@ -132,7 +132,7 @@ When starting a standalone node using a configuration file we must supply the RP
|
||||
.. code-block:: text
|
||||
|
||||
rpcUsers : [
|
||||
{ username=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
|
||||
{ username=user, password=password, permissions=[ StartFlow.net.corda.finance.flows.CashFlow ] }
|
||||
]
|
||||
|
||||
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar
|
||||
@ -143,7 +143,7 @@ manner:
|
||||
rpcUsers = [
|
||||
['username' : "user",
|
||||
'password' : "password",
|
||||
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
|
||||
'permissions' : ["StartFlow.net.corda.finance.flows.CashFlow"]]
|
||||
]
|
||||
|
||||
You can then deploy and launch the nodes (Notary and Alice) as follows:
|
||||
|
@ -66,8 +66,6 @@ We start by defining the ``CommercialPaper`` class. As in the previous tutorial,
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class CommercialPaper : Contract {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
||||
|
||||
override fun verify(tx: LedgerTransaction) = verifyClause(tx, Clauses.Group(), tx.commands.select<Commands>())
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -79,11 +77,6 @@ We start by defining the ``CommercialPaper`` class. As in the previous tutorial,
|
||||
.. sourcecode:: java
|
||||
|
||||
public class CommercialPaper implements Contract {
|
||||
@Override
|
||||
public SecureHash getLegalContractReference() {
|
||||
return SecureHash.Companion.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException {
|
||||
ClauseVerifier.verifyClause(tx, new Clauses.Group(), extractCommands(tx));
|
||||
|
@ -59,8 +59,6 @@ Kotlin syntax works.
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class CommercialPaper : Contract {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
TODO()
|
||||
}
|
||||
@ -69,22 +67,16 @@ Kotlin syntax works.
|
||||
.. sourcecode:: java
|
||||
|
||||
public class CommercialPaper implements Contract {
|
||||
@Override
|
||||
public SecureHash getLegalContractReference() {
|
||||
return SecureHash.Companion.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(LedgerTransaction tx) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
Every contract must have at least a ``getLegalContractReference()`` and a ``verify()`` method. In Kotlin we express
|
||||
a getter without a setter as an immutable property (val). The *legal contract reference* is supposed to be a hash
|
||||
of a document that describes the legal contract and may take precedence over the code, in case of a dispute.
|
||||
Every contract must have at least a ``verify()`` method.
|
||||
|
||||
.. note:: The way legal contract prose is bound to a smart contract implementation will change in future.
|
||||
.. 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.
|
||||
|
@ -407,10 +407,10 @@ list:
|
||||
.. sourcecode:: none
|
||||
|
||||
com.example.flow.ExampleFlow$Initiator
|
||||
net.corda.flows.CashExitFlow
|
||||
net.corda.flows.CashIssueFlow
|
||||
net.corda.flows.CashPaymentFlow
|
||||
net.corda.flows.ContractUpgradeFlow
|
||||
net.corda.finance.flows.CashExitFlow
|
||||
net.corda.finance.flows.CashIssueFlow
|
||||
net.corda.finance.flows.CashPaymentFlow
|
||||
net.corda.finance.flows.ContractUpgradeFlow
|
||||
|
||||
We can create a new IOU using the ``ExampleFlow$Initiator`` flow. For example, from the interactive shell of NodeA, you
|
||||
can agree an IOU of 50 with NodeB by running ``flow start Initiator iouValue: 50, otherParty: NodeB``.
|
||||
@ -450,8 +450,7 @@ We can see a list of the states in our node's vault using ``run vaultAndUpdates`
|
||||
linearId:
|
||||
externalId: null
|
||||
id: "84628565-2688-45ef-bb06-aae70fcf3be7"
|
||||
contract:
|
||||
legalContractReference: "4DDE2A47C361106CBAEC06CC40FE418A994822A3C8054851FEECD51207BFAF82"
|
||||
contract: {}
|
||||
participants:
|
||||
- "CN=NodeB,O=NodeB,L=New York,C=US"
|
||||
- "CN=NodeA,O=NodeA,L=London,C=UK"
|
||||
@ -485,8 +484,7 @@ abbreviated the output below):
|
||||
linearId:
|
||||
externalId: null
|
||||
id: "84628565-2688-45ef-bb06-aae70fcf3be7"
|
||||
contract:
|
||||
legalContractReference: "4DDE2A47C361106CBAEC06CC40FE418A994822A3C8054851FEECD51207BFAF82"
|
||||
contract: {}
|
||||
participants:
|
||||
- "CN=NodeB,O=NodeB,L=New York,C=US"
|
||||
- "CN=NodeA,O=NodeA,L=London,C=UK"
|
||||
|
@ -87,9 +87,9 @@ we just use a helper that handles it for us. We also assume that we already have
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val inputState = StateAndRef(sate, stateRef)
|
||||
val moveTransactionBuilder = DummyContract.move(inputState, newOwner = aliceParty.owningKey)
|
||||
val moveTransactionBuilder = DummyContract.withNewOwnerAndAmount(inputState, newOwner = aliceParty.owningKey)
|
||||
|
||||
The ``DummyContract.move()`` method will a new transaction builder with our old state as the input, a new state
|
||||
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:
|
||||
|
@ -92,6 +92,8 @@ The ``WebServerPluginRegistry`` class defines the following:
|
||||
be distributed within the CorDapp jars. This static content will not be available if the bundled web server is not
|
||||
started
|
||||
|
||||
* ``customizeJSONSerialization``, which can be overridden to register custom JSON serializers if required by the REST api.
|
||||
|
||||
* The static web content itself should be placed inside the ``src/main/resources`` directory
|
||||
|
||||
To learn about how to use gradle to build your cordapp against Corda and generate an artifact please read
|
||||
|
Reference in New Issue
Block a user