Merge remote-tracking branch 'open/master' into colljos-os-merge-rc01

This commit is contained in:
josecoll
2017-12-18 10:24:38 +00:00
215 changed files with 4624 additions and 1717 deletions

View File

@ -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``).

View File

@ -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)

View File

@ -6,6 +6,7 @@ CorDapps
cordapp-overview
writing-a-cordapp
upgrade-notes
cordapp-build-systems
building-against-master
corda-api

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -10,7 +10,6 @@ Corda nodes
corda-configuration-file
clientrpc
shell
node-auth-config
node-database
node-administration
out-of-process-verification

View File

@ -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')

View File

@ -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;

View File

@ -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> {

View File

@ -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;

View File

@ -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();

View File

@ -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 {
}

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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
---------------

View File

@ -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.

View File

@ -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.

View File

@ -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
----------------------------------------------

View File

@ -6,6 +6,5 @@ Release process
release-notes
changelog
upgrade-notes
codestyle
testing

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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

View File

@ -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>`.

View File

@ -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

View File

@ -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>`.

View File

@ -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``