Merge pull request #1238 from corda/anthony-os-merge-20180710

O/S Merge 2018-07-10
This commit is contained in:
Michele Sollecito 2018-07-10 15:48:10 +01:00 committed by GitHub
commit d9923f8809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 595 additions and 197 deletions

View File

@ -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()}."
}
}
}
}

View File

@ -13,6 +13,7 @@ package net.corda.core.node.services
import net.corda.core.CordaException import net.corda.core.CordaException
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.* import net.corda.core.identity.*
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.PublicKey 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 * 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. * 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) @Throws(UnknownAnonymousPartyException::class)
fun assertOwnership(party: Party, anonymousParty: AnonymousParty) 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 * 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]. * @param key The owning [PublicKey] of the [Party].
* @return Returns a [Party] with a matching owningKey if known, else returns null. * @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 * 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. * @param party identity to determine well known identity for.
* @return well known identity, if found. * @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]. * 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. * @param partyRef identity (and reference, which is unused) to determine well known identity for.
* @return the well known identity, or null if unknown. * @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. * 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. * @return the well known identity.
* @throws IllegalArgumentException * @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 * Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may

View File

@ -12,6 +12,9 @@ Unreleased
and within that file the ``bridgeMode`` propety has been modified to ``firewallMode`` for overall consistency. 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. 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. * 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. 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.

View File

@ -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. :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 :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: :raft: If part of a distributed Raft cluster specify this config object, with the following settings:

View File

@ -24,18 +24,13 @@ You can find out more about network maps and network parameters from :doc:`netwo
Bootstrapping a test network Bootstrapping a test network
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The bootstrapper is distributed as part of |release| in the form of runnable JAR file "|jar_name|". The bootstrapper can be downloaded from https://downloads.corda.net/network-bootstrapper-VERSION.jar, where ``VERSION``
is the Corda version.
.. |jar_name| replace:: corda-tools-network-bootstrapper-|version|.jar
Create a directory containing a node config file, ending in "_node.conf", for each node you want to create. Then run the Create a directory containing a node config file, ending in "_node.conf", for each node you want to create. Then run the
following command: following command:
.. parsed-literal:: ``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
> java -jar |jar_name| --dir <nodes-root-dir>
..
For example running the command on a directory containing these files: 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 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. 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 .. 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. 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 If you would like the Network Bootstrapper to include your CorDapps in each generated node, just place them in the directory
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 alongside the config files. For example, if your directory has this structure:
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.
Running the bootstrapper again on the same network will allow a new node to be added or an existing one to have its updated .. sourcecode:: none
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. .
├── 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 Whitelisting contracts
~~~~~~~~~~~~~~~~~~~~~~ ----------------------
The CorDapp JARs are also automatically used to create the *Zone whitelist* (see :doc:`api-contract-constraints`) for Any CorDapps provided when bootstrapping a network will be scanned for contracts which will be used to create the
the network. *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. .. 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 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`). 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 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 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.Cash
net.corda.finance.contracts.asset.CommercialPaper 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.

View File

@ -10,7 +10,107 @@ Quickstart
getting-set-up.rst getting-set-up.rst
tutorial-cordapp.rst tutorial-cordapp.rst
* :doc:`Set up your machine for CorDapp development <getting-set-up>` Welcome to the Corda Quickstart Guide. Follow the links below to help get going quickly with Corda.
* :doc:`Run the Example CorDapp <tutorial-cordapp>`
* `View CorDapps in Corda Explore <http://explore.corda.zone/>`_ I want to:
* `Download sample CorDapps <https://www.corda.net/samples/>`_
* :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 |
+---------------------------------------------------------------------------------------------------------+

View File

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

View File

