mirror of
https://github.com/corda/corda.git
synced 2025-03-14 00:06:45 +00:00
Merge remote-tracking branch 'open/master' into anthony-os-merge-20180710
# Conflicts: # .idea/compiler.xml # docs/source/changelog.rst # docs/source/network-bootstrapper.rst # node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt # node/src/main/kotlin/net/corda/node/services/identity/IdentityServiceUtil.kt # node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt
This commit is contained in:
commit
8d7aa5f590
@ -0,0 +1,20 @@
|
||||
package net.corda.core.internal.errors
|
||||
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
|
||||
class AddressBindingException(val addresses: Set<NetworkHostAndPort>) : CordaRuntimeException(message(addresses)) {
|
||||
|
||||
constructor(address: NetworkHostAndPort) : this(setOf(address))
|
||||
|
||||
private companion object {
|
||||
private fun message(addresses: Set<NetworkHostAndPort>): String {
|
||||
require(addresses.isNotEmpty())
|
||||
return if (addresses.size > 1) {
|
||||
"Failed to bind on an address in ${addresses.joinToString(", ", "[", "]")}."
|
||||
} else {
|
||||
"Failed to bind on address ${addresses.single()}."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ package net.corda.core.node.services
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.*
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
@ -47,10 +48,17 @@ interface IdentityService {
|
||||
* Asserts that an anonymous party maps to the given full party, by looking up the certificate chain associated with
|
||||
* the anonymous party and resolving it back to the given full party.
|
||||
*
|
||||
* @throws IllegalStateException if the anonymous party is not owned by the full party.
|
||||
* @throws UnknownAnonymousPartyException if the anonymous party is not owned by the full party.
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun assertOwnership(party: Party, anonymousParty: AnonymousParty)
|
||||
@Throws(UnknownAnonymousPartyException::class)
|
||||
fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||
val anonymousIdentity = certificateFromKey(anonymousParty.owningKey)
|
||||
?: throw UnknownAnonymousPartyException("Unknown $anonymousParty")
|
||||
val issuingCert = anonymousIdentity.certPath.certificates[1]
|
||||
require(issuingCert.publicKey == party.owningKey) {
|
||||
"Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all identities known to the service. This is expensive, and [partyFromKey] or [partyFromX500Name] should be
|
||||
@ -73,7 +81,7 @@ interface IdentityService {
|
||||
* @param key The owning [PublicKey] of the [Party].
|
||||
* @return Returns a [Party] with a matching owningKey if known, else returns null.
|
||||
*/
|
||||
fun partyFromKey(key: PublicKey): Party?
|
||||
fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party
|
||||
|
||||
/**
|
||||
* Resolves a party name to the well known identity [Party] instance for this name. Where possible well known identity
|
||||
@ -92,7 +100,21 @@ interface IdentityService {
|
||||
* @param party identity to determine well known identity for.
|
||||
* @return well known identity, if found.
|
||||
*/
|
||||
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party?
|
||||
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
||||
// The original version of this would return the party as-is if it was a Party (rather than AnonymousParty),
|
||||
// however that means that we don't verify that we know who owns the key. As such as now enforce turning the key
|
||||
// into a party, and from there figure out the well known party.
|
||||
val candidate = partyFromKey(party.owningKey)
|
||||
// TODO: This should be done via the network map cache, which is the authoritative source of well known identities
|
||||
return if (candidate != null) {
|
||||
require(party.nameOrNull() == null || party.nameOrNull() == candidate.name) {
|
||||
"Candidate party $candidate does not match expected $party"
|
||||
}
|
||||
wellKnownPartyFromX500Name(candidate.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a (optionally) confidential identity to the corresponding well known identity [Party].
|
||||
@ -103,7 +125,7 @@ interface IdentityService {
|
||||
* @param partyRef identity (and reference, which is unused) to determine well known identity for.
|
||||
* @return the well known identity, or null if unknown.
|
||||
*/
|
||||
fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference) = wellKnownPartyFromAnonymous(partyRef.party)
|
||||
fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference): Party? = wellKnownPartyFromAnonymous(partyRef.party)
|
||||
|
||||
/**
|
||||
* Resolve the well known identity of a party. Throws an exception if the party cannot be identified.
|
||||
@ -112,7 +134,10 @@ interface IdentityService {
|
||||
* @return the well known identity.
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
fun requireWellKnownPartyFromAnonymous(party: AbstractParty): Party
|
||||
fun requireWellKnownPartyFromAnonymous(party: AbstractParty): Party {
|
||||
return wellKnownPartyFromAnonymous(party)
|
||||
?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may
|
||||
|
@ -12,6 +12,9 @@ Unreleased
|
||||
and within that file the ``bridgeMode`` propety has been modified to ``firewallMode`` for overall consistency.
|
||||
This will be a breaking change for early adopters and their deployments, but hopefully will be more future proof.
|
||||
|
||||
* Docs for IdentityService. assertOwnership updated to correctly state that an UnknownAnonymousPartyException is thrown
|
||||
rather than IllegalStateException.
|
||||
|
||||
* The Corda JPA entities no longer implement java.io.Serializable, as this was causing persistence errors in obscure cases.
|
||||
Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java serializable please contact us.
|
||||
|
||||
|
@ -153,7 +153,7 @@ absolute path to the node's base directory.
|
||||
:validating: Boolean to determine whether the notary is a validating or non-validating one.
|
||||
|
||||
:serviceLegalName: If the node is part of a distributed cluster, specify the legal name of the cluster. At runtime, Corda
|
||||
checks whether this name matches the name of the certificate of the notary cluster.
|
||||
checks whether this name matches the name of the certificate of the notary cluster.
|
||||
|
||||
:raft: If part of a distributed Raft cluster specify this config object, with the following settings:
|
||||
|
||||
|
@ -24,18 +24,13 @@ You can find out more about network maps and network parameters from :doc:`netwo
|
||||
Bootstrapping a test network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The bootstrapper is distributed as part of |release| in the form of runnable JAR file "|jar_name|".
|
||||
|
||||
.. |jar_name| replace:: corda-tools-network-bootstrapper-|version|.jar
|
||||
The bootstrapper can be downloaded from https://downloads.corda.net/network-bootstrapper-VERSION.jar, where ``VERSION``
|
||||
is the Corda version.
|
||||
|
||||
Create a directory containing a node config file, ending in "_node.conf", for each node you want to create. Then run the
|
||||
following command:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
> java -jar |jar_name| --dir <nodes-root-dir>
|
||||
|
||||
..
|
||||
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
|
||||
|
||||
For example running the command on a directory containing these files:
|
||||
|
||||
@ -50,9 +45,7 @@ will generate directories containing three nodes: ``notary``, ``partya`` and ``p
|
||||
that comes with the bootstrapper. If a different version of Corda is required then simply place that ``corda.jar`` file
|
||||
alongside the configuration files in the directory.
|
||||
|
||||
The directory can also contain CorDapp JARs which will be copied to each node's ``cordapps`` directory.
|
||||
|
||||
You can also have the node directories containing their ``node.conf`` files already laid out. The previous example would be:
|
||||
You can also have the node directories containing their "node.conf" files already laid out. The previous example would be:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
@ -66,33 +59,34 @@ You can also have the node directories containing their ``node.conf`` files alre
|
||||
|
||||
Similarly, each node directory may contain its own ``corda.jar``, which the bootstrapper will use instead.
|
||||
|
||||
Synchronisation
|
||||
~~~~~~~~~~~~~~~
|
||||
Providing CorDapps to the Network Bootstrapper
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This tool only bootstraps a network. It cannot dynamically update if a new node needs to join the network or if an existing
|
||||
one has changed something in their node-info, e.g. their P2P address. For this the new node-info file will need to be placed
|
||||
in the other nodes' ``additional-node-infos`` directory. A simple way to do this is to use `rsync <https://en.wikipedia.org/wiki/Rsync>`_.
|
||||
However, if it's known beforehand the set of nodes that will eventually form part of the network then all the node directories
|
||||
can be pre-generated in the bootstrap and only started when needed.
|
||||
If you would like the Network Bootstrapper to include your CorDapps in each generated node, just place them in the directory
|
||||
alongside the config files. For example, if your directory has this structure:
|
||||
|
||||
Running the bootstrapper again on the same network will allow a new node to be added or an existing one to have its updated
|
||||
node-info re-distributed. However, this comes at the expense of having to temporarily collect the node directories back
|
||||
together again under a common parent directory.
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary_node.conf // The notary's node.conf file
|
||||
├── partya_node.conf // Party A's node.conf file
|
||||
├── partyb_node.conf // Party B's node.conf file
|
||||
├── cordapp-a.jar // A cordapp to be installed on all nodes
|
||||
└── cordapp-b.jar // Another cordapp to be installed on all nodes
|
||||
|
||||
The ``cordapp-a.jar`` and ``cordapp-b.jar`` will be installed in each node directory, and any contracts within them will be
|
||||
added to the Contract Whitelist (see below).
|
||||
|
||||
Whitelisting contracts
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
----------------------
|
||||
|
||||
The CorDapp JARs are also automatically used to create the *Zone whitelist* (see :doc:`api-contract-constraints`) for
|
||||
the network.
|
||||
Any CorDapps provided when bootstrapping a network will be scanned for contracts which will be used to create the
|
||||
*Zone whitelist* (see :doc:`api-contract-constraints`) for the network.
|
||||
|
||||
.. note:: If you only wish to whitelist the CorDapps but not copy them to each node then run with the ``--no-copy`` flag.
|
||||
|
||||
The CorDapp JARs will be hashed and scanned for ``Contract`` classes. These contract class implementations will become part
|
||||
of the whitelisted contracts in the network parameters (see ``NetworkParameters.whitelistedContractImplementations`` :doc:`network-map`).
|
||||
If the network already has a set of network parameters defined (i.e. the node directories all contain the same network-parameters
|
||||
file) then the new set of contracts will be appended to the current whitelist.
|
||||
|
||||
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
|
||||
|
||||
By default the bootstrapper will whitelist all the contracts found in all the CorDapp JARs. To prevent certain
|
||||
contracts from being whitelisted, add their fully qualified class name in the ``exclude_whitelist.txt``. These will instead
|
||||
@ -104,3 +98,151 @@ For example:
|
||||
|
||||
net.corda.finance.contracts.asset.Cash
|
||||
net.corda.finance.contracts.asset.CommercialPaper
|
||||
|
||||
Modifying a bootstrapped network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The network bootstrapper is provided as a development tool for setting up Corda networks for development and testing.
|
||||
There is some limited functionality which can be used to make changes to a network, but for anything more complicated consider
|
||||
using a :doc:`network-map` server.
|
||||
|
||||
When running the Network Bootstrapper, each ``node-info`` file needs to be gathered together in one directory. If
|
||||
the nodes are being run on different machines you need to do the following:
|
||||
|
||||
* Copy the node directories from each machine into one directory, on one machine
|
||||
* Depending on the modification being made (see below for more information), add any new files required to the root directory
|
||||
* Run the Network Bootstrapper from the root directory
|
||||
* Copy each individual node's directory back to the original machine
|
||||
|
||||
The network bootstrapper cannot dynamically update the network if an existing node has changed something in their node-info,
|
||||
e.g. their P2P address. For this the new node-info file will need to be placed in the other nodes' ``additional-node-infos`` directory.
|
||||
If the nodes are located on different machines, then a utility such as `rsync <https://en.wikipedia.org/wiki/Rsync>`_ can be used
|
||||
so that the nodes can share node-infos.
|
||||
|
||||
Adding a new node to the network
|
||||
--------------------------------
|
||||
|
||||
Running the bootstrapper again on the same network will allow a new node to be added and its
|
||||
node-info distributed to the existing nodes.
|
||||
|
||||
As an example, if we have an existing bootstrapped network, with a Notary and PartyA and we want to add a PartyB, we
|
||||
can use the network bootstrapper on the following network structure:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary // existing node directories
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ ├── node-info-notary
|
||||
│ └── additional-node-infos
|
||||
│ ├── node-info-notary
|
||||
│ └── node-info-partya
|
||||
├── partya
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ ├── node-info-partya
|
||||
│ └── additional-node-infos
|
||||
│ ├── node-info-notary
|
||||
│ └── node-info-partya
|
||||
└── partyb_node.conf // the node.conf for the node to be added
|
||||
|
||||
Then run the network bootstrapper again from the root dir:
|
||||
|
||||
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
|
||||
|
||||
Which will give the following:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary // the contents of the existing nodes (keys, db's etc...) are unchanged
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ ├── node-info-notary
|
||||
│ └── additional-node-infos
|
||||
│ ├── node-info-notary
|
||||
│ ├── node-info-partya
|
||||
│ └── node-info-partyb
|
||||
├── partya
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ ├── node-info-partya
|
||||
│ └── additional-node-infos
|
||||
│ ├── node-info-notary
|
||||
│ ├── node-info-partya
|
||||
│ └── node-info-partyb
|
||||
└── partyb // a new node directory is created for PartyB
|
||||
├── node.conf
|
||||
├── network-parameters
|
||||
├── node-info-partyb
|
||||
└── additional-node-infos
|
||||
├── node-info-notary
|
||||
├── node-info-partya
|
||||
└── node-info-partyb
|
||||
|
||||
The bootstrapper will generate a directory and the ``node-info`` file for PartyB, and will also make sure a copy of each
|
||||
nodes' ``node-info`` file is in the ``additional-node-info`` directory of every node. Any other files in the existing nodes,
|
||||
such a generated keys, will be unaffected.
|
||||
|
||||
.. note:: The bootstrapper is provided for test deployments and can only generate information for nodes collected on
|
||||
the same machine. If a network needs to be updated using the bootstrapper once deployed, the nodes will need
|
||||
collecting back together.
|
||||
|
||||
Updating the contract whitelist for bootstrapped networks
|
||||
---------------------------------------------------------
|
||||
|
||||
If the network already has a set of network parameters defined (i.e. the node directories all contain the same network-parameters
|
||||
file) then the bootstrapper can be used to append contracts from new CorDapps to the current whitelist.
|
||||
For example, with the following pre-generated network:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ └── cordapps
|
||||
│ └── cordapp-a.jar
|
||||
├── partya
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ └── cordapps
|
||||
│ └── cordapp-a.jar
|
||||
├── partyb
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters
|
||||
│ └── cordapps
|
||||
│ └── cordapp-a.jar
|
||||
└── cordapp-b.jar // The new cordapp to add to the existing nodes
|
||||
|
||||
Then run the network bootstrapper again from the root dir:
|
||||
|
||||
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
|
||||
|
||||
To give the following:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters // The contracts from cordapp-b are appended to the whitelist in network-parameters
|
||||
│ └── cordapps
|
||||
│ ├── cordapp-a.jar
|
||||
│ └── cordapp-b.jar // The updated cordapp is placed in the nodes cordapp directory
|
||||
├── partya
|
||||
│ ├── node.conf
|
||||
│ ├── network-parameters // The contracts from cordapp-b are appended to the whitelist in network-parameters
|
||||
│ └── cordapps
|
||||
│ ├── cordapp-a.jar
|
||||
│ └── cordapp-b.jar // The updated cordapp is placed in the nodes cordapp directory
|
||||
└── partyb
|
||||
├── node.conf
|
||||
├── network-parameters // The contracts from cordapp-b are appended to the whitelist in network-parameters
|
||||
└── cordapps
|
||||
├── cordapp-a.jar
|
||||
└── cordapp-b.jar // The updated cordapp is placed in the nodes cordapp directory
|
||||
|
||||
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
|
||||
|
||||
|
@ -10,7 +10,107 @@ Quickstart
|
||||
getting-set-up.rst
|
||||
tutorial-cordapp.rst
|
||||
|
||||
* :doc:`Set up your machine for CorDapp development <getting-set-up>`
|
||||
* :doc:`Run the Example CorDapp <tutorial-cordapp>`
|
||||
* `View CorDapps in Corda Explore <http://explore.corda.zone/>`_
|
||||
* `Download sample CorDapps <https://www.corda.net/samples/>`_
|
||||
Welcome to the Corda Quickstart Guide. Follow the links below to help get going quickly with Corda.
|
||||
|
||||
I want to:
|
||||
|
||||
* :ref:`Learn <quickstart-learn>` about Corda for the first time
|
||||
* :ref:`Develop <quickstart-develop>` a CorDapp
|
||||
* :ref:`Run <quickstart-run>` and test a CorDapp on a local Corda network
|
||||
* :ref:`Add <quickstart-add>` a node to an existing test Corda network
|
||||
* :ref:`Add <quickstart-production>` a node to an existing production network
|
||||
|
||||
.. _quickstart-learn:
|
||||
|
||||
Learn about Corda for the first time
|
||||
------------------------------------
|
||||
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| Useful links | Description |
|
||||
+============================================+=========================================================================================================+
|
||||
| :doc:`key-concepts` | The key concepts and features of the Corda Platform |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`getting-set-up` | Set up your machine for running and developing CorDapps |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`tutorial-cordapp` | A guide to running a simple CorDapp |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
.. _quickstart-develop:
|
||||
|
||||
Develop a CorDapp
|
||||
-----------------
|
||||
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| Useful links | Description |
|
||||
+============================================+=========================================================================================================+
|
||||
| :doc:`hello-world-introduction` | A coding walk-through of a basic CorDapp |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`cordapp-overview` | An introduction to CordApps |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`writing-a-cordapp` | How to structure a CorDapp project |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`cordapp-build-systems` | How to build a CorDapp |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`corda-api` | A guide to the CorDapp API |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
.. _quickstart-run:
|
||||
|
||||
Run and test a CorDapp on local Corda network
|
||||
---------------------------------------------
|
||||
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| Useful links | Description |
|
||||
+============================================+=========================================================================================================+
|
||||
| :doc:`generating-a-node` | Guidance on creating Corda nodes for development and testing locally and on Docker |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`node-structure` | The Corda node folder structure and how to name your node |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`corda-configuration-file` | A detailed description of the Corda node configuration file with examples |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`running-a-node` | Guidance on running Corda nodes locally and on Docker |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`setting-up-a-corda-network` | Considerations for setting up a Corda network |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`shell` | Guidance on using an embedded command line to control and monitor a node |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`node-administration` | How to monitor a Corda node using an RPC interface |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`node-explorer` | A GUI-based tool to view transactional data and transactional history for a node |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
.. _quickstart-add:
|
||||
|
||||
Add a node to an existing test Corda network
|
||||
--------------------------------------------
|
||||
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| Useful links | Description |
|
||||
+============================================+=========================================================================================================+
|
||||
| :doc:`node-structure` | The Corda node folder structure and how to name your node |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`corda-configuration-file` | A detailed description of the Corda node configuration file with examples |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`deploying-a-node` | A step-by-step guide on deploying a Corda node to your own server |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`azure-vm` | A step-by-step guide on creating a Corda Network on Azure |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`aws-vm` | A step-by-step guide on creating a Corda Network on AWS |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`shell` | Guidance on using an embedded command line to control and monitor a node |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`node-administration` | How to monitor a Corda node using an RPC interface |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`node-explorer` | A GUI-based tool to view transactional data and transactional history for a node |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| :doc:`blob-inspector` | A troubleshooting tool allowing you to read the contents of a binary blob file |
|
||||
+--------------------------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
.. _quickstart-production:
|
||||
|
||||
Add a node to an existing production network
|
||||
--------------------------------------------
|
||||
|
||||
+---------------------------------------------------------------------------------------------------------+
|
||||
| Contact R3 Solutions Engineering at support@r3.com |
|
||||
+---------------------------------------------------------------------------------------------------------+
|
||||
|
@ -0,0 +1,48 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.driver
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
|
||||
class AddressBindingFailureTests {
|
||||
|
||||
companion object {
|
||||
private val portAllocation = PortAllocation.Incremental(20_000)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `p2p address`() = assertBindExceptionForOverrides { address -> mapOf("p2pAddress" to address.toString()) }
|
||||
|
||||
@Test
|
||||
fun `rpc address`() = assertBindExceptionForOverrides { address -> mapOf("rpcSettings" to mapOf("address" to address.toString())) }
|
||||
|
||||
@Test
|
||||
fun `rpc admin address`() = assertBindExceptionForOverrides { address -> mapOf("rpcSettings" to mapOf("adminAddress" to address.toString())) }
|
||||
|
||||
@Test
|
||||
fun `H2 address`() = assertBindExceptionForOverrides { address -> mapOf("h2Settings" to mapOf("address" to address.toString())) }
|
||||
|
||||
private fun assertBindExceptionForOverrides(overrides: (NetworkHostAndPort) -> Map<String, Any?>) {
|
||||
|
||||
ServerSocket(0).use { socket ->
|
||||
|
||||
val address = InetSocketAddress(socket.localPort).toNetworkHostAndPort()
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), inMemoryDB = false, portAllocation = portAllocation)) {
|
||||
|
||||
assertThatThrownBy { startNode(customOverrides = overrides(address)).getOrThrow() }.isInstanceOfSatisfying(AddressBindingException::class.java) { exception ->
|
||||
assertThat(exception.addresses).contains(address).withFailMessage("Expected addresses to contain $address but was ${exception.addresses}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun InetSocketAddress.toNetworkHostAndPort() = NetworkHostAndPort(hostName, port)
|
||||
}
|
@ -73,7 +73,7 @@ class BootTests : IntegrationTest() {
|
||||
|
||||
@Test
|
||||
fun `double node start doesn't write into log file`() {
|
||||
driver {
|
||||
driver(DriverParameters(notarySpecs = emptyList())) {
|
||||
val alice = startNode(providedName = ALICE_NAME).get()
|
||||
val logFolder = alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
|
||||
val logFile = logFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
|
||||
|
@ -21,7 +21,16 @@ import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.flows.ContractUpgradeFlow
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.NotaryChangeFlow
|
||||
import net.corda.core.flows.NotaryFlow
|
||||
import net.corda.core.flows.StartableByService
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
@ -33,14 +42,31 @@ import net.corda.core.internal.concurrent.map
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.notary.NotaryService
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.FlowHandleImpl
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.messaging.FlowProgressHandleImpl
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.node.AppServiceHub
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.node.services.TransactionVerifierService
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.node.CordaClock
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.CheckpointVerifier.verifyCheckpointsCompatible
|
||||
@ -56,9 +82,26 @@ import net.corda.node.internal.security.RPCSecurityManager
|
||||
import net.corda.node.services.ContractUpgradeHandler
|
||||
import net.corda.node.services.FinalityHandler
|
||||
import net.corda.node.services.NotaryChangeHandler
|
||||
import net.corda.node.services.api.*
|
||||
import net.corda.node.services.config.*
|
||||
import net.corda.node.services.api.CheckpointStorage
|
||||
import net.corda.node.services.api.DummyAuditService
|
||||
import net.corda.node.services.api.FlowStarter
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.node.services.api.MonitoringService
|
||||
import net.corda.node.services.api.NetworkMapCacheBaseInternal
|
||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.node.services.api.NodePropertiesStore
|
||||
import net.corda.node.services.api.SchedulerService
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.node.services.api.VaultServiceInternal
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||
import net.corda.node.services.config.shell.toShellConfig
|
||||
import net.corda.node.services.config.shouldInitCrashShell
|
||||
import net.corda.node.services.events.NodeSchedulerService
|
||||
import net.corda.node.services.events.ScheduledActivityObserver
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
@ -78,14 +121,14 @@ import net.corda.node.services.persistence.DBTransactionStorage
|
||||
import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.node.services.persistence.NodePropertiesPersistentStore
|
||||
import net.corda.node.services.persistence.RunOnceService
|
||||
import net.corda.node.services.network.*
|
||||
import net.corda.node.services.persistence.*
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.statemachine.ExternalEvent
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import net.corda.node.services.statemachine.SingleThreadedStateMachineManager
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.node.services.statemachine.FlowMonitor
|
||||
import net.corda.node.services.statemachine.StateMachineManagerInternal
|
||||
import net.corda.node.services.statemachine.appName
|
||||
import net.corda.node.services.statemachine.flowVersionAndInitiatingClass
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
@ -97,8 +140,6 @@ import net.corda.node.services.transactions.RaftUniquenessProvider
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.node.services.statemachine.*
|
||||
import net.corda.node.services.transactions.*
|
||||
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
@ -140,7 +181,6 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
@ -431,7 +471,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
|
||||
open fun startShell() {
|
||||
if (configuration.shouldInitCrashShell()) {
|
||||
InteractiveShell.startShellInternal(configuration.toShellConfig(), cordappLoader.appClassLoader)
|
||||
val shellConfiguration = configuration.toShellConfig()
|
||||
shellConfiguration.sshHostKeyDirectory?.let {
|
||||
log.info("Binding Shell SSHD server on port $it.")
|
||||
}
|
||||
InteractiveShell.startShellInternal(shellConfiguration, cordappLoader.appClassLoader)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.artemis.ArtemisBroker
|
||||
import net.corda.node.internal.artemis.BrokerAddresses
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
||||
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
@ -46,7 +47,6 @@ import net.corda.node.services.api.NodePropertiesStore
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.SecurityConfiguration
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.node.services.config.shouldInitCrashShell
|
||||
import net.corda.node.services.config.shouldStartLocalShell
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
@ -71,10 +71,12 @@ import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||
import net.corda.serialization.internal.AMQP_RPC_SERVER_CONTEXT
|
||||
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import org.h2.jdbc.JdbcSQLException
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Scheduler
|
||||
import rx.schedulers.Schedulers
|
||||
import java.net.BindException
|
||||
import java.nio.file.Path
|
||||
import java.security.PublicKey
|
||||
import java.time.Clock
|
||||
@ -354,7 +356,7 @@ open class Node(configuration: NodeConfiguration,
|
||||
if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) {
|
||||
val effectiveH2Settings = configuration.effectiveH2Settings
|
||||
|
||||
if(effectiveH2Settings != null && effectiveH2Settings.address != null) {
|
||||
if (effectiveH2Settings?.address != null) {
|
||||
val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';')
|
||||
val server = org.h2.tools.Server.createTcpServer(
|
||||
"-tcpPort", effectiveH2Settings.address.port.toString(),
|
||||
@ -364,7 +366,15 @@ open class Node(configuration: NodeConfiguration,
|
||||
// override interface that createTcpServer listens on (which is always 0.0.0.0)
|
||||
System.setProperty("h2.bindAddress", effectiveH2Settings.address.host)
|
||||
runOnStop += server::stop
|
||||
val url = server.start().url
|
||||
val url = try {
|
||||
server.start().url
|
||||
} catch (e: JdbcSQLException) {
|
||||
if (e.cause is BindException) {
|
||||
throw AddressBindingException(effectiveH2Settings.address)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node")
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,14 @@ import com.typesafe.config.ConfigRenderOptions
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.internal.concurrent.thenMatch
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.location
|
||||
import net.corda.core.internal.randomOrNull
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.*
|
||||
@ -166,6 +172,9 @@ open class NodeStartup(val args: Array<String>) {
|
||||
} catch (e: CheckpointIncompatibleException) {
|
||||
logger.error(e.message)
|
||||
return false
|
||||
} catch (e: AddressBindingException) {
|
||||
logger.error(e.message)
|
||||
return false
|
||||
} catch (e: NetworkParametersReader.Error) {
|
||||
logger.error(e.message)
|
||||
return false
|
||||
|
@ -10,9 +10,11 @@
|
||||
|
||||
package net.corda.node.internal.artemis
|
||||
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.internal.LifecycleSupport
|
||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||
import java.net.BindException
|
||||
|
||||
interface ArtemisBroker : LifecycleSupport, AutoCloseable {
|
||||
val addresses: BrokerAddresses
|
||||
@ -24,4 +26,6 @@ interface ArtemisBroker : LifecycleSupport, AutoCloseable {
|
||||
|
||||
data class BrokerAddresses(val primary: NetworkHostAndPort, private val adminArg: NetworkHostAndPort?) {
|
||||
val admin = adminArg ?: primary
|
||||
}
|
||||
}
|
||||
|
||||
fun java.io.IOException.isBindingError() = this is BindException || this is Errors.NativeIoException && message?.contains("Address already in use") == true
|
@ -10,12 +10,79 @@
|
||||
|
||||
package net.corda.node.services.api
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.CertificateExpiredException
|
||||
import java.security.cert.CertificateNotYetValidException
|
||||
import java.security.cert.TrustAnchor
|
||||
|
||||
interface IdentityServiceInternal : IdentityService {
|
||||
|
||||
private companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
/** This method exists so it can be mocked with doNothing, rather than having to make up a possibly invalid return value. */
|
||||
fun justVerifyAndRegisterIdentity(identity: PartyAndCertificate) {
|
||||
verifyAndRegisterIdentity(identity)
|
||||
}
|
||||
|
||||
fun partiesFromName(query: String, exactMatch: Boolean, x500name: CordaX500Name, results: LinkedHashSet<Party>, party: Party) {
|
||||
val components = listOfNotNull(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country)
|
||||
components.forEach { component ->
|
||||
if (exactMatch && component == query) {
|
||||
results += party
|
||||
} else if (!exactMatch) {
|
||||
// We can imagine this being a query over a lucene index in future.
|
||||
//
|
||||
// Kostas says: We can easily use the Jaro-Winkler distance metric as it is best suited for short
|
||||
// strings such as entity/company names, and to detect small typos. We can also apply it for city
|
||||
// or any keyword related search in lists of records (not raw text - for raw text we need indexing)
|
||||
// and we can return results in hierarchical order (based on normalised String similarity 0.0-1.0).
|
||||
if (component.contains(query, ignoreCase = true))
|
||||
results += party
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an identity is valid.
|
||||
*
|
||||
* @param trustAnchor The trust anchor that will verify the identity's validity
|
||||
* @param identity The identity to verify
|
||||
*/
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
try {
|
||||
identity.verify(trustAnchor)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.")
|
||||
log.warn("Certificate path :")
|
||||
identityCertChain.reversed().forEachIndexed { index, certificate ->
|
||||
val space = (0 until index).joinToString("") { " " }
|
||||
log.warn("$space${certificate.subjectX500Principal}")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
// Ensure we record the first identity of the same name, first
|
||||
val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false }
|
||||
if (wellKnownCert != identity.certificate) {
|
||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||
verifyAndRegisterIdentity(trustAnchor, PartyAndCertificate(firstPath))
|
||||
}
|
||||
return registerIdentity(identity)
|
||||
}
|
||||
|
||||
fun registerIdentity(identity: PartyAndCertificate): PartyAndCertificate?
|
||||
}
|
||||
|
@ -10,16 +10,13 @@
|
||||
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
@ -32,10 +29,9 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
*
|
||||
* @param identities initial set of identities for the service, typically only used for unit tests.
|
||||
*/
|
||||
// TODO There is duplicated logic between this and PersistentIdentityService
|
||||
@ThreadSafe
|
||||
class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(),
|
||||
override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
|
||||
override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityServiceInternal {
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
}
|
||||
@ -54,29 +50,10 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
|
||||
}
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? = verifyAndRegisterIdentity(trustAnchor, identity)
|
||||
|
||||
override fun registerIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
try {
|
||||
identity.verify(trustAnchor)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.")
|
||||
log.warn("Certificate path :")
|
||||
identityCertChain.reversed().forEachIndexed { index, certificate ->
|
||||
val space = (0 until index).joinToString("") { " " }
|
||||
log.warn("$space${certificate.subjectX500Principal}")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
// Ensure we record the first identity of the same name, first
|
||||
val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false }
|
||||
if (wellKnownCert != identity.certificate) {
|
||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
log.trace { "Registering identity $identity" }
|
||||
keyToParties[identity.owningKey] = identity
|
||||
// Always keep the first party we registered, as that's the well known identity
|
||||
@ -89,26 +66,7 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
|
||||
// We give the caller a copy of the data set to avoid any locking problems
|
||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = ArrayList(keyToParties.values)
|
||||
|
||||
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party
|
||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = principalToParties[name]?.party
|
||||
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
||||
// The original version of this would return the party as-is if it was a Party (rather than AnonymousParty),
|
||||
// however that means that we don't verify that we know who owns the key. As such as now enforce turning the key
|
||||
// into a party, and from there figure out the well known party.
|
||||
val candidate = partyFromKey(party.owningKey)
|
||||
// TODO: This should be done via the network map cache, which is the authoritative source of well known identities
|
||||
return if (candidate != null) {
|
||||
require(party.nameOrNull() == null || party.nameOrNull() == candidate.name) { "Candidate party $candidate does not match expected $party" }
|
||||
wellKnownPartyFromX500Name(candidate.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference) = wellKnownPartyFromAnonymous(partyRef.party)
|
||||
override fun requireWellKnownPartyFromAnonymous(party: AbstractParty): Party {
|
||||
return wellKnownPartyFromAnonymous(party) ?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}")
|
||||
}
|
||||
|
||||
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
||||
val results = LinkedHashSet<Party>()
|
||||
@ -117,14 +75,4 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
@Throws(UnknownAnonymousPartyException::class)
|
||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||
val anonymousIdentity = keyToParties[anonymousParty.owningKey] ?:
|
||||
throw UnknownAnonymousPartyException("Unknown $anonymousParty")
|
||||
val issuingCert = anonymousIdentity.certPath.certificates[1]
|
||||
require(issuingCert.publicKey == party.owningKey) {
|
||||
"Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,8 @@
|
||||
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -24,7 +21,6 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||
@ -45,7 +41,6 @@ import javax.persistence.Lob
|
||||
* @param trustRoot certificate from the zone operator for identity on the network.
|
||||
* @param caCertificates list of additional certificates.
|
||||
*/
|
||||
// TODO There is duplicated logic between this and InMemoryIdentityService
|
||||
@ThreadSafe
|
||||
class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
private val database: CordaPersistence,
|
||||
@ -137,38 +132,21 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
return database.transaction {
|
||||
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
try {
|
||||
identity.verify(trustAnchor)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
log.warn(e.localizedMessage)
|
||||
log.warn("Path = ")
|
||||
identityCertChain.reversed().forEach {
|
||||
log.warn(it.subjectX500Principal.toString())
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
// Ensure we record the first identity of the same name, first
|
||||
val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false }
|
||||
if (wellKnownCert != identity.certificate) {
|
||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
log.debug { "Registering identity $identity" }
|
||||
val key = mapToKey(identity)
|
||||
keyToParties.addWithDuplicatesAllowed(key, identity, false)
|
||||
// Always keep the first party we registered, as that's the well known identity
|
||||
principalToParties.addWithDuplicatesAllowed(identity.name, key, false)
|
||||
val parentId = mapToKey(identityCertChain[1].publicKey)
|
||||
keyToParties[parentId]
|
||||
verifyAndRegisterIdentity(trustAnchor, identity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
log.debug { "Registering identity $identity" }
|
||||
val key = mapToKey(identity)
|
||||
keyToParties.addWithDuplicatesAllowed(key, identity)
|
||||
// Always keep the first party we registered, as that's the well known identity
|
||||
principalToParties.addWithDuplicatesAllowed(identity.name, key, false)
|
||||
val parentId = mapToKey(identityCertChain[1].publicKey)
|
||||
return keyToParties[parentId]
|
||||
}
|
||||
|
||||
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction { keyToParties[mapToKey(owningKey)] }
|
||||
|
||||
private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? {
|
||||
@ -183,27 +161,9 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
// We give the caller a copy of the data set to avoid any locking problems
|
||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = database.transaction { keyToParties.allPersisted().map { it.second }.asIterable() }
|
||||
|
||||
override fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party
|
||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party
|
||||
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
||||
return database.transaction {
|
||||
// The original version of this would return the party as-is if it was a Party (rather than AnonymousParty),
|
||||
// however that means that we don't verify that we know who owns the key. As such as now enforce turning the key
|
||||
// into a party, and from there figure out the well known party.
|
||||
val candidate = partyFromKey(party.owningKey)
|
||||
// TODO: This should be done via the network map cache, which is the authoritative source of well known identities
|
||||
if (candidate != null) {
|
||||
wellKnownPartyFromX500Name(candidate.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference) = wellKnownPartyFromAnonymous(partyRef.party)
|
||||
override fun requireWellKnownPartyFromAnonymous(party: AbstractParty): Party {
|
||||
return wellKnownPartyFromAnonymous(party) ?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}")
|
||||
}
|
||||
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? = database.transaction { super.wellKnownPartyFromAnonymous(party) }
|
||||
|
||||
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
||||
return database.transaction {
|
||||
@ -216,13 +176,6 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
}
|
||||
|
||||
@Throws(UnknownAnonymousPartyException::class)
|
||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||
database.transaction {
|
||||
val anonymousIdentity = certificateFromKey(anonymousParty.owningKey) ?: throw UnknownAnonymousPartyException("Unknown $anonymousParty")
|
||||
val issuingCert = anonymousIdentity.certPath.certificates[1]
|
||||
require(issuingCert.publicKey == party.owningKey) {
|
||||
"Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}."
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) = database.transaction { super.assertOwnership(party, anonymousParty) }
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
package net.corda.node.services.messaging
|
||||
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -19,6 +20,7 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.node.internal.artemis.*
|
||||
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_P2P_ROLE
|
||||
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.PEER_ROLE
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport
|
||||
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
|
||||
@ -101,7 +103,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||
|
||||
// TODO: Maybe wrap [IOException] on a key store load error so that it's clearly splitting key store loading from
|
||||
// Artemis IO errors
|
||||
@Throws(IOException::class, KeyStoreException::class)
|
||||
@Throws(IOException::class, AddressBindingException::class, KeyStoreException::class)
|
||||
private fun configureAndStartServer() {
|
||||
val artemisConfig = createArtemisConfig()
|
||||
val securityManager = createArtemisSecurityManager()
|
||||
@ -114,7 +116,15 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
|
||||
}
|
||||
|
||||
activeMQServer.start()
|
||||
try {
|
||||
activeMQServer.start()
|
||||
} catch (e: java.io.IOException) {
|
||||
if (e.isBindingError()) {
|
||||
throw AddressBindingException(config.p2pAddress)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
activeMQServer.remotingService.addIncomingInterceptor(ArtemisMessageSizeChecksInterceptor(maxMessageSize))
|
||||
activeMQServer.remotingService.addIncomingInterceptor(AmqpMessageSizeChecksInterceptor(maxMessageSize))
|
||||
// Config driven switch between legacy CORE bridges and the newer AMQP protocol bridges.
|
||||
|
@ -10,11 +10,13 @@
|
||||
|
||||
package net.corda.node.services.rpc
|
||||
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.internal.artemis.*
|
||||
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_SECURITY_CONFIG
|
||||
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.RPC_SECURITY_CONFIG
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.node.internal.security.RPCSecurityManager
|
||||
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
@ -54,7 +56,15 @@ internal class ArtemisRpcBroker internal constructor(
|
||||
|
||||
override fun start() {
|
||||
logger.debug("Artemis RPC broker is starting.")
|
||||
server.start()
|
||||
try {
|
||||
server.start()
|
||||
} catch (e: java.io.IOException) {
|
||||
if (e.isBindingError()) {
|
||||
throw AddressBindingException(adminAddressOptional?.let { setOf(it, addresses.primary) } ?: setOf(addresses.primary))
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
logger.debug("Artemis RPC broker is started.")
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ package net.corda.webserver
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.core.internal.location
|
||||
import net.corda.core.internal.rootCause
|
||||
import net.corda.webserver.internal.NodeWebServer
|
||||
@ -76,6 +77,9 @@ fun main(args: Array<String>) {
|
||||
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
||||
println("Webserver started up in $elapsed sec")
|
||||
server.run()
|
||||
} catch (e: AddressBindingException) {
|
||||
log.error(e.message)
|
||||
exitProcess(1)
|
||||
} catch (e: Exception) {
|
||||
log.error("Exception during node startup", e)
|
||||
exitProcess(1)
|
||||
|
@ -11,26 +11,18 @@
|
||||
package net.corda.webserver.internal
|
||||
|
||||
import com.google.common.html.HtmlEscapers.htmlEscaper
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.RPCException
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.webserver.WebServerConfig
|
||||
import net.corda.webserver.converters.CordaConverterProvider
|
||||
import net.corda.webserver.services.WebServerPluginRegistry
|
||||
import net.corda.webserver.servlets.AttachmentDownloadServlet
|
||||
import net.corda.webserver.servlets.CorDappInfoServlet
|
||||
import net.corda.webserver.servlets.DataUploadServlet
|
||||
import net.corda.webserver.servlets.ObjectMapperConfig
|
||||
import net.corda.webserver.servlets.ResponseFilter
|
||||
import org.eclipse.jetty.server.Connector
|
||||
import org.eclipse.jetty.server.HttpConfiguration
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.SslConnectionFactory
|
||||
import net.corda.webserver.servlets.*
|
||||
import org.eclipse.jetty.server.*
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.DefaultServlet
|
||||
@ -44,6 +36,7 @@ import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.io.Writer
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.BindException
|
||||
import java.nio.file.NoSuchFileException
|
||||
import java.util.*
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
@ -105,7 +98,15 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
server.connectors = arrayOf<Connector>(connector)
|
||||
|
||||
server.handler = handlerCollection
|
||||
server.start()
|
||||
try {
|
||||
server.start()
|
||||
} catch (e: IOException) {
|
||||
if (e is BindException || e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) {
|
||||
throw AddressBindingException(address)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
log.info("Starting webserver on address $address")
|
||||
return server
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user