mirror of
https://github.com/corda/corda.git
synced 2025-06-14 13:18:18 +00:00
Merge remote-tracking branch 'open/master' into colljos-os-merge-rc01
This commit is contained in:
@ -1,15 +1,18 @@
|
||||
API: Contract Constraints
|
||||
=========================
|
||||
|
||||
A basic understanding of contract key concepts, which can be found :doc:`here </key-concepts-contracts>`,
|
||||
is required reading for this page.
|
||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
||||
|
||||
.. contents::
|
||||
|
||||
Contract constraints
|
||||
--------------------
|
||||
Transaction states specify a constraint over the contract that will be used to verify it. For a transaction to be
|
||||
valid, the ``verify`` function associated with each state must run successfully. However, for this to be secure, it is
|
||||
not sufficient to specify the ``verify`` function by name as there may exist multiple different implementations with
|
||||
the same method signature and enclosing class. Contract constraints solve this problem by allowing a contract developer
|
||||
to constrain which ``verify`` functions out of the universe of implementations can be used (i.e. the universe is
|
||||
everything that matches the signature and contract constraints restricts this universe to a subset).
|
||||
everything that matches the signature and contract constraints restrict this universe to a subset).
|
||||
|
||||
A typical constraint is the hash of the CorDapp JAR that contains the contract and states but will in future releases
|
||||
include constraints that require specific signers of the JAR, or both the signer and the hash. Constraints can be
|
||||
@ -60,10 +63,10 @@ that they were loaded from. This makes it possible to find the attachment for an
|
||||
automatic resolution of attachments is done by the ``TransactionBuilder`` and how, when verifying the constraints and
|
||||
contracts, attachments are associated with their respective contracts.
|
||||
|
||||
Implementations
|
||||
---------------
|
||||
Implementations of AttachmentConstraint
|
||||
---------------------------------------
|
||||
|
||||
There are three implementations of ``AttachmentConstraints`` with more planned in the future.
|
||||
There are three implementations of ``AttachmentConstraint`` with more planned in the future.
|
||||
|
||||
``AlwaysAcceptAttachmentConstraint``: Any attachment (except a missing one) will satisfy this constraint.
|
||||
|
||||
@ -137,3 +140,18 @@ Full Nodes
|
||||
**********
|
||||
|
||||
When testing against full nodes simply place your CorDapp into the cordapps directory of the node.
|
||||
|
||||
Debugging
|
||||
---------
|
||||
If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are two
|
||||
common sources of ``MissingContractAttachments`` exceptions:
|
||||
|
||||
Not setting CorDapp packages in tests
|
||||
*************************************
|
||||
You are running a test and have not specified the CorDapp packages to scan. See the instructions above.
|
||||
|
||||
Wrong fully-qualified contract name
|
||||
***********************************
|
||||
You are specifying the fully-qualified name of the contract incorrectly. For example, you've defined ``MyContract`` in
|
||||
the package ``com.mycompany.myapp.contracts``, but the fully-qualified contract name you pass to the
|
||||
``TransactionBuilder`` is ``com.mycompany.myapp.MyContract`` (instead of ``com.mycompany.myapp.contracts.MyContract``).
|
@ -55,8 +55,8 @@ Helper methods are also provided with default values for arguments:
|
||||
The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of
|
||||
filter criteria:
|
||||
|
||||
- Use ``queryBy`` to obtain a only current snapshot of data (for a given ``QueryCriteria``)
|
||||
- Use ``trackBy`` to obtain a both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
|
||||
- Use ``queryBy`` to obtain a current snapshot of data (for a given ``QueryCriteria``)
|
||||
- Use ``trackBy`` to obtain both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
|
||||
|
||||
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL)
|
||||
|
||||
|
@ -6,6 +6,7 @@ CorDapps
|
||||
|
||||
cordapp-overview
|
||||
writing-a-cordapp
|
||||
upgrade-notes
|
||||
cordapp-build-systems
|
||||
building-against-master
|
||||
corda-api
|
||||
|
@ -6,6 +6,50 @@ from the previous milestone release.
|
||||
|
||||
UNRELEASED
|
||||
----------
|
||||
|
||||
* The network map service concept has been re-designed. More information can be found in :doc:`network-map`.
|
||||
|
||||
* The previous design was never intended to be final but was rather a quick implementation in the earliest days of the
|
||||
Corda project to unblock higher priority items. It suffers from numerous disadvantages including lack of scalability,
|
||||
as one node is expected to hold open and manage connections to every node on the network; not reliable; hard to defend
|
||||
against DoS attacks; etc.
|
||||
|
||||
* There is no longer a special network map node for distributing the network map to the other nodes. Instead the network
|
||||
map is now a collection of signed ``NodeInfo`` files distributed via HTTP.
|
||||
|
||||
* The ``certificateSigningService`` config has been replaced by ``compatibilityZoneURL`` which is the base URL for the
|
||||
doorman registration and for downloading the network map. There is also an end-point for the node to publish its node-info
|
||||
object, which the node does each time it changes. ``networkMapService`` config has been removed.
|
||||
|
||||
* To support local and test deployments, the node polls the ``additional-node-infos`` directory for these signed ``NodeInfo``
|
||||
objects which are stored in its local cache. On startup the node generates its own signed file with the filename format
|
||||
"nodeInfo-*". This can be copied to every node's ``additional-node-infos`` directory that is part of the network.
|
||||
|
||||
* Cordform (which is the ``deployNodes`` gradle task) does this copying automatically for the demos. The ``NetworkMap``
|
||||
parameter is no longer needed.
|
||||
|
||||
* For test deployments we've introduced a bootstrapping tool (see :doc:`setting-up-a-corda-network`).
|
||||
|
||||
* ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` configs have been
|
||||
removed. The configuration of notaries has been simplified into a single ``notary`` config object. See
|
||||
:doc:`corda-configuration-file` for more details.
|
||||
|
||||
* Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
|
||||
to correctly interop.
|
||||
|
||||
* The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
|
||||
their X500 name.
|
||||
|
||||
* Single node notaries no longer have a second separate notary identity. Their main identity *is* their notary identity.
|
||||
Use ``NetworkMapCache.notaryIdentities`` to get the list of available notaries.
|
||||
|
||||
* The common name in the node's X500 legal name is no longer reserved and can be used as part of the node's name.
|
||||
|
||||
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
|
||||
was needed to allow changes to the schema.
|
||||
|
||||
* Support for external user credentials data source and password encryption [CORDA-827].
|
||||
|
||||
* Integrate database migration tool: http://www.liquibase.org/ :
|
||||
* The migration files are split per ``MappedSchemas``. (added new property: migrationResource used to point to the resource file containing the db changes corresponding to the JPA entities)
|
||||
* config flag ``database.initialiseSchema`` was renamed to: ``database.runMigration`` (if true then the migration is run during startup just before hibernate is initialised.)
|
||||
@ -45,24 +89,12 @@ UNRELEASED
|
||||
deploys nodes for development and testing, the latter turns a project into a cordapp project that generates JARs in
|
||||
the standard CorDapp format.
|
||||
|
||||
* ``Cordform`` and node identity generation:
|
||||
* Removed the parameter ``NetworkMap`` from Cordform. Now at the end of the deployment the following happens:
|
||||
1. Each node is started and its signed serialized NodeInfo is written to disk in the node base directory.
|
||||
2. Every serialized ``NodeInfo`` above is copied in every other node "additional-node-info" folder under the NodeInfo folder.
|
||||
|
||||
* Nodes read and poll the filesystem for serialized ``NodeInfo`` in the ``additional-node-info`` directory.
|
||||
|
||||
* ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup.
|
||||
|
||||
* Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't
|
||||
either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is
|
||||
thrown.
|
||||
|
||||
* ``extraAdvertisedServiceIds`` config has been removed as part of the previous work to retire the concept of advertised
|
||||
services. The remaining use of this config was for notaries, the configuring of which has been cleaned up and simplified.
|
||||
``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` have also been removed and replaced by a single
|
||||
``notary`` config object. See :doc:`corda-configuration-file` for more details.
|
||||
|
||||
* Gradle task ``deployNodes`` can have an additional parameter ``configFile`` with the path to a properties file
|
||||
to be appended to node.conf.
|
||||
|
||||
@ -79,8 +111,8 @@ UNRELEASED
|
||||
allows services to start flows marked with the ``StartableByService`` annotation. For backwards compatability
|
||||
service classes with only ``ServiceHub`` constructors will still work.
|
||||
|
||||
* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the
|
||||
time-window is open-ended.
|
||||
* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window as a ``java.time.Duration`` object,
|
||||
or ``null`` if the time-window isn't closed.
|
||||
|
||||
* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command``
|
||||
signers.
|
||||
@ -96,18 +128,16 @@ UNRELEASED
|
||||
* The ``ReceiveTransactionFlow`` can now be told to record the transaction at the same time as receiving it. Using this
|
||||
feature, better support for observer/regulator nodes has been added. See :doc:`tutorial-observer-nodes`.
|
||||
|
||||
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This is
|
||||
needed to allow new ``node_info_hash`` column to be added for the network map redesign work.
|
||||
|
||||
* Added an overload of ``TransactionWithSignatures.verifySignaturesExcept`` which takes in a collection of ``PublicKey``s.
|
||||
|
||||
* Replaced node configuration parameter ``certificateSigningService`` with ``compatibilityZoneURL``, which is Corda
|
||||
compatibility zone network management service's address.
|
||||
* ``DriverDSLExposedInterface`` has been renamed to ``DriverDSL`` and the ``waitForAllNodesToFinish()`` method has instead
|
||||
become a parameter on driver creation.
|
||||
|
||||
* ``waitForAllNodesToFinish()`` method in ``DriverDSLExposedInterface`` has instead become a parameter on driver creation.
|
||||
* Values for the ``database.transactionIsolationLevel`` config now follow the ``java.sql.Connection`` int constants but
|
||||
without the "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
|
||||
|
||||
* ``database.transactionIsolationLevel`` values now follow the ``java.sql.Connection`` int constants but without the
|
||||
"TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
|
||||
* Peer-to-peer communications is now via AMQP 1.0 as default.
|
||||
Although the legacy Artemis CORE bridging can still be used by setting the ``useAMQPBridges`` configuration property to false.
|
||||
|
||||
* Enterprise Corda only: Compatibility with SQL Server 2017 and SQL Azure databases.
|
||||
|
||||
|
@ -26,7 +26,7 @@ permissions that RPC can use for fine-grain access control.
|
||||
|
||||
These users are added to the node's ``node.conf`` file.
|
||||
|
||||
The syntax for adding an RPC user is:
|
||||
The simplest way of adding an RPC user is to include it in the ``rpcUsers`` list:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -59,9 +59,6 @@ Users need permissions to invoke any RPC call. By default, nothing is allowed. T
|
||||
...
|
||||
]
|
||||
|
||||
.. 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.
|
||||
|
||||
Permissions Syntax
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -71,6 +68,134 @@ Fine grained permissions allow a user to invoke a specific RPC operation, or to
|
||||
- to invoke a RPC operation: ``InvokeRpc.<rpc method name>`` e.g., ``InvokeRpc.nodeInfo``.
|
||||
.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows.
|
||||
|
||||
RPC security management
|
||||
-----------------------
|
||||
|
||||
Setting ``rpcUsers`` provides a simple way of granting RPC permissions to a fixed set of users, but has some
|
||||
obvious shortcomings. To support use cases aiming for higher security and flexibility, Corda offers additional security
|
||||
features such as:
|
||||
|
||||
* Fetching users credentials and permissions from an external data source (e.g.: a remote RDBMS), with optional in-memory
|
||||
caching. In particular, this allows credentials and permissions to be updated externally without requiring nodes to be
|
||||
restarted.
|
||||
* Password stored in hash-encrypted form. This is regarded as must-have when security is a concern. Corda currently supports
|
||||
a flexible password hash format conforming to the Modular Crypt Format provided by the `Apache Shiro framework <https://shiro.apache.org/static/1.2.5/apidocs/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.html>`_
|
||||
|
||||
These features are controlled by a set of options nested in the ``security`` field of ``node.conf``.
|
||||
The following example shows how to configure retrieval of users credentials and permissions from a remote database with
|
||||
passwords in hash-encrypted format and enable in-memory caching of users data:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "DB",
|
||||
passwordEncryption = "SHIRO_1_CRYPT",
|
||||
connection = {
|
||||
jdbcUrl = "<jdbc connection string>"
|
||||
username = "<db username>"
|
||||
password = "<db user password>"
|
||||
driverClassName = "<JDBC driver>"
|
||||
}
|
||||
}
|
||||
options = {
|
||||
cache = {
|
||||
expireAfterSecs = 120
|
||||
maxEntries = 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It is also possible to have a static list of users embedded in the ``security`` structure by specifying a ``dataSource``
|
||||
of ``INMEMORY`` type:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "INMEMORY",
|
||||
users = [
|
||||
{
|
||||
username = "<username>",
|
||||
password = "<password>",
|
||||
permissions = ["<permission 1>", "<permission 2>", ...]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. warning:: A valid configuration cannot specify both the ``rpcUsers`` and ``security`` fields. Doing so will trigger
|
||||
an exception at node startup.
|
||||
|
||||
Authentication/authorisation data
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``dataSource`` structure defines the data provider supplying credentials and permissions for users. There exist two
|
||||
supported types of such data source, identified by the ``dataSource.type`` field:
|
||||
|
||||
:INMEMORY: A static list of user credentials and permissions specified by the ``users`` field.
|
||||
|
||||
:DB: An external RDBMS accessed via the JDBC connection described by ``connection``. Note that, unlike the ``INMEMORY``
|
||||
case, in a user database permissions are assigned to *roles* rather than individual users. The current implementation
|
||||
expects the database to store data according to the following schema:
|
||||
|
||||
- Table ``users`` containing columns ``username`` and ``password``. The ``username`` column *must have unique values*.
|
||||
- Table ``user_roles`` containing columns ``username`` and ``role_name`` associating a user to a set of *roles*.
|
||||
- Table ``roles_permissions`` containing columns ``role_name`` and ``permission`` associating a role to a set of
|
||||
permission strings.
|
||||
|
||||
.. note:: There is no prescription on the SQL type of each column (although our tests were conducted on ``username`` and
|
||||
``role_name`` declared of SQL type ``VARCHAR`` and ``password`` of ``TEXT`` type). It is also possible to have extra columns
|
||||
in each table alongside the expected ones.
|
||||
|
||||
Password encryption
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Storing passwords in plain text is discouraged in applications where security is critical. Passwords are assumed
|
||||
to be in plain format by default, unless a different format is specified by the ``passwordEncryption`` field, like:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
passwordEncryption = SHIRO_1_CRYPT
|
||||
|
||||
``SHIRO_1_CRYPT`` identifies the `Apache Shiro fully reversible
|
||||
Modular Crypt Format <https://shiro.apache.org/static/1.2.5/apidocs/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.html>`_,
|
||||
it is currently the only non-plain password hash-encryption format supported. Hash-encrypted passwords in this
|
||||
format can be produced by using the `Apache Shiro Hasher command line tool <https://shiro.apache.org/command-line-hasher.html>`_.
|
||||
|
||||
Caching user accounts data
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A cache layer on top of the external data source of users credentials and permissions can significantly improve
|
||||
performances in some cases, with the disadvantage of causing a (controllable) delay in picking up updates to the underlying data.
|
||||
Caching is disabled by default, it can be enabled by defining the ``options.cache`` field in ``security.authService``,
|
||||
for example:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
options = {
|
||||
cache = {
|
||||
expireAfterSecs = 120
|
||||
maxEntries = 10000
|
||||
}
|
||||
}
|
||||
|
||||
This will enable a non-persistent cache contained in the node's memory with maximum number of entries set to ``maxEntries``
|
||||
where entries are expired and refreshed after ``expireAfterSecs`` seconds.
|
||||
|
||||
Observables
|
||||
-----------
|
||||
The RPC system handles observables in a special way. When a method returns an observable, whether directly or
|
||||
|
@ -101,6 +101,9 @@ path to the node's base directory.
|
||||
|
||||
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
|
||||
|
||||
:security: Contains various nested fields controlling user authentication/authorization, in particular for RPC accesses. See
|
||||
:doc:`clientrpc` for details.
|
||||
|
||||
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
|
||||
|
||||
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
|
||||
@ -191,4 +194,7 @@ path to the node's base directory.
|
||||
:sshPort: Port to be used for SSH connection, default ``22``.
|
||||
|
||||
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
|
||||
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
||||
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
||||
|
||||
:useAMQPBridges: Optionally can be set to ``false`` to use Artemis CORE Bridges for peer-to-peer communications.
|
||||
Otherwise, defaults to ``true`` and the AMQP 1.0 protocol will be used for message transfer between nodes.
|
@ -10,7 +10,6 @@ Corda nodes
|
||||
corda-configuration-file
|
||||
clientrpc
|
||||
shell
|
||||
node-auth-config
|
||||
node-database
|
||||
node-administration
|
||||
out-of-process-verification
|
@ -36,6 +36,7 @@ dependencies {
|
||||
compile project(':core')
|
||||
compile project(':client:jfx')
|
||||
compile project(':node-driver')
|
||||
compile project(':webserver')
|
||||
testCompile project(':verifier')
|
||||
testCompile project(':test-utils')
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
package com.template;
|
||||
|
||||
import net.corda.core.contracts.CommandData;
|
||||
import net.corda.core.contracts.Contract;
|
@ -1,17 +1,21 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
|
||||
// DOCSTART 01
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.template.TemplateContract;
|
||||
import net.corda.core.flows.*;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.Command;
|
||||
import net.corda.core.contracts.CommandData;
|
||||
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 static net.corda.docs.java.tutorial.helloworld.TemplateContract.TEMPLATE_CONTRACT_ID;
|
||||
import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID;
|
||||
|
||||
// Replace TemplateFlow's definition with:
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
public class IOUFlow extends FlowLogic<Void> {
|
||||
|
@ -1,13 +1,15 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
|
||||
// DOCSTART 01
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.Party;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.identity.Party;
|
||||
|
||||
// Replace TemplateState's definition with:
|
||||
public class IOUState implements ContractState {
|
||||
private final int value;
|
||||
private final Party lender;
|
||||
|
@ -3,48 +3,33 @@ package net.corda.docs.java.tutorial.testdsl;
|
||||
import kotlin.Unit;
|
||||
import net.corda.core.contracts.PartyAndReference;
|
||||
import net.corda.core.identity.CordaX500Name;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.finance.contracts.ICommercialPaperState;
|
||||
import net.corda.finance.contracts.JavaCommercialPaper;
|
||||
import net.corda.finance.contracts.asset.Cash;
|
||||
import net.corda.node.services.api.IdentityServiceInternal;
|
||||
import net.corda.testing.SerializationEnvironmentRule;
|
||||
import net.corda.testing.node.MockServices;
|
||||
import net.corda.testing.TestIdentity;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.corda.core.crypto.Crypto.generateKeyPair;
|
||||
import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.finance.Currencies.issuedBy;
|
||||
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
|
||||
import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
|
||||
import static net.corda.testing.node.NodeTestUtils.ledger;
|
||||
import static net.corda.testing.node.NodeTestUtils.transaction;
|
||||
import static net.corda.testing.CoreTestUtils.rigorousMock;
|
||||
import static net.corda.testing.TestConstants.*;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
public class CommercialPaperTest {
|
||||
private static final TestIdentity ALICE = new TestIdentity(getALICE_NAME(), 70L);
|
||||
private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L);
|
||||
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic();
|
||||
private static final TestIdentity BOB = new TestIdentity(getBOB_NAME(), 80L);
|
||||
private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L);
|
||||
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
||||
private static final Party DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L).getParty();
|
||||
@Rule
|
||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
private final byte[] defaultRef = {123};
|
||||
private final MockServices ledgerServices;
|
||||
|
||||
{
|
||||
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
|
||||
doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey());
|
||||
doReturn(null).when(identityService).partyFromKey(BIG_CORP_PUBKEY);
|
||||
doReturn(null).when(identityService).partyFromKey(ALICE.getPubkey());
|
||||
ledgerServices = new MockServices(identityService, MEGA_CORP.getName());
|
||||
}
|
||||
private final MockServices ledgerServices = new MockServices(emptyList(), makeTestIdentityService(MEGA_CORP.getIdentity()));
|
||||
|
||||
// DOCSTART 1
|
||||
private ICommercialPaperState getPaper() {
|
||||
@ -52,7 +37,7 @@ public class CommercialPaperTest {
|
||||
MEGA_CORP.ref(defaultRef),
|
||||
MEGA_CORP.getParty(),
|
||||
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)),
|
||||
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
|
||||
TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
|
||||
);
|
||||
}
|
||||
// DOCEND 1
|
||||
@ -61,7 +46,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCP() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
@ -76,10 +61,10 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMove() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
return tx.verifies();
|
||||
});
|
||||
@ -92,10 +77,10 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMoveFails() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
return tx.failsWith("the state is propagated");
|
||||
});
|
||||
@ -108,10 +93,10 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMoveSuccess() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.failsWith("the state is propagated");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty()));
|
||||
@ -125,17 +110,17 @@ public class CommercialPaperTest {
|
||||
// DOCSTART 6
|
||||
@Test
|
||||
public void simpleIssuanceWithTweak() {
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
|
||||
tw.timeWindow(getTEST_TX_TIME());
|
||||
tw.timeWindow(TEST_TX_TIME);
|
||||
return tw.failsWith("output states are issued by a command signer");
|
||||
});
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
@ -146,16 +131,16 @@ public class CommercialPaperTest {
|
||||
// DOCSTART 7
|
||||
@Test
|
||||
public void simpleIssuanceWithTweakTopLevelTx() {
|
||||
transaction(ledgerServices, DUMMY_NOTARY, tx -> {
|
||||
transaction(ledgerServices, tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
|
||||
tw.timeWindow(getTEST_TX_TIME());
|
||||
tw.timeWindow(TEST_TX_TIME);
|
||||
return tw.failsWith("output states are issued by a command signer");
|
||||
});
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
}
|
||||
@ -165,7 +150,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaper() {
|
||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
||||
@ -176,9 +161,9 @@ public class CommercialPaperTest {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -188,8 +173,8 @@ public class CommercialPaperTest {
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
||||
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
@ -201,7 +186,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaperDoubleSpend() {
|
||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
||||
@ -212,9 +197,9 @@ public class CommercialPaperTest {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -224,8 +209,8 @@ public class CommercialPaperTest {
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
||||
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -234,7 +219,7 @@ public class CommercialPaperTest {
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
// We moved a paper to other pubkey.
|
||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
l.fails();
|
||||
@ -247,7 +232,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaperTweak() {
|
||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
||||
@ -258,9 +243,9 @@ public class CommercialPaperTest {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -270,8 +255,8 @@ public class CommercialPaperTest {
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
||||
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -281,7 +266,7 @@ public class CommercialPaperTest {
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
// We moved a paper to another pubkey.
|
||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
lw.fails();
|
||||
|
@ -1,20 +1,25 @@
|
||||
package net.corda.docs.java.tutorial.twoparty;
|
||||
|
||||
// DOCSTART 01
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.CommandData;
|
||||
import net.corda.core.contracts.CommandWithParties;
|
||||
import net.corda.core.contracts.Contract;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.LedgerTransaction;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.CommandWithParties;
|
||||
import net.corda.core.identity.Party;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
// Replace TemplateContract's definition with:
|
||||
public class IOUContract implements Contract {
|
||||
public static final String IOU_CONTRACT_ID = "com.template.IOUContract";
|
||||
|
||||
// Our Create command.
|
||||
public static class Create implements CommandData {
|
||||
}
|
||||
|
@ -45,15 +45,14 @@ public class IOUFlow extends FlowLogic<Void> {
|
||||
// We retrieve the notary identity from the network map.
|
||||
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
|
||||
|
||||
// DOCSTART 02
|
||||
// We create a transaction builder.
|
||||
final TransactionBuilder txBuilder = new TransactionBuilder();
|
||||
txBuilder.setNotary(notary);
|
||||
|
||||
// DOCSTART 02
|
||||
// We create the transaction components.
|
||||
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
|
||||
String outputContract = IOUContract.class.getName();
|
||||
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
|
||||
StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.IOU_CONTRACT_ID);
|
||||
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
|
||||
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
package net.corda.docs.java.tutorial.twoparty;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
import net.corda.docs.java.tutorial.helloworld.IOUFlow;
|
||||
import net.corda.docs.java.tutorial.helloworld.IOUState;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
// Define IOUFlowResponder:
|
||||
@InitiatedBy(IOUFlow.class)
|
||||
public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
private final FlowSession otherPartySession;
|
||||
|
@ -1,16 +1,19 @@
|
||||
package net.corda.docs.tutorial.helloworld
|
||||
|
||||
// DOCSTART 01
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
|
||||
// Replace TemplateFlow's definition with:
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class IOUFlow(val iouValue: Int,
|
||||
|
@ -1,9 +1,12 @@
|
||||
package net.corda.docs.tutorial.helloworld
|
||||
|
||||
// DOCSTART 01
|
||||
import net.corda.core.contracts.ContractState
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.identity.Party
|
||||
|
||||
// Replace TemplateState's definition with:
|
||||
class IOUState(val value: Int,
|
||||
val lender: Party,
|
||||
val borrower: Party) : ContractState {
|
||||
|
@ -67,7 +67,7 @@ class TutorialMockNetwork {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = MockNetwork()
|
||||
mockNet = MockNetwork(emptyList())
|
||||
nodeA = mockNet.createPartyNode()
|
||||
nodeB = mockNet.createPartyNode()
|
||||
nodeB.registerInitiatedFlow(FlowB::class.java)
|
||||
|
@ -14,6 +14,7 @@ import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import net.corda.testing.node.transaction
|
||||
@ -28,15 +29,15 @@ class CommercialPaperTest {
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val ALICE get() = alice.party
|
||||
val ALICE_PUBKEY get() = alice.pubkey
|
||||
val ALICE_PUBKEY get() = alice.publicKey
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
|
@ -1,12 +1,16 @@
|
||||
package net.corda.docs.tutorial.twoparty
|
||||
|
||||
// DOCSTART 01
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.requireSingleCommand
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.*
|
||||
|
||||
// Replace IOUContract's contract ID and definition with:
|
||||
val IOU_CONTRACT_ID = "com.template.IOUContract"
|
||||
|
||||
class IOUContract : Contract {
|
||||
// Our Create command.
|
||||
class Create : CommandData
|
||||
@ -20,7 +24,7 @@ class IOUContract : Contract {
|
||||
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
|
||||
|
||||
// IOU-specific constraints.
|
||||
val out = tx.outputsOfType<net.corda.docs.tutorial.helloworld.IOUState>().single()
|
||||
val out = tx.outputsOfType<IOUState>().single()
|
||||
"The IOU's value must be non-negative." using (out.value > 0)
|
||||
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
|
||||
|
||||
|
@ -6,9 +6,17 @@ import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.StateAndContract
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import net.corda.webserver.services.WebServerPluginRegistry
|
||||
import java.util.function.Function
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.Produces
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
// DOCEND 01
|
||||
|
||||
@InitiatingFlow
|
||||
@ -25,14 +33,13 @@ class IOUFlow(val iouValue: Int,
|
||||
// We retrieve the notary identity from the network map.
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
|
||||
// We create a transaction builder
|
||||
// DOCSTART 02
|
||||
// We create a transaction builder.
|
||||
val txBuilder = TransactionBuilder(notary = notary)
|
||||
|
||||
// DOCSTART 02
|
||||
// We create the transaction components.
|
||||
val outputState = IOUState(iouValue, ourIdentity, otherParty)
|
||||
val outputContract = IOUContract::class.jvmName
|
||||
val outputContractAndState = StateAndContract(outputState, outputContract)
|
||||
val outputContractAndState = StateAndContract(outputState, IOU_CONTRACT_ID)
|
||||
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
|
||||
|
||||
// We add the items to the builder.
|
||||
|
@ -1,16 +1,19 @@
|
||||
package net.corda.docs.tutorial.twoparty
|
||||
|
||||
// DOCSTART 01
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.SignTransactionFlow
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.docs.tutorial.helloworld.IOUFlow
|
||||
import net.corda.docs.tutorial.helloworld.IOUState
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
// Define IOUFlowResponder:
|
||||
@InitiatedBy(IOUFlow::class)
|
||||
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
Writing the flow
|
||||
================
|
||||
A flow encodes a sequence of steps that a node can run to achieve a specific ledger update. By installing new flows on
|
||||
a node, we allow the node to handle new business processes. The flow we define will allow a node to issue an
|
||||
A flow encodes a sequence of steps that a node can perform to achieve a specific ledger update. By installing new flows
|
||||
on a node, we allow the node to handle new business processes. The flow we define will allow a node to issue an
|
||||
``IOUState`` onto the ledger.
|
||||
|
||||
Flow outline
|
||||
@ -40,8 +40,8 @@ FlowLogic
|
||||
---------
|
||||
All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``.
|
||||
|
||||
Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete both the existing flows in the
|
||||
template, and replace them with the following:
|
||||
Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete the two existing flows in the
|
||||
template (``Initiator`` and ``Responder``), and replace them with the following:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
|
@ -107,9 +107,15 @@ commands.
|
||||
|
||||
We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing:
|
||||
|
||||
.. code:: bash
|
||||
.. container:: codeset
|
||||
|
||||
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
|
||||
.. code-block:: java
|
||||
|
||||
start IOUFlow arg0: 99, arg1: "O=PartyB,L=New York,C=US"
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
|
||||
|
||||
This single command will cause PartyA and PartyB to automatically agree an IOU. This is one of the great advantages of
|
||||
the flow framework - it allows you to reduce complex negotiation and update processes into a single function call.
|
||||
@ -118,13 +124,7 @@ If the flow worked, it should have recorded a new IOU in the vaults of both Part
|
||||
|
||||
We can check the contents of each node's vault by running:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
run vaultQuery contractStateType: com.template.state.IOUState
|
||||
|
||||
.. code-block:: kotlin
|
||||
.. code-block:: base
|
||||
|
||||
run vaultQuery contractStateType: com.template.IOUState
|
||||
|
||||
@ -174,6 +174,11 @@ parts:
|
||||
* The ``IOUState``, representing IOUs on the ledger
|
||||
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
|
||||
|
||||
After completing this tutorial, your CorDapp should look like this:
|
||||
|
||||
* Java: https://github.com/corda/corda-tut1-solution-java
|
||||
* Kotlin: https://github.com/corda/corda-tut1-solution-kotlin
|
||||
|
||||
Next steps
|
||||
----------
|
||||
There are a number of improvements we could make to this CorDapp:
|
||||
@ -183,4 +188,4 @@ There are a number of improvements we could make to this CorDapp:
|
||||
* We could add an API, to make it easier to interact with the CorDapp
|
||||
|
||||
But for now, the biggest priority is to add an ``IOUContract`` imposing constraints on the evolution of each
|
||||
``IOUState`` over time. This will be the focus of our next tutorial.
|
||||
``IOUState`` over time. This will be the focus of our next tutorial.
|
@ -24,7 +24,6 @@ interface is defined as follows:
|
||||
val participants: List<AbstractParty>
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
The first thing you'll probably notice about this interface declaration is that its not written in Java or another
|
||||
common language. The core Corda platform, including the interface declaration above, is entirely written in Kotlin.
|
||||
|
||||
@ -70,7 +69,7 @@ later is often as simple as adding an additional property to your class definiti
|
||||
|
||||
Defining IOUState
|
||||
-----------------
|
||||
Let's get started by opening ``TemplateState.java`` (for Java) or ``App.kt`` (for Kotlin) and updating
|
||||
Let's get started by opening ``TemplateState.java`` (for Java) or ``StatesAndContracts.kt`` (for Kotlin) and updating
|
||||
``TemplateState`` to define an ``IOUState``:
|
||||
|
||||
.. container:: codeset
|
||||
|
@ -41,34 +41,33 @@ https://docs.corda.net/tutorial-cordapp.html#opening-the-example-cordapp-in-inte
|
||||
|
||||
Template structure
|
||||
------------------
|
||||
The template has a number of files, but we can ignore most of them. To implement our IOU CorDapp in Java, we'll only
|
||||
need to modify two files. For Kotlin, we'll simply be modifying the ``App.kt`` file:
|
||||
The template has a number of files, but we can ignore most of them. We will only be modifying the following files:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
// 1. The state
|
||||
src/main/java/com/template/TemplateState.java
|
||||
cordapp-contracts-states/src/main/java/com/template/TemplateState.java
|
||||
|
||||
// 2. The flow
|
||||
src/main/java/com/template/TemplateFlow.java
|
||||
cordapp/src/main/java/com/template/TemplateFlow.java
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
src/main/kotlin/com/template/App.kt
|
||||
// 1. The state
|
||||
cordapp-contracts-states/src/main/kotlin/com/template/StatesAndContracts.kt
|
||||
|
||||
// 2. The flow
|
||||
cordapp/src/main/kotlin/com/template/App.kt
|
||||
|
||||
Clean up
|
||||
--------
|
||||
To prevent build errors later on, we should delete the following files before we begin:
|
||||
|
||||
* Java:
|
||||
* ``src/main/java/com/template/TemplateClient.java``
|
||||
* ``src/test/java/com/template/FlowTests.java``
|
||||
* Java: ``cordapp/src/main/java/com/template/TemplateClient.java``
|
||||
|
||||
* Kotlin:
|
||||
* ``src/main/kotlin/com/template/TemplateClient.kt``
|
||||
* ``src/test/kotlin/com/template/FlowTests.kt``
|
||||
* Kotlin: ``cordapp/src/main/kotlin/com/template/TemplateClient.kt``
|
||||
|
||||
Progress so far
|
||||
---------------
|
||||
|
@ -2,7 +2,7 @@ Network Map
|
||||
===========
|
||||
|
||||
The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
|
||||
There two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
||||
There are two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
||||
|
||||
1. the REST protocol with the network map service, which also provides a publishing API,
|
||||
|
||||
@ -13,7 +13,7 @@ Protocol Design
|
||||
---------------
|
||||
The node info publishing protocol:
|
||||
|
||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedData<NodeInfo>`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future.
|
||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
|
||||
|
||||
* Serialise the signed data and POST the data to the network map server.
|
||||
|
||||
@ -25,7 +25,18 @@ Node side network map update protocol:
|
||||
|
||||
* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header.
|
||||
|
||||
* The network map service returns a signed ``NetworkMap`` object, containing list of node info hashes and the network parameters hashes.
|
||||
* The network map service returns a signed ``NetworkMap`` object which looks as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
data class NetworkMap {
|
||||
val nodeInfoHashes: List<SecureHash>,
|
||||
val networkParametersHash: SecureHash
|
||||
}
|
||||
|
||||
The object contains list of node info hashes and hash of the network parameters data structure (without the signatures).
|
||||
|
||||
* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``.
|
||||
|
||||
@ -34,13 +45,13 @@ Network Map service REST API:
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Request method | Path | Description |
|
||||
+================+===================================+========================================================================================================================================================+
|
||||
| POST | /api/network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
||||
| POST | /network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /api/network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and NetworkParameters hash. |
|
||||
| GET | /network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and ``NetworkParameters`` hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /api/network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
||||
| GET | /network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. |
|
||||
| GET | /network-map/parameters/{hash} | Retrieve ``NetworkParameters`` object with the same hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
TODO: Access control of the network map will be added in the future.
|
||||
@ -50,8 +61,24 @@ The ``additional-node-infos`` directory
|
||||
---------------------------------------
|
||||
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
|
||||
|
||||
Nodes expect to find a serialized ``SignedData<NodeInfo>`` object, the same object which is sent to network map server.
|
||||
Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
|
||||
|
||||
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
|
||||
|
||||
Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
|
||||
|
||||
|
||||
Network parameters
|
||||
------------------
|
||||
Network parameters are constants that every node participating in the network needs to agree on and use for interop purposes.
|
||||
The structure is distributed as a file containing serialized ``SignedData<NetworkParameters>`` with a signature from
|
||||
a sub-key of the compatibility zone root cert. Network map advertises the hash of currently used network parameters.
|
||||
The ``NetworkParameters`` structure contains:
|
||||
* ``minimumPlatformVersion`` - minimum version of Corda platform that is required for nodes in the network.
|
||||
* ``notaries`` - list of well known and trusted notary identities with information on validation type.
|
||||
* ``maxMessageSize`` - maximum P2P message size sent over the wire in bytes.
|
||||
* ``maxTransactionSize`` - maximum permitted transaction size in bytes.
|
||||
* ``modifiedTime`` - the time the network parameters were created by the CZ operator.
|
||||
* ``epoch`` - version number of the network parameters. Starting from 1, this will always increment on each new set of parameters.
|
||||
|
||||
The set of parameters is still under development and we may find the need to add additional fields.
|
||||
|
@ -1,136 +0,0 @@
|
||||
Access security settings
|
||||
========================
|
||||
|
||||
Access to node functionalities via SSH or RPC is protected by an authentication and authorisation policy.
|
||||
|
||||
The field ``security`` in ``node.conf`` exposes various sub-fields related to authentication/authorisation specifying:
|
||||
|
||||
* The data source providing credentials and permissions for users (e.g.: a remote RDBMS)
|
||||
* An optional password encryption method.
|
||||
* An optional caching of users data from Node side.
|
||||
|
||||
.. warning:: Specifying both ``rpcUsers`` and ``security`` fields in ``node.conf`` is considered an illegal setting and
|
||||
rejected by the node at startup since ``rpcUsers`` is effectively deprecated in favour of ``security.authService``.
|
||||
|
||||
**Example 1:** connect to remote RDBMS for credentials/permissions, with encrypted user passwords and
|
||||
caching on node-side:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "DB",
|
||||
passwordEncryption = "SHIRO_1_CRYPT",
|
||||
connection = {
|
||||
jdbcUrl = "<jdbc connection string>"
|
||||
username = "<db username>"
|
||||
password = "<db user password>"
|
||||
driverClassName = "<JDBC driver>"
|
||||
}
|
||||
}
|
||||
options = {
|
||||
cache = {
|
||||
expiryTimeSecs = 120
|
||||
capacity = 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**Example 2:** list of user credentials and permissions hard-coded in ``node.conf``
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "INMEMORY",
|
||||
users =[
|
||||
{
|
||||
username = "user1"
|
||||
password = "password"
|
||||
permissions = [
|
||||
"StartFlow.net.corda.flows.ExampleFlow1",
|
||||
"StartFlow.net.corda.flows.ExampleFlow2",
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Let us look in more details at the structure of ``security.authService``:
|
||||
|
||||
Authentication/authorisation data
|
||||
---------------------------------
|
||||
|
||||
The ``dataSource`` field defines the data provider supplying credentials and permissions for users. The ``type``
|
||||
subfield identify the type of data provider, currently supported one are:
|
||||
|
||||
* **INMEMORY:** a list of user credentials and permissions hard-coded in configuration in the ``users`` field
|
||||
(see example 2 above)
|
||||
|
||||
* **DB:** An external RDBMS accessed via the JDBC connection described by ``connection``. The current implementation
|
||||
expect the database to store data according to the following schema:
|
||||
|
||||
- Table ``users`` containing columns ``username`` and ``password``.
|
||||
The ``username`` column *must have unique values*.
|
||||
- Table ``user_roles`` containing columns ``username`` and ``role_name`` associating a user to a set of *roles*
|
||||
- Table ``roles_permissions`` containing columns ``role_name`` and ``permission`` associating a role to a set of
|
||||
permission strings
|
||||
|
||||
Note in particular how in the DB case permissions are assigned to _roles_ rather than individual users.
|
||||
Also, there is no prescription on the SQL type of the columns (although in our tests we defined ``username`` and
|
||||
``role_name`` of SQL type ``VARCHAR`` and ``password`` of ``TEXT`` type) and it is allowed to put additional columns
|
||||
besides the one expected by the implementation.
|
||||
|
||||
Password encryption
|
||||
-------------------
|
||||
|
||||
Storing passwords in plain text is discouraged in production systems aiming for high security requirements. We support
|
||||
reading passwords stored using the Apache Shiro fully reversible Modular Crypt Format, specified in the documentation
|
||||
of ``org.apache.shiro.crypto.hash.format.Shiro1CryptFormat``.
|
||||
|
||||
Password are assumed in plain format by default. To specify an encryption it is necessary to use the field:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
passwordEncryption = SHIRO_1_CRYPT
|
||||
|
||||
Hash encrypted password based on the Shiro1CryptFormat can be produced with the `Apache Shiro Hasher tool <https://shiro.apache.org/command-line-hasher.html>`_
|
||||
|
||||
Cache
|
||||
-----
|
||||
|
||||
Adding a cache layer on top of an external provider of users credentials and permissions can significantly benefit
|
||||
performances in some cases, with the disadvantage of introducing a latency in the propagation of changes to the data.
|
||||
|
||||
Caching of users data is disabled by default, it can be enabled by defining the ``options.cache`` field, like seen in
|
||||
the examples above:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
options = {
|
||||
cache = {
|
||||
expiryTimeSecs = 120
|
||||
capacity = 10000
|
||||
}
|
||||
}
|
||||
|
||||
This will enable an in-memory cache with maximum capacity (number of entries) and maximum life time of entries given by
|
||||
respectively the values set by the ``capacity`` and ``expiryTimeSecs`` fields.
|
||||
|
||||
|
||||
|
||||
|
@ -32,7 +32,13 @@ A Corda network has three types of certificate authorities (CAs):
|
||||
* The **node CAs**
|
||||
|
||||
* Each node serves as its own CA in issuing the child certificates that it uses to sign its identity
|
||||
keys, anonymous keys and TLS certificates
|
||||
keys and TLS certificates
|
||||
|
||||
We can visualise the permissioning structure as follows:
|
||||
|
||||
.. image:: resources/certificate_structure.png
|
||||
:scale: 55%
|
||||
:align: center
|
||||
|
||||
Keypair and certificate formats
|
||||
-------------------------------
|
||||
@ -45,6 +51,13 @@ public/private keypairs and certificates. The keypairs and certificates should o
|
||||
|
||||
* The TLS certificates must follow the `TLS v1.2 standard <https://tools.ietf.org/html/rfc5246>`_
|
||||
|
||||
* The root network CA, intermediate network CA and node CA keys, as well as the node TLS
|
||||
keys, must follow one of the following schemes:
|
||||
|
||||
* ECDSA using the NIST P-256 curve (secp256r1)
|
||||
|
||||
* RSA with 3072-bit key size
|
||||
|
||||
Creating the root and intermediate network CAs
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -6,6 +6,5 @@ Release process
|
||||
|
||||
release-notes
|
||||
changelog
|
||||
upgrade-notes
|
||||
codestyle
|
||||
testing
|
BIN
docs/source/resources/certificate_structure.png
Normal file
BIN
docs/source/resources/certificate_structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 108 KiB |
@ -6,11 +6,9 @@ Creating a Corda network
|
||||
A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in
|
||||
order to create and validate transactions.
|
||||
|
||||
There are four broader categories of functionality one such node may have. These pieces of functionality are provided
|
||||
There are three broader categories of functionality one such node may have. These pieces of functionality are provided
|
||||
as services, and one node may run several of them.
|
||||
|
||||
* Network map: The node running the network map provides a way to resolve identities to physical node addresses and
|
||||
associated public keys
|
||||
* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a
|
||||
double-spend or not
|
||||
* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of
|
||||
@ -46,12 +44,38 @@ The most important fields regarding network configuration are:
|
||||
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
|
||||
* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine.
|
||||
|
||||
Bootstrapping the network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The nodes see each other using the network map. This is a collection of statically signed node-info files, one for each
|
||||
node in the network. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
|
||||
|
||||
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
|
||||
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
|
||||
in its local network map cache. The node generates its own node-info file on startup.
|
||||
|
||||
In addition to the network map, all the nodes on a network must use the same set of network parameters. These are a set
|
||||
of constants which guarantee interoperability between nodes. The HTTP network map distributes the network parameters
|
||||
which the node downloads automatically. In the absence of this the network parameters must be generated locally. This can
|
||||
be done with the network bootstrapper. This a tool that scans all the node configurations from a common directory to
|
||||
generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file
|
||||
to every other node.
|
||||
|
||||
The bootstrapper tool can be built with the command:
|
||||
|
||||
``gradlew buildBootstrapperJar``
|
||||
|
||||
The resulting jar can be found in ``tools/bootstrapper/build/libs/``.
|
||||
|
||||
To use it, run the following command, specifying the root directory which hosts all the node directories as the argument:
|
||||
|
||||
``java -jar network-bootstrapper.jar <nodes-root-dir>``
|
||||
|
||||
Starting the nodes
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You may now start the nodes in any order. Note that the node is not fully started until it has successfully registered with the network map!
|
||||
|
||||
You should see a banner, some log lines and eventually ``Node started up and registered``, indicating that the node is fully started.
|
||||
You may now start the nodes in any order. You should see a banner, some log lines and eventually ``Node started up and registered``,
|
||||
indicating that the node is fully started.
|
||||
|
||||
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.
|
||||
|
||||
@ -66,7 +90,6 @@ details/diagnosing problems check the logs.
|
||||
Logging is standard log4j2_ and may be configured accordingly. Logs
|
||||
are by default redirected to files in ``NODE_DIRECTORY/logs/``.
|
||||
|
||||
|
||||
Connecting to the nodes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -77,7 +77,7 @@ We can picture this transaction as follows:
|
||||
Defining IOUContract
|
||||
--------------------
|
||||
Let's write a contract that enforces these constraints. We'll do this by modifying either ``TemplateContract.java`` or
|
||||
``App.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
||||
``StatesAndContracts.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
|
@ -17,7 +17,7 @@ We'll do this by modifying the flow we wrote in the previous tutorial.
|
||||
|
||||
Verifying the transaction
|
||||
-------------------------
|
||||
In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
|
||||
In ``IOUFlow.java``/``App.kt``, change the imports block to the following:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -31,7 +31,8 @@ In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
|
||||
:start-after: DOCSTART 01
|
||||
:end-before: DOCEND 01
|
||||
|
||||
And update ``IOUFlow.call`` by changing the code following the creation of the ``TransactionBuilder`` as follows:
|
||||
And update ``IOUFlow.call`` by changing the code following the retrieval of the notary's identity from the network as
|
||||
follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -138,6 +139,11 @@ Our CorDapp now imposes restrictions on the issuance of IOUs. Most importantly,
|
||||
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.
|
||||
|
||||
After completing this tutorial, your CorDapp should look like this:
|
||||
|
||||
* Java: https://github.com/corda/corda-tut2-solution-java
|
||||
* Kotlin: https://github.com/corda/corda-tut2-solution-kotlin
|
||||
|
||||
You should now be ready to develop your own CorDapps. You can also find a list of sample CorDapps
|
||||
`here <https://www.corda.net/samples/>`_. As you write CorDapps, you'll also want to learn more about the
|
||||
:doc:`Corda API <corda-api>`.
|
||||
|
@ -1,11 +1,23 @@
|
||||
Tutorials
|
||||
=========
|
||||
|
||||
This section is split into two parts.
|
||||
|
||||
The Hello, World tutorials should be followed in sequence and show how to extend the Java or Kotlin CorDapp Template
|
||||
into a full CorDapp.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
hello-world-introduction
|
||||
tut-two-party-introduction
|
||||
|
||||
The remaining tutorials cover individual platform features in isolation. They don't depend on the code from the Hello,
|
||||
World tutorials, and can be read in any order.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
tutorial-contract
|
||||
tutorial-test-dsl
|
||||
contract-upgrade
|
||||
|
@ -1,5 +1,5 @@
|
||||
Upgrade notes
|
||||
=============
|
||||
Upgrading a CorDapp to a new version
|
||||
====================================
|
||||
|
||||
These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our
|
||||
first public Beta (:ref:`Milestone 12 <changelog_m12>`), to :ref:`V1.0 <changelog_v1>`.
|
||||
|
@ -32,8 +32,8 @@ Flow versioning
|
||||
---------------
|
||||
|
||||
In addition to the evolution of the platform, flows that run on top of the platform can also evolve. It may be that the
|
||||
flow protocol between an initiating flow and it's intiated flow changes from one CorDapp release to the next in such as
|
||||
way to be backwards incompatible with existing flows. For example, if a sequence of sends and receives needs to change
|
||||
flow protocol between an initiating flow and its initiated flow changes from one CorDapp release to the next in such a
|
||||
way to be backward incompatible with existing flows. For example, if a sequence of sends and receives needs to change
|
||||
or if the semantics of a particular receive changes.
|
||||
|
||||
The ``InitiatingFlow`` annotation (see :doc:`flow-state-machine` for more information on the flow annotations) has a ``version``
|
||||
|
Reference in New Issue
Block a user