@ -73,7 +73,7 @@ class BootTests : IntegrationTest() {
@Test @Test
fun `double node start doesn't write into log file`() { fun `double node start doesn't write into log file`() {
driver { driver(DriverParameters(notarySpecs = emptyList())) {
val alice = startNode(providedName = ALICE_NAME).get() val alice = startNode(providedName = ALICE_NAME).get()
val logFolder = alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME val logFolder = alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
val logFile = logFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() } val logFile = logFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }

View File

@ -21,7 +21,16 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationContext
import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sign 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.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party 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.concurrent.openFuture
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.* import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.* import net.corda.core.messaging.FlowHandle
import net.corda.core.node.services.* 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.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize 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.CordaClock
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.CheckpointVerifier.verifyCheckpointsCompatible 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.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.api.* import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.config.* 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.shell.toShellConfig
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService 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.NodeAttachmentService
import net.corda.node.services.persistence.NodePropertiesPersistentStore import net.corda.node.services.persistence.NodePropertiesPersistentStore
import net.corda.node.services.persistence.RunOnceService 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.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.SingleThreadedStateMachineManager import net.corda.node.services.statemachine.SingleThreadedStateMachineManager
import net.corda.node.services.statemachine.StateMachineManager 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.appName
import net.corda.node.services.statemachine.flowVersionAndInitiatingClass import net.corda.node.services.statemachine.flowVersionAndInitiatingClass
import net.corda.node.services.transactions.BFTNonValidatingNotaryService 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.RaftValidatingNotaryService
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.services.transactions.ValidatingNotaryService 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.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
@ -140,7 +181,6 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.SECONDS
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.set import kotlin.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -431,7 +471,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
open fun startShell() { open fun startShell() {
if (configuration.shouldInitCrashShell()) { 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)
} }
} }

View File

@ -36,6 +36,7 @@ import net.corda.node.VersionInfo
import net.corda.node.internal.artemis.ArtemisBroker import net.corda.node.internal.artemis.ArtemisBroker
import net.corda.node.internal.artemis.BrokerAddresses import net.corda.node.internal.artemis.BrokerAddresses
import net.corda.node.internal.cordapp.CordappLoader 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.RPCSecurityManagerImpl
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme 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.api.SchemaService
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.SecurityConfiguration 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.shouldInitCrashShell
import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.messaging.ArtemisMessagingServer 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_RPC_SERVER_CONTEXT
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
import net.corda.serialization.internal.SerializationFactoryImpl import net.corda.serialization.internal.SerializationFactoryImpl
import org.h2.jdbc.JdbcSQLException
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rx.Scheduler import rx.Scheduler
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import java.net.BindException
import java.nio.file.Path import java.nio.file.Path
import java.security.PublicKey import java.security.PublicKey
import java.time.Clock import java.time.Clock
@ -354,7 +356,7 @@ open class Node(configuration: NodeConfiguration,
if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) { if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) {
val effectiveH2Settings = configuration.effectiveH2Settings val effectiveH2Settings = configuration.effectiveH2Settings
if(effectiveH2Settings != null && effectiveH2Settings.address != null) { if (effectiveH2Settings?.address != null) {
val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';') val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';')
val server = org.h2.tools.Server.createTcpServer( val server = org.h2.tools.Server.createTcpServer(
"-tcpPort", effectiveH2Settings.address.port.toString(), "-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) // override interface that createTcpServer listens on (which is always 0.0.0.0)
System.setProperty("h2.bindAddress", effectiveH2Settings.address.host) System.setProperty("h2.bindAddress", effectiveH2Settings.address.host)
runOnStop += server::stop 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") printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node")
} }
} }

View File

@ -17,8 +17,14 @@ import com.typesafe.config.ConfigRenderOptions
import io.netty.channel.unix.Errors import io.netty.channel.unix.Errors
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.Crypto 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.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.Try
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.* import net.corda.node.*
@ -166,6 +172,9 @@ open class NodeStartup(val args: Array<String>) {
} catch (e: CheckpointIncompatibleException) { } catch (e: CheckpointIncompatibleException) {
logger.error(e.message) logger.error(e.message)
return false return false
} catch (e: AddressBindingException) {
logger.error(e.message)
return false
} catch (e: NetworkParametersReader.Error) { } catch (e: NetworkParametersReader.Error) {
logger.error(e.message) logger.error(e.message)
return false return false

View File

@ -10,9 +10,11 @@
package net.corda.node.internal.artemis package net.corda.node.internal.artemis
import io.netty.channel.unix.Errors
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.LifecycleSupport import net.corda.node.internal.LifecycleSupport
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
import java.net.BindException
interface ArtemisBroker : LifecycleSupport, AutoCloseable { interface ArtemisBroker : LifecycleSupport, AutoCloseable {
val addresses: BrokerAddresses val addresses: BrokerAddresses
@ -24,4 +26,6 @@ interface ArtemisBroker : LifecycleSupport, AutoCloseable {
data class BrokerAddresses(val primary: NetworkHostAndPort, private val adminArg: NetworkHostAndPort?) { data class BrokerAddresses(val primary: NetworkHostAndPort, private val adminArg: NetworkHostAndPort?) {
val admin = adminArg ?: primary val admin = adminArg ?: primary
} }
fun java.io.IOException.isBindingError() = this is BindException || this is Errors.NativeIoException && message?.contains("Address already in use") == true

View File

@ -10,12 +10,79 @@
package net.corda.node.services.api 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.identity.PartyAndCertificate
import net.corda.core.internal.CertRole
import net.corda.core.node.services.IdentityService 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 { 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. */ /** 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) { fun justVerifyAndRegisterIdentity(identity: PartyAndCertificate) {
verifyAndRegisterIdentity(identity) 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?
} }

View File

@ -10,16 +10,13 @@
package net.corda.node.services.identity package net.corda.node.services.identity
import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.CordaX500Name
import net.corda.core.crypto.toStringShort import net.corda.core.identity.Party
import net.corda.core.identity.* import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.CertRole
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace 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 net.corda.nodeapi.internal.crypto.x509Certificates
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.PublicKey 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. * @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 @ThreadSafe
class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(), class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(),
override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityService { override val trustRoot: X509Certificate) : SingletonSerializeAsToken(), IdentityServiceInternal {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
} }
@ -54,29 +50,10 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
} }
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? = verifyAndRegisterIdentity(trustAnchor, identity)
// Validate the chain first, before we do anything clever with it
override fun registerIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
val identityCertChain = identity.certPath.x509Certificates 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" } log.trace { "Registering identity $identity" }
keyToParties[identity.owningKey] = identity keyToParties[identity.owningKey] = identity
// Always keep the first party we registered, as that's the well known 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 // 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 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 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> { override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
val results = LinkedHashSet<Party>() val results = LinkedHashSet<Party>()
@ -117,14 +75,4 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
} }
return results 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()}."
}
}
} }

View File

@ -10,11 +10,8 @@
package net.corda.node.services.identity package net.corda.node.services.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.* import net.corda.core.identity.*
import net.corda.core.internal.CertRole
import net.corda.core.internal.hash import net.corda.core.internal.hash
import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken 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.services.api.IdentityServiceInternal
import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.crypto.X509CertificateFactory 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.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX 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 trustRoot certificate from the zone operator for identity on the network.
* @param caCertificates list of additional certificates. * @param caCertificates list of additional certificates.
*/ */
// TODO There is duplicated logic between this and InMemoryIdentityService
@ThreadSafe @ThreadSafe
class PersistentIdentityService(override val trustRoot: X509Certificate, class PersistentIdentityService(override val trustRoot: X509Certificate,
private val database: CordaPersistence, private val database: CordaPersistence,
@ -137,38 +132,21 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
return database.transaction { return database.transaction {
verifyAndRegisterIdentity(trustAnchor, identity)
// 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]
} }
} }
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)] } override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction { keyToParties[mapToKey(owningKey)] }
private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? { 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 // 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 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 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 wellKnownPartyFromAnonymous(party: AbstractParty): Party? = database.transaction { super.wellKnownPartyFromAnonymous(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> { override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
return database.transaction { return database.transaction {
@ -216,13 +176,6 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
} }
@Throws(UnknownAnonymousPartyException::class) @Throws(UnknownAnonymousPartyException::class)
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) { override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) = database.transaction { super.assertOwnership(party, 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()}."
}
}
}
} }

View File

@ -10,6 +10,7 @@
package net.corda.node.services.messaging package net.corda.node.services.messaging
import io.netty.channel.unix.Errors
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.serialization.SingletonSerializeAsToken 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.*
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_P2P_ROLE import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_P2P_ROLE
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.PEER_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.node.services.config.NodeConfiguration
import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor 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 // TODO: Maybe wrap [IOException] on a key store load error so that it's clearly splitting key store loading from
// Artemis IO errors // Artemis IO errors
@Throws(IOException::class, KeyStoreException::class) @Throws(IOException::class, AddressBindingException::class, KeyStoreException::class)
private fun configureAndStartServer() { private fun configureAndStartServer() {
val artemisConfig = createArtemisConfig() val artemisConfig = createArtemisConfig()
val securityManager = createArtemisSecurityManager() val securityManager = createArtemisSecurityManager()
@ -114,7 +116,15 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } } 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(ArtemisMessageSizeChecksInterceptor(maxMessageSize))
activeMQServer.remotingService.addIncomingInterceptor(AmqpMessageSizeChecksInterceptor(maxMessageSize)) activeMQServer.remotingService.addIncomingInterceptor(AmqpMessageSizeChecksInterceptor(maxMessageSize))
// Config driven switch between legacy CORE bridges and the newer AMQP protocol bridges. // Config driven switch between legacy CORE bridges and the newer AMQP protocol bridges.

View File

@ -10,11 +10,13 @@
package net.corda.node.services.rpc package net.corda.node.services.rpc
import io.netty.channel.unix.Errors
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.internal.artemis.* 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.NODE_SECURITY_CONFIG
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.RPC_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.node.internal.security.RPCSecurityManager
import net.corda.nodeapi.BrokerRpcSslOptions import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration
@ -54,7 +56,15 @@ internal class ArtemisRpcBroker internal constructor(
override fun start() { override fun start() {
logger.debug("Artemis RPC broker is starting.") 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.") logger.debug("Artemis RPC broker is started.")
} }

View File

@ -14,6 +14,7 @@ package net.corda.webserver
import com.typesafe.config.ConfigException import com.typesafe.config.ConfigException
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.internal.location import net.corda.core.internal.location
import net.corda.core.internal.rootCause import net.corda.core.internal.rootCause
import net.corda.webserver.internal.NodeWebServer import net.corda.webserver.internal.NodeWebServer
@ -76,6 +77,9 @@ fun main(args: Array<String>) {
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
println("Webserver started up in $elapsed sec") println("Webserver started up in $elapsed sec")
server.run() server.run()
} catch (e: AddressBindingException) {
log.error(e.message)
exitProcess(1)
} catch (e: Exception) { } catch (e: Exception) {
log.error("Exception during node startup", e) log.error("Exception during node startup", e)
exitProcess(1) exitProcess(1)

View File

@ -11,26 +11,18 @@
package net.corda.webserver.internal package net.corda.webserver.internal
import com.google.common.html.HtmlEscapers.htmlEscaper import com.google.common.html.HtmlEscapers.htmlEscaper
import io.netty.channel.unix.Errors
import net.corda.client.jackson.JacksonSupport import net.corda.client.jackson.JacksonSupport
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCException
import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.webserver.WebServerConfig import net.corda.webserver.WebServerConfig
import net.corda.webserver.converters.CordaConverterProvider import net.corda.webserver.converters.CordaConverterProvider
import net.corda.webserver.services.WebServerPluginRegistry import net.corda.webserver.services.WebServerPluginRegistry
import net.corda.webserver.servlets.AttachmentDownloadServlet import net.corda.webserver.servlets.*
import net.corda.webserver.servlets.CorDappInfoServlet import org.eclipse.jetty.server.*
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 org.eclipse.jetty.server.handler.ErrorHandler import org.eclipse.jetty.server.handler.ErrorHandler
import org.eclipse.jetty.server.handler.HandlerCollection import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.DefaultServlet import org.eclipse.jetty.servlet.DefaultServlet
@ -44,6 +36,7 @@ import org.slf4j.LoggerFactory
import java.io.IOException import java.io.IOException
import java.io.Writer import java.io.Writer
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.net.BindException
import java.nio.file.NoSuchFileException import java.nio.file.NoSuchFileException
import java.util.* import java.util.*
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
@ -105,7 +98,15 @@ class NodeWebServer(val config: WebServerConfig) {
server.connectors = arrayOf<Connector>(connector) server.connectors = arrayOf<Connector>(connector)
server.handler = handlerCollection 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") log.info("Starting webserver on address $address")
return server return server
} }