[CORDA-2235]: Add overrides for network parameters via command line and file (#4279)

* Temp commit

* Print the error message first by default, makes error output more natural.

* Polishing

* Further modifications after testing

* Documentation updates

* Couple of fixes after review

* Removing unnecessary tests

* Fix broken test

* Add interface to bootstrapper for testign

* Added unit tests

* Remove unused class

* Fix up bootstrapper unit tests and add a couple more

* Refactor the tests slightly

* Review comments

* Couple of minor tweaks
This commit is contained in:
Anthony Keenan 2018-11-26 17:11:05 +00:00 committed by GitHub
parent d399e3c242
commit b7d04b1c6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 787 additions and 415 deletions

View File

@ -40,6 +40,7 @@ buildscript {
ext.fileupload_version = '1.3.3'
ext.junit_version = '4.12'
ext.mockito_version = '2.18.3'
ext.mockito_kotlin_version = '1.5.0'
ext.hamkrest_version = '1.4.2.2'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'

View File

@ -448,7 +448,7 @@ object Configuration {
override fun toString(): String {
return "(keyName='$keyName', typeName='$typeName', path=$path, message='$message')"
return "$message: (keyName='$keyName', typeName='$typeName', path=$path)"
}
/**

View File

@ -107,8 +107,8 @@ data class NetworkParameters(
private fun owns(packageName: String, fullClassName: String) = fullClassName.startsWith("$packageName.", ignoreCase = true)
// Make sure that packages don't overlap so that ownership is clear.
private fun noOverlap(packages: Collection<String>) = packages.all { currentPackage ->
packages.none { otherPackage -> otherPackage != currentPackage && otherPackage.startsWith("${currentPackage}.") }
fun noOverlap(packages: Collection<String>) = packages.all { currentPackage ->
packages.none { otherPackage -> otherPackage != currentPackage && otherPackage.startsWith("$currentPackage.") }
}
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
@ -117,14 +117,14 @@ data class NetworkParameters(
}
init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
require(minimumPlatformVersion > 0) { "Minimum platform level must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
require(epoch > 0) { "epoch must be at least 1" }
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
require(!eventHorizon.isNegative) { "eventHorizon must be positive value" }
require(epoch > 0) { "Epoch must be at least 1" }
require(maxMessageSize > 0) { "Maximum message size must be at least 1" }
require(maxTransactionSize > 0) { "Maximum transaction size must be at least 1" }
require(!eventHorizon.isNegative) { "Event Horizon must be a positive value" }
packageOwnership.keys.forEach(::requirePackageValid)
require(noOverlap(packageOwnership.keys)) { "multiple packages added to the packageOwnership overlap." }
require(noOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." }
}
fun copy(minimumPlatformVersion: Int,
@ -217,3 +217,7 @@ data class NotaryInfo(val identity: Party, val validating: Boolean)
* version.
*/
class ZoneVersionTooLowException(message: String) : CordaRuntimeException(message)
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
return this.findAnnotation<AutoAcceptable>() != null
}

View File

@ -288,7 +288,7 @@ Configuring a node where the Corda Compatibility Zone's registration and Network
.. literalinclude:: example-code/src/main/resources/example-node-with-networkservices.conf
Fields Override
Fields override
---------------
JVM options or environmental variables prefixed with ``corda.`` can override ``node.conf`` fields.
Provided system properties can also set values for absent fields in ``node.conf``.
@ -299,7 +299,7 @@ This is an example of adding/overriding the keyStore password :
java -Dcorda.rpcSettings.ssl.keyStorePassword=mypassword -jar node.jar
CRL Configuration
CRL configuration
-----------------
The Corda Network provides an endpoint serving an empty certificate revocation list for the TLS-level certificates.
This is intended for deployments that do not provide a CRL infrastructure but still require a strict CRL mode checking.
@ -318,7 +318,9 @@ Together with the above configuration `tlsCertCrlIssuer` option needs to be set
This set-up ensures that the TLS-level certificates are embedded with the CRL distribution point referencing the CRL issued by R3.
In cases where a proprietary CRL infrastructure is provided those values need to be changed accordingly.
Hiding Sensitive Data
.. _corda-configuration-hiding-sensitive-data:
Hiding sensitive data
---------------------
A frequent requirement is that configuration files must not expose passwords to unauthorised readers. By leveraging environment variables, it is possible to hide passwords and other similar fields.

View File

@ -15,7 +15,7 @@ In addition to the network map, all the nodes must also use the same set of netw
which guarantee interoperability between the nodes. The HTTP network map distributes the network parameters which are downloaded
automatically by the nodes. In the absence of this the network parameters must be generated locally.
For these reasons, test deployments can avail themselves of the network bootstrapper. This is a tool that scans all the
For these reasons, test deployments can avail themselves of the Network Bootstrapper. This is a tool that scans all the
node configurations from a common directory to generate the network parameters file, which is then copied to all the nodes'
directories. It also copies each node's node-info file to every other node so that they can all be visible to each other.
@ -41,7 +41,7 @@ For example running the command on a directory containing these files:
└── partyb_node.conf // Party B's node.conf file
will generate directories containing three nodes: ``notary``, ``partya`` and ``partyb``. They will each use the ``corda.jar``
that comes with the bootstrapper. If a different version of Corda is required then simply place that ``corda.jar`` file
that comes with the Network Bootstrapper. If a different version of Corda is required then simply place that ``corda.jar`` file
alongside the configuration files in the directory.
You can also have the node directories containing their "node.conf" files already laid out. The previous example would be:
@ -56,7 +56,7 @@ You can also have the node directories containing their "node.conf" files alread
└── partyb
└── node.conf
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.
Providing CorDapps to the Network Bootstrapper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -87,7 +87,7 @@ Any CorDapps provided when bootstrapping a network will be scanned for contracts
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`).
By default the bootstrapper will whitelist all the contracts found in the unsigned CorDapp JARs (a JAR file not signed by jarSigner tool).
By default the Bootstrapper will whitelist all the contracts found in the unsigned CorDapp JARs (a JAR file not signed by jarSigner tool).
Whitelisted contracts are checked by `Zone constraints`, while contract classes from signed JARs will be checked by `Signature constraints`.
To prevent certain contracts from unsigned JARs from being whitelisted, add their fully qualified class name in the ``exclude_whitelist.txt``.
These will instead use the more restrictive ``HashAttachmentConstraint``.
@ -103,7 +103,7 @@ For example:
Modifying a bootstrapped network
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The network bootstrapper is provided as a development tool for setting up Corda networks for development and testing.
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.
@ -115,7 +115,7 @@ the nodes are being run on different machines you need to do the following:
* 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,
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.
@ -123,11 +123,11 @@ 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
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:
can use the Network Bootstrapper on the following network structure:
.. sourcecode:: none
@ -148,7 +148,7 @@ can use the network bootstrapper on the following network structure:
│ └── 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:
Then run the Network Bootstrapper again from the root dir:
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
@ -182,19 +182,19 @@ Which will give the following:
├── 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
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
.. note:: The Network 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.
file) then the Network Bootstrapper can be used to append contracts from new CorDapps to the current whitelist.
For example, with the following pre-generated network:
.. sourcecode:: none
@ -217,7 +217,7 @@ For example, with the following pre-generated network:
│ └── 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:
Then run the Network Bootstrapper again from the root dir:
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
@ -247,81 +247,183 @@ To give the following:
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
Package namespace ownership
----------------------------
Modifying the network parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Package namespace ownership is a Corda security feature that allows a compatibility zone to give ownership of parts of the Java package namespace to registered users (e.g. CorDapp development organisations).
The exact mechanism used to claim a namespace is up to the zone operator. A typical approach would be to accept an SSL
certificate with the domain in it as proof of domain ownership, or to accept an email from that domain.
The Network Bootstrapper creates a network parameters file when bootstrapping a network, using a set of sensible defaults. However, if you would like
to override these defaults when testing, there are two ways of doing this. Options can be overridden via the command line or by supplying a configuration
file. If the same parameter is overridden both by a command line argument and in the configuration file, the command line value
will take precedence.
Overriding network parameters via command line
----------------------------------------------
The ``--minimum-platform-version``, ``--max-message-size``, ``--max-transaction-size`` and ``--event-horizon`` command line parameters can
be used to override the default network parameters. See `Command line options`_ for more information.
Overriding network parameters via a file
----------------------------------------
You can provide a network parameters overrides file using the following syntax:
``java -jar network-bootstrapper-VERSION.jar --network-parameters-overrides=<path_to_file>``
Or alternatively, by using the short form version:
``java -jar network-bootstrapper-VERSION.jar -n=<path_to_file>``
The network parameter overrides file is a HOCON file with the following fields, all of which are optional. Any field that is not provided will be
ignored. If a field is not provided and you are bootstrapping a new network, a sensible default value will be used. If a field is not provided and you
are updating an existing network, the value in the existing network parameters file will be used.
.. note:: All fields can be used with placeholders for environment variables. For example: ``${KEY_STORE_PASSWORD}`` would be replaced by the contents of environment
variable ``KEY_STORE_PASSWORD``. See: :ref:`corda-configuration-hiding-sensitive-data` .
The available configuration fields are listed below:
:minimumPlatformVersion: The minimum supported version of the Corda platform that is required for nodes in the network.
:maxMessageSize: The maximum permitted message size, in bytes. This is currently ignored but will be used in a future release.
:maxTransactionSize: The maximum permitted transaction size, in bytes.
:eventHorizon: The time after which nodes will be removed from the network map if they have not been seen during this period. This parameter uses
the ``parse`` function on the ``java.time.Duration`` class to interpret the data. See `here <https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence->`_
for information on valid inputs.
:packageOwnership: A list of package owners. See `Package namespace ownership`_ for more information. For each package owner, the following fields
are required:
:packageName: Java package name (e.g `com.my_company` ).
:keystore: The path of the keystore file containing the signed certificate.
:keystorePassword: The password for the given keystore (not to be confused with the key password).
:keystoreAlias: The alias for the name associated with the certificate to be associated with the package namespace.
An example configuration file:
.. parsed-literal::
minimumPlatformVersion=4
maxMessageSize=10485760
maxTransactionSize=524288000
eventHorizon="30 days"
packageOwnership=[
{
packageName="com.example"
keystore="myteststore"
keystorePassword="MyStorePassword"
keystoreAlias="MyKeyAlias"
}
]
Package namespace ownership
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Package namespace ownership is a Corda security feature that allows a compatibility zone to give ownership of parts of the Java package
namespace to registered users (e.g. a CorDapp development organisation). The exact mechanism used to claim a namespace is up to the zone
operator. A typical approach would be to accept an SSL certificate with the domain in it as proof of domain ownership, or to accept an email from that domain.
.. note:: Read more about *Package ownership* :doc:`here<design/data-model-upgrades/package-namespace-ownership>`.
A Java package namespace is case insensitive and cannot be a sub-package of an existing registered namespace.
See `Naming a Package <https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html>`_ and `Naming Conventions <https://www.oracle.com/technetwork/java/javase/documentation/codeconventions-135099.html#28840 for guidelines and conventions>`_ for guidelines on naming conventions.
Registration of a java package namespace requires creation of a signed certificate as generated by the
The registration of a Java package namespace requires the creation of a signed certificate as generated by the
`Java keytool <https://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html>`_.
The following four items are passed as a semi-colon separated string to the ``--register-package-owner`` command:
The packages can be registered by supplying a network parameters override config file via the command line, using the ``--network-parameters-overrides`` command.
1. Java package name (e.g `com.my_company` ).
2. Keystore file refers to the full path of the file containing the signed certificate.
3. Password refers to the key store password (not to be confused with the key password).
4. Alias refers to the name associated with a certificate containing the public key to be associated with the package namespace.
For each package to be registered, the following are required:
Let's use the `Example CorDapp <https://github.com/corda/cordapp-example>`_ to initialise a simple network, and then register and unregister a package namespace.
:packageName: Java package name (e.g `com.my_company` ).
:keystore: The path of the keystore file containing the signed certificate. If a relative path is provided, it is assumed to be relative to the
location of the configuration file.
:keystorePassword: The password for the given keystore (not to be confused with the key password).
:keystoreAlias: The alias for the name associated with the certificate to be associated with the package namespace.
Using the `Example CorDapp <https://github.com/corda/cordapp-example>`_ as an example, we will initialise a simple network and then register and unregister a package namespace.
Checkout the Example CorDapp and follow the instructions to build it `here <https://docs.corda.net/tutorial-cordapp.html#building-the-example-cordapp>`_.
.. note:: You can point to any existing bootstrapped corda network (this will have the effect of updating the associated network parameters file).
1. Create a new public key to use for signing the java package namespace we wish to register:
#. Create a new public key to use for signing the Java package namespace we wish to register:
.. code-block:: shell
$JAVA_HOME/bin/keytool -genkeypair -keystore _teststore -storepass MyStorePassword -keyalg RSA -alias MyKeyAlias -keypass MyKeyPassword -dname "O=Alice Corp, L=Madrid, C=ES"
This will generate a key store file called ``_teststore`` in the current directory.
#. Create a ``network-parameters.conf`` file in the same directory, with the following information:
.. parsed-literal::
packageOwnership=[
{
packageName="com.example"
keystore="_teststore"
keystorePassword="MyStorePassword"
keystoreAlias="MyKeyAlias"
}
]
#. Register the package namespace to be claimed by the public key generated above:
.. code-block:: shell
# Register the Java package namespace using the Network Bootstrapper
java -jar network-bootstrapper.jar --dir build/nodes --network-parameter-overrides=network-parameters.conf
#. To unregister the package namespace, edit the ``network-parameters.conf`` file to remove the package:
.. parsed-literal::
packageOwnership=[]
#. Unregister the package namespace:
.. code-block:: shell
# Unregister the Java package namespace using the Network Bootstrapper
java -jar network-bootstrapper.jar --dir build/nodes --network-parameter-overrides=network-parameters.conf
Command line options
~~~~~~~~~~~~~~~~~~~~
The Network Bootstrapper can be started with the following command line options:
.. code-block:: shell
$JAVA_HOME/bin/keytool -genkeypair -keystore _teststore -storepass MyStorePassword -keyalg RSA -alias MyKeyAlias -keypass MyKeyPassword -dname "O=Alice Corp, L=Madrid, C=ES"
This will generate a key store file called ``_teststore`` in the current directory.
2. Register the package namespace to be claimed by the public key generated above:
.. code-block:: shell
# Register the java package namespace using the bootstrapper tool
java -jar network-bootstrapper.jar --dir build/nodes --register-package-owner com.example;./_teststore;MyStorePassword;MyKeyAlias
3. Unregister the package namespace:
.. code-block:: shell
# Unregister the java package namespace using the bootstrapper tool
java -jar network-bootstrapper.jar --dir build/nodes --unregister-package-owner com.example
Command-line options
--------------------
The network bootstrapper can be started with the following command-line options:
.. code-block:: shell
bootstrapper [-hvV] [--no-copy] [--dir=<dir>] [--logging-level=<loggingLevel>]
[--minimum-platform-version=<minimumPlatformVersion>]
[--register-package-owner java-package-namespace=keystore-file:password:alias]
[--unregister-package-owner java-package-namespace]
[COMMAND]
bootstrapper [-hvV] [--no-copy] [--dir=<dir>] [--event-horizon=<eventHorizon>]
[--logging-level=<loggingLevel>]
[--max-message-size=<maxMessageSize>]
[--max-transaction-size=<maxTransactionSize>]
[--minimum-platform-version=<minimumPlatformVersion>]
[-n=<networkParametersFile>] [COMMAND]
* ``--dir=<dir>``: Root directory containing the node configuration files and CorDapp JARs that will form the test network.
It may also contain existing node directories. Defaults to the current directory.
It may also contain existing node directories. Defaults to the current directory.
* ``--no-copy``: Don't copy the CorDapp JARs into the nodes' "cordapps" directories.
* ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file.
* ``--logging-level=<loggingLevel>``: Enable logging at this level and higher. Possible values: ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO.
* ``--help``, ``-h``: Show this help message and exit.
* ``--version``, ``-V``: Print version information and exit.
* ``--minimum-platform-version``: The minimum platform version to use in the generated network-parameters.
* ``--register-package-owner``: Register a java package namespace with its owners public key.
* ``--unregister-package-owner``: Unregister a java package namespace.
* ``--minimum-platform-version``: The minimum platform version to use in the network-parameters.
* ``--max-message-size``: The maximum message size to use in the network-parameters, in bytes.
* ``--max-transaction-size``: The maximum transaction size to use in the network-parameters, in bytes.
* ``--event-horizon``: The event horizon to use in the network-parameters.
* ``--network-parameter-overrides=<networkParametersFile>``, ``-n=<networkParametersFile>`: Overrides the default network parameters with those
in the given file. See `Overriding network parameters via a file`_ for more information.
Sub-commands
^^^^^^^^^^^^
``install-shell-extensions``: Install ``bootstrapper`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
------------
``install-shell-extensions``: Install ``bootstrapper`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.

View File

@ -3,7 +3,6 @@ package net.corda.nodeapi.internal.network
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.*
@ -19,7 +18,6 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.days
import net.corda.core.utilities.filterNotNullValues
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.*
@ -36,6 +34,7 @@ import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.concurrent.Executors
@ -56,7 +55,7 @@ class NetworkBootstrapper
internal constructor(private val initSerEnv: Boolean,
private val embeddedCordaJar: () -> InputStream,
private val nodeInfosGenerator: (List<Path>) -> List<Path>,
private val contractsJarConverter: (Path) -> ContractsJar) {
private val contractsJarConverter: (Path) -> ContractsJar) : NetworkBootstrapperWithOverridableParameters {
constructor() : this(
initSerEnv = true,
@ -128,6 +127,9 @@ internal constructor(private val initSerEnv: Boolean,
throw IllegalStateException("Error while generating node info file. Please check the logs in $nodeDir.")
}
}
const val DEFAULT_MAX_MESSAGE_SIZE: Int = 10485760
const val DEFAULT_MAX_TRANSACTION_SIZE: Int = 524288000
}
sealed class NotaryCluster {
@ -189,14 +191,15 @@ internal constructor(private val initSerEnv: Boolean,
}
/** Entry point for the tool */
fun bootstrap(directory: Path, copyCordapps: Boolean, minimumPlatformVersion: Int, packageOwnership: Map<String, PublicKey?> = emptyMap()) {
require(minimumPlatformVersion <= PLATFORM_VERSION) { "Minimum platform version cannot be greater than $PLATFORM_VERSION" }
// Don't accidently include the bootstrapper jar as a CorDapp!
override fun bootstrap(directory: Path, copyCordapps: Boolean, networkParameterOverrides: NetworkParametersOverrides) {
require(networkParameterOverrides.minimumPlatformVersion == null || networkParameterOverrides.minimumPlatformVersion <= PLATFORM_VERSION) { "Minimum platform version cannot be greater than $PLATFORM_VERSION" }
// Don't accidentally include the bootstrapper jar as a CorDapp!
val bootstrapperJar = javaClass.location.toPath()
val cordappJars = directory.list { paths ->
paths.filter { it.toString().endsWith(".jar") && !it.isSameAs(bootstrapperJar) && it.fileName.toString() != "corda.jar" }.toList()
paths.filter { it.toString().endsWith(".jar") && !it.isSameAs(bootstrapperJar) && it.fileName.toString() != "corda.jar" }
.toList()
}
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false, minimumPlatformVersion = minimumPlatformVersion, packageOwnership = packageOwnership)
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false, networkParametersOverrides = networkParameterOverrides)
}
private fun bootstrap(
@ -204,8 +207,7 @@ internal constructor(private val initSerEnv: Boolean,
cordappJars: List<Path>,
copyCordapps: Boolean,
fromCordform: Boolean,
minimumPlatformVersion: Int = PLATFORM_VERSION,
packageOwnership: Map<String, PublicKey?> = emptyMap()
networkParametersOverrides: NetworkParametersOverrides = NetworkParametersOverrides()
) {
directory.createDirectories()
println("Bootstrapping local test network in $directory")
@ -250,8 +252,9 @@ internal constructor(private val initSerEnv: Boolean,
println("Gathering notary identities")
val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs)
println("Generating contract implementations whitelist")
// Only add contracts to the whitelist from unsigned jars
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.filter { !isSigned(it) }.map(contractsJarConverter))
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, minimumPlatformVersion, packageOwnership)
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs, networkParametersOverrides)
if (newNetParams != existingNetParams) {
println("${if (existingNetParams == null) "New" else "Updated"} $newNetParams")
} else {
@ -283,7 +286,8 @@ internal constructor(private val initSerEnv: Boolean,
println("Generating node directory for $nodeName")
val nodeDir = (directory / nodeName).createDirectories()
confFile.copyTo(nodeDir / "node.conf", REPLACE_EXISTING)
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.copyTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }
?.copyTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
}
@ -378,50 +382,42 @@ internal constructor(private val initSerEnv: Boolean,
throw IllegalStateException(msg.toString())
}
private fun defaultNetworkParametersWith(notaryInfos: List<NotaryInfo>,
whitelist: Map<String, List<AttachmentId>>): NetworkParameters {
return NetworkParameters(
minimumPlatformVersion = PLATFORM_VERSION,
notaries = notaryInfos,
modifiedTime = Instant.now(),
maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE,
maxTransactionSize = DEFAULT_MAX_TRANSACTION_SIZE,
whitelistedContractImplementations = whitelist,
packageOwnership = emptyMap(),
epoch = 1,
eventHorizon = 30.days
)
}
private fun installNetworkParameters(
notaryInfos: List<NotaryInfo>,
whitelist: Map<String, List<AttachmentId>>,
existingNetParams: NetworkParameters?,
nodeDirs: List<Path>,
minimumPlatformVersion: Int,
packageOwnership: Map<String, PublicKey?>
networkParametersOverrides: NetworkParametersOverrides
): NetworkParameters {
// TODO Add config for maxMessageSize and maxTransactionSize
val netParams = if (existingNetParams != null) {
if (existingNetParams.whitelistedContractImplementations == whitelist && existingNetParams.notaries == notaryInfos &&
existingNetParams.packageOwnership.entries.containsAll(packageOwnership.entries)) {
existingNetParams
} else {
var updatePackageOwnership = mutableMapOf(*existingNetParams.packageOwnership.map { Pair(it.key, it.value) }.toTypedArray())
packageOwnership.forEach { key, value ->
if (value == null) {
if (updatePackageOwnership.remove(key) != null)
println("Unregistering package $key")
} else {
if (updatePackageOwnership.put(key, value) == null)
println("Registering package $key for owner ${value.toStringShort()}")
}
}
existingNetParams.copy(
notaries = notaryInfos,
val newNetParams = existingNetParams
.copy(notaries = notaryInfos, whitelistedContractImplementations = whitelist)
.overrideWith(networkParametersOverrides)
if (newNetParams != existingNetParams) {
newNetParams.copy(
modifiedTime = Instant.now(),
whitelistedContractImplementations = whitelist,
packageOwnership = updatePackageOwnership,
epoch = existingNetParams.epoch + 1
)
} else {
existingNetParams
}
} else {
NetworkParameters(
minimumPlatformVersion = minimumPlatformVersion,
notaries = notaryInfos,
modifiedTime = Instant.now(),
maxMessageSize = 10485760,
maxTransactionSize = 524288000,
whitelistedContractImplementations = whitelist,
packageOwnership = packageOwnership.filterNotNullValues(),
epoch = 1,
eventHorizon = 30.days
)
defaultNetworkParametersWith(notaryInfos, whitelist).overrideWith(networkParametersOverrides)
}
val copier = NetworkParametersCopier(netParams, overwriteFile = true)
nodeDirs.forEach(copier::install)
@ -435,7 +431,7 @@ internal constructor(private val initSerEnv: Boolean,
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
// cluster and is shared by all the other members. This is the notary identity.
2 -> legalIdentities[1]
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenario: $this")
}
}
@ -464,3 +460,27 @@ internal constructor(private val initSerEnv: Boolean,
}
}
}
fun NetworkParameters.overrideWith(override: NetworkParametersOverrides): NetworkParameters {
return this.copy(
minimumPlatformVersion = override.minimumPlatformVersion ?: this.minimumPlatformVersion,
maxMessageSize = override.maxMessageSize ?: this.maxMessageSize,
maxTransactionSize = override.maxTransactionSize ?: this.maxTransactionSize,
eventHorizon = override.eventHorizon ?: this.eventHorizon,
packageOwnership = override.packageOwnership?.map { it.javaPackageName to it.publicKey }?.toMap() ?: this.packageOwnership
)
}
data class PackageOwner(val javaPackageName: String, val publicKey: PublicKey)
data class NetworkParametersOverrides(
val minimumPlatformVersion: Int? = null,
val maxMessageSize: Int? = null,
val maxTransactionSize: Int? = null,
val packageOwnership: List<PackageOwner>? = null,
val eventHorizon: Duration? = null
)
interface NetworkBootstrapperWithOverridableParameters {
fun bootstrap(directory: Path, copyCordapps: Boolean, networkParameterOverrides: NetworkParametersOverrides = NetworkParametersOverrides())
}

View File

@ -11,9 +11,12 @@ import net.corda.core.serialization.serialize
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.core.internal.NODE_INFO_DIRECTORY
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_MESSAGE_SIZE
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_TRANSACTION_SIZE
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
import net.corda.testing.core.*
import net.corda.testing.internal.createNodeInfoAndSigned
@ -26,6 +29,7 @@ import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import java.nio.file.Path
import java.security.PublicKey
import java.time.Duration
import kotlin.streams.toList
class NetworkBootstrapperTest {
@ -210,6 +214,24 @@ class NetworkBootstrapperTest {
assertThat(networkParameters.epoch).isEqualTo(2)
}
@Test
fun `network parameters overrides`() {
createNodeConfFile("alice", aliceConfig)
val minimumPlatformVersion = 2
val maxMessageSize = 10000
val maxTransactionSize = 20000
val eventHorizon = 7.days
bootstrap(minimumPlatformVerison = minimumPlatformVersion,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
assertThat(networkParameters.minimumPlatformVersion).isEqualTo(minimumPlatformVersion)
assertThat(networkParameters.maxMessageSize).isEqualTo(maxMessageSize)
assertThat(networkParameters.maxTransactionSize).isEqualTo(maxTransactionSize)
assertThat(networkParameters.eventHorizon).isEqualTo(eventHorizon)
}
private val ALICE = TestIdentity(ALICE_NAME, 70)
private val BOB = TestIdentity(BOB_NAME, 80)
@ -230,7 +252,7 @@ class NetworkBootstrapperTest {
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
// register additional package name
createNodeConfFile("bob", bobConfig)
bootstrap(packageOwnership = mapOf(Pair(bobPackageName, BOB.publicKey)))
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
assertContainsPackageOwner("bob", mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
}
@ -243,8 +265,8 @@ class NetworkBootstrapperTest {
// register overlapping package name
createNodeConfFile("bob", bobConfig)
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("multiple packages added to the packageOwnership overlap.")
bootstrap(packageOwnership = mapOf(Pair(bobPackageName, BOB.publicKey)))
expectedEx.expectMessage("Multiple packages added to the packageOwnership overlap.")
bootstrap(packageOwnership = mapOf(Pair(greedyNamespace, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
}
@Test
@ -253,7 +275,7 @@ class NetworkBootstrapperTest {
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
// unregister package name
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null)))
bootstrap(packageOwnership = emptyMap())
assertContainsPackageOwner("alice", emptyMap())
}
@ -262,8 +284,8 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
// unregister package name
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null)))
assertContainsPackageOwner("alice", mapOf(Pair(bobPackageName, BOB.publicKey)))
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
}
@Test
@ -271,19 +293,10 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
// unregister all package names
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null), Pair(bobPackageName, null)))
bootstrap(packageOwnership = emptyMap())
assertContainsPackageOwner("alice", emptyMap())
}
@Test
fun `register and unregister sample package namespace in network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(alicePackageName, null)))
assertContainsPackageOwner("alice", emptyMap())
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, null), Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
}
private val rootDir get() = tempFolder.root.toPath()
private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
@ -292,9 +305,20 @@ class NetworkBootstrapperTest {
return bytes
}
private fun bootstrap(copyCordapps: Boolean = true, packageOwnership : Map<String, PublicKey?> = emptyMap()) {
private fun bootstrap(copyCordapps: Boolean = true,
packageOwnership: Map<String, PublicKey>? = emptyMap(),
minimumPlatformVerison: Int? = PLATFORM_VERSION,
maxMessageSize: Int? = DEFAULT_MAX_MESSAGE_SIZE,
maxTransactionSize: Int? = DEFAULT_MAX_TRANSACTION_SIZE,
eventHorizon: Duration? = 30.days) {
providedCordaJar = (rootDir / "corda.jar").let { if (it.exists()) it.readAll() else null }
bootstrapper.bootstrap(rootDir, copyCordapps, PLATFORM_VERSION, packageOwnership)
bootstrapper.bootstrap(rootDir, copyCordapps, NetworkParametersOverrides(
minimumPlatformVersion = minimumPlatformVerison,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon,
packageOwnership = packageOwnership?.map { PackageOwner(it.key, it.value!!) }
))
}
private fun createNodeConfFile(nodeDirName: String, config: FakeNodeConfig) {
@ -320,19 +344,23 @@ class NetworkBootstrapperTest {
return cordappBytes
}
private val Path.networkParameters: NetworkParameters get() {
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>().verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
}
private val Path.networkParameters: NetworkParameters
get() {
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>()
.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
}
private val Path.nodeInfoFile: Path get() {
return list { it.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.toList() }.single()
}
private val Path.nodeInfoFile: Path
get() {
return list { it.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.toList() }.single()
}
private val Path.nodeInfo: NodeInfo get() = nodeInfoFile.readObject<SignedNodeInfo>().verified()
private val Path.fakeNodeConfig: FakeNodeConfig get() {
return ConfigFactory.parseFile((this / "node.conf").toFile()).parseAs(FakeNodeConfig::class)
}
private val Path.fakeNodeConfig: FakeNodeConfig
get() {
return ConfigFactory.parseFile((this / "node.conf").toFile()).parseAs(FakeNodeConfig::class)
}
private fun assertBootstrappedNetwork(cordaJar: ByteArray, vararg nodes: Pair<String, FakeNodeConfig>): NetworkParameters {
val networkParameters = (rootDir / nodes[0].first).networkParameters

View File

@ -49,7 +49,6 @@ open class SharedNodeCmdLineOptions {
var devMode: Boolean? = null
open fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
val option = Configuration.Validation.Options(strict = unknownConfigKeysPolicy == UnknownConfigKeysPolicy.FAIL)
return configuration.parseAsNodeConfiguration(option)
}

View File

@ -143,21 +143,19 @@ open class NodeStartup : NodeStartupLogging {
val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional ?: return ExitCodes.FAILURE
// Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success
?: return ExitCodes.FAILURE
if (attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } !is Try.Success) return ExitCodes.FAILURE
// Step 7. Any actions required before starting up the Corda network layer.
attempt { preNetworkRegistration(configuration) }.doOnException(::handleRegistrationError) as? Try.Success
?: return ExitCodes.FAILURE
if (attempt { preNetworkRegistration(configuration) }.doOnException(::handleRegistrationError) !is Try.Success) return ExitCodes.FAILURE
// Step 8. Log startup info.
logStartupInfo(versionInfo, configuration)
// Step 9. Start node: create the node, check for other command-line options, add extra logging etc.
attempt {
cmdLineOptions.baseDirectory.createDirectories()
afterNodeInitialisation.run(createNode(configuration, versionInfo))
}.doOnException(::handleStartError) as? Try.Success ?: return ExitCodes.FAILURE
if (attempt {
cmdLineOptions.baseDirectory.createDirectories()
afterNodeInitialisation.run(createNode(configuration, versionInfo))
}.doOnException(::handleStartError) !is Try.Success) return ExitCodes.FAILURE
return ExitCodes.SUCCESS
}

View File

@ -109,7 +109,7 @@ class NetworkParametersTest {
"com.example.stuff" to key2
)
)
}.withMessage("multiple packages added to the packageOwnership overlap.")
}.withMessage("Multiple packages added to the packageOwnership overlap.")
val params = NetworkParameters(1,
emptyList(),

View File

@ -24,7 +24,7 @@ dependencies {
// Unit testing helpers.
compile "junit:junit:$junit_version"
compile 'org.hamcrest:hamcrest-library:1.3'
compile 'com.nhaarman:mockito-kotlin:1.5.0'
compile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version"
compile "org.mockito:mockito-core:$mockito_version"
compile "org.assertj:assertj-core:$assertj_version"
compile "com.natpryce:hamkrest:$hamkrest_version"

View File

@ -3,6 +3,7 @@ package net.corda.testing.core
import net.corda.core.internal.JarSignatureCollector
import net.corda.core.internal.div
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.testing.core.JarSignatureTestUtils.signJar
import java.io.FileInputStream
import java.nio.file.Path
import java.nio.file.Paths
@ -44,6 +45,11 @@ object JarSignatureTestUtils {
return ks.getCertificate(alias).publicKey
}
fun Path.getPublicKey(alias: String, storePassword: String) : PublicKey {
val ks = loadKeyStore(this.resolve("_teststore"), storePassword)
return ks.getCertificate(alias).publicKey
}
fun Path.getJarSigners(fileName: String) =
JarInputStream(FileInputStream((this / fileName).toFile())).use(JarSignatureCollector::collectSigners)
}

View File

@ -7,6 +7,7 @@ description 'Network bootstrapper'
dependencies {
compile project(':node-api')
compile project(':tools:cliutils')
compile project(':common-configuration-parsing')
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
testCompile(project(':test-utils')) {
@ -14,6 +15,8 @@ dependencies {
}
testCompile(project(':test-cli'))
testCompile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version"
testCompile "org.mockito:mockito-core:$mockito_version"
}
processResources {

View File

@ -1,109 +1,96 @@
package net.corda.bootstrapper
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.cliutils.CordaCliWrapper
import net.corda.cliutils.ExitCodes
import net.corda.cliutils.printError
import net.corda.cliutils.start
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.requirePackageValid
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.core.internal.exists
import net.corda.nodeapi.internal.network.NetworkBootstrapper
import picocli.CommandLine
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_MESSAGE_SIZE
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_TRANSACTION_SIZE
import net.corda.nodeapi.internal.network.NetworkBootstrapperWithOverridableParameters
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
import picocli.CommandLine.Option
import java.io.IOException
import java.io.FileNotFoundException
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyStoreException
import java.security.PublicKey
import java.time.Duration
fun main(args: Array<String>) {
NetworkBootstrapperRunner().start(args)
}
class NetworkBootstrapperRunner : CordaCliWrapper("bootstrapper", "Bootstrap a local test Corda network using a set of node configuration files and CorDapp JARs") {
@Option(
names = ["--dir"],
description = [
"Root directory containing the node configuration files and CorDapp JARs that will form the test network.",
"It may also contain existing node directories."
]
)
class NetworkBootstrapperRunner(private val bootstrapper: NetworkBootstrapperWithOverridableParameters = NetworkBootstrapper()) : CordaCliWrapper("bootstrapper", "Bootstrap a local test Corda network using a set of node configuration files and CorDapp JARs") {
@Option(names = ["--dir"],
description = [ "Root directory containing the node configuration files and CorDapp JARs that will form the test network.",
"It may also contain existing node directories."])
var dir: Path = Paths.get(".")
@Option(names = ["--no-copy"], description = ["""Don't copy the CorDapp JARs into the nodes' "cordapps" directories."""])
var noCopy: Boolean = false
@Option(names = ["--minimum-platform-version"], description = ["The minimumPlatformVersion to use in the network-parameters."])
var minimumPlatformVersion = PLATFORM_VERSION
@Option(names = ["--minimum-platform-version"], description = ["The minimum platform version to use in the network-parameters. Current default is $PLATFORM_VERSION."])
var minimumPlatformVersion: Int? = null
@Option(names = ["--register-package-owner"],
converter = [PackageOwnerConverter::class],
description = [
"Register owner of Java package namespace in the network-parameters.",
"Format: [java-package-namespace;keystore-file;password;alias]",
" `java-package-namespace` is case insensitive and cannot be a sub-package of an existing registered namespace",
" `keystore-file` refers to the location of key store file containing the signed certificate as generated by the Java 'keytool' tool (see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html)",
" `password` to open the key store",
" `alias` refers to the name associated with a certificate containing the public key to be associated with the package namespace"
])
var registerPackageOwnership: List<PackageOwner> = mutableListOf()
@Option(names = ["--max-message-size"], description = ["The maximum message size to use in the network-parameters, in bytes. Current default is $DEFAULT_MAX_MESSAGE_SIZE."])
var maxMessageSize: Int? = null
@Option(names = ["--unregister-package-owner"],
description = [
"Unregister owner of Java package namespace in the network-parameters.",
"Format: [java-package-namespace]",
" `java-package-namespace` is case insensitive and cannot be a sub-package of an existing registered namespace"
])
var unregisterPackageOwnership: List<String> = mutableListOf()
@Option(names = ["--max-transaction-size"], description = ["The maximum transaction size to use in the network-parameters, in bytes. Current default is $DEFAULT_MAX_TRANSACTION_SIZE."])
var maxTransactionSize: Int? = null
@Option(names = ["--event-horizon"], description = ["The event horizon to use in the network-parameters. Default is 30 days."])
var eventHorizon: Duration? = null
@Option(names = ["--network-parameter-overrides", "-n"], description = ["Overrides the default network parameters with those in the given file."])
var networkParametersFile: Path? = null
private fun verifyInputs() {
require(minimumPlatformVersion == null || minimumPlatformVersion ?: 0 > 0) { "The --minimum-platform-version parameter must be at least 1" }
require(eventHorizon == null || eventHorizon?.isNegative == false) { "The --event-horizon parameter must be a positive value" }
require(maxTransactionSize == null || maxTransactionSize ?: 0 > 0) { "The --max-transaction-size parameter must be at least 1" }
require(maxMessageSize == null || maxMessageSize ?: 0 > 0) { "The --max-message-size parameter must be at least 1" }
}
private fun commandLineOverrides(): Map<String, Any> {
val overrides = mutableMapOf<String, Any>()
overrides += minimumPlatformVersion?.let { mapOf("minimumPlatformVersion" to minimumPlatformVersion!!) } ?: mutableMapOf()
overrides += maxMessageSize?.let { mapOf("maxMessageSize" to maxMessageSize!!) } ?: emptyMap()
overrides += maxTransactionSize?.let { mapOf("maxTransactionSize" to maxTransactionSize!!) } ?: emptyMap()
overrides += eventHorizon?.let { mapOf("eventHorizon" to eventHorizon!!) } ?: emptyMap()
return overrides
}
private fun getNetworkParametersOverrides(): Valid<NetworkParametersOverrides> {
val parseOptions = ConfigParseOptions.defaults()
val config = if (networkParametersFile == null) {
ConfigFactory.empty()
} else {
if (networkParametersFile?.exists() != true) throw FileNotFoundException("Unable to find specified network parameters config file at $networkParametersFile")
ConfigFactory.parseFile(networkParametersFile!!.toFile(), parseOptions)
}
val finalConfig = ConfigFactory.parseMap(commandLineOverrides()).withFallback(config).resolve()
return finalConfig.parseAsNetworkParametersConfiguration()
}
private fun <T> Collection<T>.pluralise() = if (this.count() > 1) "s" else ""
private fun reportErrors(errors: Set<Configuration.Validation.Error>) {
printError("Error${errors.pluralise()} found parsing the network parameter overrides file at $networkParametersFile:")
errors.forEach { printError("Error parsing ${it.pathAsString}: ${it.message}") }
}
override fun runProgram(): Int {
NetworkBootstrapper().bootstrap(dir.toAbsolutePath().normalize(),
verifyInputs()
val networkParameterOverrides = getNetworkParametersOverrides().doOnErrors(::reportErrors).optional ?: return ExitCodes.FAILURE
bootstrapper.bootstrap(dir.toAbsolutePath().normalize(),
copyCordapps = !noCopy,
minimumPlatformVersion = minimumPlatformVersion,
packageOwnership = registerPackageOwnership.map { Pair(it.javaPackageName, it.publicKey) }.toMap()
.plus(unregisterPackageOwnership.map { Pair(it, null) })
networkParameterOverrides = networkParameterOverrides
)
return 0 //exit code
}
}
data class PackageOwner(val javaPackageName: String, val publicKey: PublicKey)
/**
* Converter from String to PackageOwner (String and PublicKey)
*/
class PackageOwnerConverter : CommandLine.ITypeConverter<PackageOwner> {
override fun convert(packageOwner: String): PackageOwner {
if (!packageOwner.isBlank()) {
val packageOwnerSpec = packageOwner.split(";")
if (packageOwnerSpec.size < 4)
throw IllegalArgumentException("Package owner must specify 4 elements separated by semi-colon: 'java-package-namespace;keyStorePath;keyStorePassword;alias'")
// java package name validation
val javaPackageName = packageOwnerSpec[0]
requirePackageValid(javaPackageName)
// cater for passwords that include the argument delimiter field
val keyStorePassword =
if (packageOwnerSpec.size > 4)
packageOwnerSpec.subList(2, packageOwnerSpec.size-1).joinToString(";")
else packageOwnerSpec[2]
try {
val ks = loadKeyStore(Paths.get(packageOwnerSpec[1]), keyStorePassword)
try {
val publicKey = ks.getCertificate(packageOwnerSpec[packageOwnerSpec.size-1]).publicKey
return PackageOwner(javaPackageName,publicKey)
}
catch(kse: KeyStoreException) {
throw IllegalArgumentException("Keystore has not been initialized for alias ${packageOwnerSpec[3]}")
}
}
catch(kse: KeyStoreException) {
throw IllegalArgumentException("Password is incorrect or the key store is damaged for keyStoreFilePath: ${packageOwnerSpec[1]} and keyStorePassword: $keyStorePassword")
}
catch(e: IOException) {
throw IllegalArgumentException("Error reading the key store from the file for keyStoreFilePath: ${packageOwnerSpec[1]} and keyStorePassword: $keyStorePassword")
}
}
else throw IllegalArgumentException("Must specify package owner argument: 'java-package-namespace;keyStorePath;keyStorePassword;alias'")
return ExitCodes.SUCCESS
}
}

View File

@ -0,0 +1,103 @@
package net.corda.bootstrapper
import com.typesafe.config.Config
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.configuration.parsing.internal.get
import net.corda.common.configuration.parsing.internal.mapValid
import net.corda.common.configuration.parsing.internal.nested
import net.corda.common.validation.internal.Validated
import net.corda.core.internal.div
import net.corda.core.internal.requirePackageValid
import net.corda.core.node.NetworkParameters
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
import net.corda.nodeapi.internal.network.PackageOwner
import java.io.IOException
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyStoreException
internal typealias Valid<TARGET> = Validated<TARGET, Configuration.Validation.Error>
fun Config.parseAsNetworkParametersConfiguration(options: Configuration.Validation.Options = Configuration.Validation.Options(strict = false)):
Valid<NetworkParametersOverrides> = NetworkParameterOverridesSpec.parse(this, options)
internal fun <T> badValue(msg: String): Valid<T> = Validated.invalid(sequenceOf(Configuration.Validation.Error.BadValue.of(msg)).toSet())
internal fun <T> valid(value: T): Valid<T> = Validated.valid(value)
internal object NetworkParameterOverridesSpec : Configuration.Specification<NetworkParametersOverrides>("DefaultNetworkParameters") {
private val minimumPlatformVersion by int().mapValid(::parsePositiveInteger).optional()
private val maxMessageSize by int().mapValid(::parsePositiveInteger).optional()
private val maxTransactionSize by int().mapValid(::parsePositiveInteger).optional()
private val packageOwnership by nested(PackageOwnershipSpec).list().optional()
private val eventHorizon by duration().optional()
internal object PackageOwnershipSpec : Configuration.Specification<PackageOwner>("PackageOwners") {
private val packageName by string().mapValid(::toPackageName)
private val keystore by string().mapValid(::toPath)
private val keystorePassword by string()
private val keystoreAlias by string()
override fun parseValid(configuration: Config): Validated<PackageOwner, Configuration.Validation.Error> {
val suppliedKeystorePath = configuration[keystore]
val keystorePassword = configuration[keystorePassword]
return try {
val javaPackageName = configuration[packageName]
val absoluteKeystorePath = if (suppliedKeystorePath.isAbsolute) {
suppliedKeystorePath
} else {
//If a relative path is supplied, make it relative to the location of the config file
Paths.get(configuration.origin().filename()).resolveSibling(suppliedKeystorePath.toString())
}.toAbsolutePath()
val ks = loadKeyStore(absoluteKeystorePath, keystorePassword)
return try {
val publicKey = ks.getCertificate(configuration[keystoreAlias]).publicKey
valid(PackageOwner(javaPackageName, publicKey))
} catch (kse: KeyStoreException) {
badValue("Keystore has not been initialized for alias ${configuration[keystoreAlias]}")
}
} catch (kse: KeyStoreException) {
badValue("Password is incorrect or the key store is damaged for keyStoreFilePath: $suppliedKeystorePath and keyStorePassword: $keystorePassword")
} catch (e: IOException) {
badValue("Error reading the key store from the file for keyStoreFilePath: $suppliedKeystorePath and keyStorePassword: $keystorePassword ${e.message}")
}
}
private fun toPackageName(rawValue: String): Validated<String, Configuration.Validation.Error> {
return try {
requirePackageValid(rawValue)
valid(rawValue)
} catch (e: Exception) {
return badValue(e.message ?: e.toString())
}
}
private fun toPath(rawValue: String): Validated<Path, Configuration.Validation.Error> {
return try {
valid(Paths.get(rawValue))
} catch (e: InvalidPathException) {
return badValue("Path $rawValue not found")
}
}
}
override fun parseValid(configuration: Config): Valid<NetworkParametersOverrides> {
val packageOwnership = configuration[packageOwnership]
if (packageOwnership != null && !NetworkParameters.noOverlap(packageOwnership.map { it.javaPackageName })) {
return Validated.invalid(sequenceOf(Configuration.Validation.Error.BadValue.of("Package namespaces must not overlap", keyName = "packageOwnership", containingPath = listOf())).toSet())
}
return valid(NetworkParametersOverrides(
minimumPlatformVersion = configuration[minimumPlatformVersion],
maxMessageSize = configuration[maxMessageSize],
maxTransactionSize = configuration[maxTransactionSize],
packageOwnership = packageOwnership,
eventHorizon = configuration[eventHorizon]
))
}
private fun parsePositiveInteger(rawValue: Int): Valid<Int> {
if (rawValue > 0) return valid(rawValue)
return badValue("The value must be at least 1")
}
}

View File

@ -0,0 +1,5 @@
package net.corda.bootstrapper
import net.corda.testing.CliBackwardsCompatibleTest
class NetworkBootstrapperBackwardsCompatibilityTest : CliBackwardsCompatibleTest(NetworkBootstrapperRunner::class.java)

View File

@ -1,5 +0,0 @@
package net.corda.bootstrapper
import net.corda.testing.CliBackwardsCompatibleTest
class NetworkBootstrapperRunnerTest : CliBackwardsCompatibleTest(NetworkBootstrapperRunner::class.java)

View File

@ -0,0 +1,255 @@
package net.corda.bootstrapper
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import net.corda.core.internal.copyTo
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.network.NetworkBootstrapperWithOverridableParameters
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
import net.corda.nodeapi.internal.network.PackageOwner
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.JarSignatureTestUtils.generateKey
import net.corda.testing.core.JarSignatureTestUtils.getPublicKey
import org.junit.*
import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException
import java.io.PrintStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.PublicKey
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class NetworkBootstrapperRunnerTests {
private val outContent = ByteArrayOutputStream()
private val errContent = ByteArrayOutputStream()
private val originalOut = System.out
private val originalErr = System.err
@Before
fun setUpStreams() {
System.setOut(PrintStream(outContent))
System.setErr(PrintStream(errContent))
}
@After
fun restoreStreams() {
System.setOut(originalOut)
System.setErr(originalErr)
}
companion object {
private const val ALICE = "alice"
private const val ALICE_PASS = "alicepass"
private const val aliceConfigFile = "alice-network.conf"
private const val correctNetworkFile = "correct-network.conf"
private const val packageOverlapConfigFile = "package-overlap.conf"
private val dirAlice = Files.createTempDirectory(ALICE)
private val dirAliceEC = Files.createTempDirectory("sdfsdfds")
private val dirAliceDSA = Files.createTempDirectory(ALICE)
private lateinit var alicePublicKey: PublicKey
private lateinit var alicePublicKeyEC: PublicKey
private lateinit var alicePublicKeyDSA: PublicKey
private val resourceDirectory = Paths.get(".") / "src" / "test" / "resources"
private fun String.copyToTestDir(dir: Path = dirAlice): Path {
return (resourceDirectory / this).copyTo(dir / this)
}
@BeforeClass
@JvmStatic
fun beforeClass() {
dirAlice.generateKey(ALICE, ALICE_PASS, ALICE_NAME.toString())
dirAliceEC.generateKey(ALICE, ALICE_PASS, ALICE_NAME.toString(), "EC")
dirAliceDSA.generateKey(ALICE, ALICE_PASS, ALICE_NAME.toString(), "DSA")
alicePublicKey = dirAlice.getPublicKey(ALICE, ALICE_PASS)
alicePublicKeyEC = dirAliceEC.getPublicKey(ALICE, ALICE_PASS)
alicePublicKeyDSA = dirAliceDSA.getPublicKey(ALICE, ALICE_PASS)
}
@AfterClass
@JvmStatic
fun afterClass() {
dirAlice.deleteRecursively()
}
}
private fun getRunner(): Pair<NetworkBootstrapperRunner, NetworkBootstrapperWithOverridableParameters> {
val mockBootstrapper = mock<NetworkBootstrapperWithOverridableParameters>()
return Pair(NetworkBootstrapperRunner(mockBootstrapper), mockBootstrapper)
}
@Test
fun `test when defaults are run bootstrapper is called correctly`() {
val (runner, mockBootstrapper) = getRunner()
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides())
assertEquals(0, exitCode)
}
@Test
fun `test when base directory is specified it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
val tempDir = createTempDir()
runner.dir = tempDir.toPath()
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(tempDir.toPath().toAbsolutePath().normalize(), true, NetworkParametersOverrides())
assertEquals(0, exitCode)
}
@Test
fun `test when copy cordapps is specified it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
runner.noCopy = true
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), false, NetworkParametersOverrides())
assertEquals(0, exitCode)
}
@Test
fun `test when min platform version is specified it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
runner.minimumPlatformVersion = 1
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(minimumPlatformVersion = 1))
assertEquals(0, exitCode)
}
@Test
fun `test when min platform version is invalid it fails to run with a sensible error message`() {
val runner = getRunner().first
runner.minimumPlatformVersion = 0
val exception = assertFailsWith<IllegalArgumentException> { runner.runProgram() }
assertEquals("The --minimum-platform-version parameter must be at least 1", exception.message)
}
@Test
fun `test when max message size is specified it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
runner.maxMessageSize = 1
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(maxMessageSize = 1))
assertEquals(0, exitCode)
}
@Test
fun `test when max message size is invalid it fails to run with a sensible error message`() {
val runner = getRunner().first
runner.maxMessageSize = 0
val exception = assertFailsWith<IllegalArgumentException> { runner.runProgram() }
assertEquals("The --max-message-size parameter must be at least 1", exception.message)
}
@Test
fun `test when max transaction size is specified it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
runner.maxTransactionSize = 1
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(maxTransactionSize = 1))
assertEquals(0, exitCode)
}
@Test
fun `test when max transaction size is invalid it fails to run with a sensible error message`() {
val runner = getRunner().first
runner.maxTransactionSize = 0
val exception = assertFailsWith<IllegalArgumentException> { runner.runProgram() }
assertEquals("The --max-transaction-size parameter must be at least 1", exception.message)
}
@Test
fun `test when event horizon is specified it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
runner.eventHorizon = 7.days
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(eventHorizon = 7.days))
assertEquals(0, exitCode)
}
@Test
fun `test when event horizon is invalid it fails to run with a sensible error message`() {
val runner = getRunner().first
runner.eventHorizon = (-7).days
val exception = assertFailsWith<IllegalArgumentException> { runner.runProgram() }
assertEquals("The --event-horizon parameter must be a positive value", exception.message)
}
@Test
fun `test when a network parameters is specified the values are passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
val conf = correctNetworkFile.copyToTestDir()
runner.networkParametersFile = conf
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(
maxMessageSize = 10000,
maxTransactionSize = 2000,
eventHorizon = 5.days,
minimumPlatformVersion = 2
))
assertEquals(0, exitCode)
}
@Test
fun `test when a package is specified in the network parameters file it is passed through to the bootstrapper`() {
val (runner, mockBootstrapper) = getRunner()
val conf = aliceConfigFile.copyToTestDir()
runner.networkParametersFile = conf
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(
packageOwnership = listOf(PackageOwner("com.example.stuff", publicKey = alicePublicKey))
))
assertEquals(0, exitCode)
}
@Test
fun `test when a package is specified in the network parameters file it is passed through to the bootstrapper EC`() {
val (runner, mockBootstrapper) = getRunner()
val conf = aliceConfigFile.copyToTestDir(dirAliceEC)
runner.networkParametersFile = conf
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(
packageOwnership = listOf(PackageOwner("com.example.stuff", publicKey = alicePublicKeyEC))
))
assertEquals(0, exitCode)
}
@Test
fun `test when a package is specified in the network parameters file it is passed through to the bootstrapper DSA`() {
val (runner, mockBootstrapper) = getRunner()
val conf = aliceConfigFile.copyToTestDir(dirAliceDSA)
runner.networkParametersFile = conf
val exitCode = runner.runProgram()
verify(mockBootstrapper).bootstrap(Paths.get(".").toAbsolutePath().normalize(), true, NetworkParametersOverrides(
packageOwnership = listOf(PackageOwner("com.example.stuff", publicKey = alicePublicKeyDSA))
))
assertEquals(0, exitCode)
}
@Test
fun `test when packages overlap that the bootstrapper fails with a sensible message`() {
val (runner, mockBootstrapper) = getRunner()
val conf = packageOverlapConfigFile.copyToTestDir()
runner.networkParametersFile = conf
val exitCode = runner.runProgram()
val output = errContent.toString()
assert(output.contains("Error parsing packageOwnership: Package namespaces must not overlap"))
assertEquals(1, exitCode)
}
@Test
fun `test when keyfile does not exist then bootstrapper fails with a sensible message`() {
val (runner, mockBootstrapper) = getRunner()
runner.networkParametersFile = dirAlice / "filename-that-doesnt-exist"
val exception = assertFailsWith<FileNotFoundException> { runner.runProgram() }
assert(exception.message!!.startsWith("Unable to find specified network parameters config file at"))
}
}

View File

@ -1,164 +0,0 @@
package net.corda.bootstrapper
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.JarSignatureTestUtils.generateKey
import org.assertj.core.api.Assertions.assertThat
import org.junit.*
import org.junit.rules.ExpectedException
import picocli.CommandLine
import java.nio.file.Files
class PackageOwnerParsingTest {
@Rule
@JvmField
val expectedEx: ExpectedException = ExpectedException.none()
companion object {
private const val ALICE = "alice"
private const val ALICE_PASS = "alicepass"
private const val BOB = "bob"
private const val BOB_PASS = "bobpass"
private const val CHARLIE = "charlie"
private const val CHARLIE_PASS = "charliepass"
private val dirAlice = Files.createTempDirectory(ALICE)
private val dirBob = Files.createTempDirectory(BOB)
private val dirCharlie = Files.createTempDirectory(CHARLIE)
val networkBootstrapper = NetworkBootstrapperRunner()
val commandLine = CommandLine(networkBootstrapper)
@BeforeClass
@JvmStatic
fun beforeClass() {
dirAlice.generateKey(ALICE, ALICE_PASS, ALICE_NAME.toString())
dirBob.generateKey(BOB, BOB_PASS, BOB_NAME.toString(), "EC")
dirCharlie.generateKey(CHARLIE, CHARLIE_PASS, CHARLIE_NAME.toString(), "DSA")
}
@AfterClass
@JvmStatic
fun afterClass() {
dirAlice.deleteRecursively()
}
}
@Test
fun `parse registration request with single mapping`() {
val aliceKeyStorePath = dirAlice / "_teststore"
val args = arrayOf("--register-package-owner", "com.example.stuff;$aliceKeyStorePath;$ALICE_PASS;$ALICE")
commandLine.parse(*args)
assertThat(networkBootstrapper.registerPackageOwnership[0].javaPackageName).isEqualTo("com.example.stuff")
}
@Test
fun `parse registration request with invalid arguments`() {
val args = arrayOf("--register-package-owner", "com.!example.stuff")
expectedEx.expect(CommandLine.ParameterException::class.java)
expectedEx.expectMessage("Package owner must specify 4 elements separated by semi-colon")
commandLine.parse(*args)
}
@Test
fun `parse registration request with incorrect keystore specification`() {
val aliceKeyStorePath = dirAlice / "_teststore"
val args = arrayOf("--register-package-owner", "com.example.stuff;$aliceKeyStorePath$ALICE_PASS")
expectedEx.expect(CommandLine.ParameterException::class.java)
expectedEx.expectMessage("Package owner must specify 4 elements separated by semi-colon")
commandLine.parse(*args)
}
@Test
fun `parse registration request with invalid java package name`() {
val args = arrayOf("--register-package-owner", "com.!example.stuff;A;B;C")
expectedEx.expect(CommandLine.ParameterException::class.java)
expectedEx.expectMessage("Invalid Java package name")
commandLine.parse(*args)
}
@Test
fun `parse registration request with invalid keystore file`() {
val args = arrayOf("--register-package-owner", "com.example.stuff;NONSENSE;B;C")
expectedEx.expect(CommandLine.ParameterException::class.java)
expectedEx.expectMessage("Error reading the key store from the file")
commandLine.parse(*args)
}
@Test
fun `parse registration request with invalid keystore password`() {
val aliceKeyStorePath = dirAlice / "_teststore"
val args = arrayOf("--register-package-owner", "com.example.stuff;$aliceKeyStorePath;BAD_PASSWORD;$ALICE")
expectedEx.expect(CommandLine.ParameterException::class.java)
expectedEx.expectMessage("Error reading the key store from the file")
commandLine.parse(*args)
}
@Test
fun `parse registration request with invalid keystore alias`() {
val aliceKeyStorePath = dirAlice / "_teststore"
val args = arrayOf("--register-package-owner", "com.example.stuff;$aliceKeyStorePath;$ALICE_PASS;BAD_ALIAS")
expectedEx.expect(CommandLine.ParameterException::class.java)
expectedEx.expectMessage("must not be null")
commandLine.parse(*args)
}
@Test
fun `parse registration request with multiple arguments`() {
val aliceKeyStorePath = dirAlice / "_teststore"
val bobKeyStorePath = dirBob / "_teststore"
val charlieKeyStorePath = dirCharlie / "_teststore"
val args = arrayOf("--register-package-owner", "com.example.stuff;$aliceKeyStorePath;$ALICE_PASS;$ALICE",
"--register-package-owner", "com.example.more.stuff;$bobKeyStorePath;$BOB_PASS;$BOB",
"--register-package-owner", "com.example.even.more.stuff;$charlieKeyStorePath;$CHARLIE_PASS;$CHARLIE")
commandLine.parse(*args)
assertThat(networkBootstrapper.registerPackageOwnership).hasSize(3)
}
@Ignore("Ignoring this test as the delimiters don't work correctly, see CORDA-2191")
@Test
fun `parse registration request with delimiter inclusive passwords`() {
val aliceKeyStorePath1 = dirAlice / "_alicestore1"
dirAlice.generateKey("${ALICE}1", "passw;rd", ALICE_NAME.toString(), storeName = "_alicestore1")
val aliceKeyStorePath2 = dirAlice / "_alicestore2"
dirAlice.generateKey("${ALICE}2", "\"passw;rd\"", ALICE_NAME.toString(), storeName = "_alicestore2")
val aliceKeyStorePath3 = dirAlice / "_alicestore3"
dirAlice.generateKey("${ALICE}3", "passw;rd", ALICE_NAME.toString(), storeName = "_alicestore3")
val aliceKeyStorePath4 = dirAlice / "_alicestore4"
dirAlice.generateKey("${ALICE}4", "\'passw;rd\'", ALICE_NAME.toString(), storeName = "_alicestore4")
val aliceKeyStorePath5 = dirAlice / "_alicestore5"
dirAlice.generateKey("${ALICE}5", "\"\"passw;rd\"\"", ALICE_NAME.toString(), storeName = "_alicestore5")
val packageOwnerSpecs = listOf("net.something0;$aliceKeyStorePath1;passw;rd;${ALICE}1",
"net.something1;$aliceKeyStorePath2;\"passw;rd\";${ALICE}2",
"\"net.something2;$aliceKeyStorePath3;passw;rd;${ALICE}3\"",
"net.something3;$aliceKeyStorePath4;\'passw;rd\';${ALICE}4",
"net.something4;$aliceKeyStorePath5;\"\"passw;rd\"\";${ALICE}5")
packageOwnerSpecs.forEachIndexed { i, packageOwnerSpec ->
commandLine.parse(*arrayOf("--register-package-owner", packageOwnerSpec))
assertThat(networkBootstrapper.registerPackageOwnership[0].javaPackageName).isEqualTo("net.something$i")
}
}
@Test
fun `parse unregister request with single mapping`() {
val args = arrayOf("--unregister-package-owner", "com.example.stuff")
commandLine.parse(*args)
assertThat(networkBootstrapper.unregisterPackageOwnership).contains("com.example.stuff")
}
@Test
fun `parse mixed register and unregister request`() {
val aliceKeyStorePath = dirAlice / "_teststore"
val args = arrayOf("--register-package-owner", "com.example.stuff;$aliceKeyStorePath;$ALICE_PASS;$ALICE",
"--unregister-package-owner", "com.example.stuff2")
commandLine.parse(*args)
assertThat(networkBootstrapper.registerPackageOwnership.map { it.javaPackageName }).contains("com.example.stuff")
assertThat(networkBootstrapper.unregisterPackageOwnership).contains("com.example.stuff2")
}
}

View File

@ -0,0 +1,8 @@
packageOwnership=[
{
packageName="com.example.stuff"
keystore="_teststore"
keystorePassword="alicepass"
keystoreAlias="alice"
}
]

View File

@ -0,0 +1,4 @@
minimumPlatformVersion=2
maxMessageSize=10000
maxTransactionSize=2000
eventHorizon="5 days"

View File

@ -0,0 +1,14 @@
packageOwnership=[
{
packageName="com.example"
keystore="_teststore"
keystorePassword="alicepass"
keystoreAlias="alice"
}
{
packageName="com.example.overlap"
keystore="_teststore"
keystorePassword="alicepass"
keystoreAlias="alice"
}
]

View File

@ -53,6 +53,7 @@ object CordaSystemUtils {
object ShellConstants {
const val RED = "\u001B[31m"
const val YELLOW = "\u001B[33m"
const val RESET = "\u001B[0m"
}
@ -86,8 +87,8 @@ fun CordaCliWrapper.start(args: Array<String>) {
if (this.verbose || this.subCommands().any { it.verbose }) {
throwable.printStackTrace()
} else {
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
}
printError(throwable.rootMessage ?: "Use --verbose for more details")
exitProcess(ExitCodes.FAILURE)
}
}
@ -185,11 +186,12 @@ abstract class CordaCliWrapper(alias: String, description: String) : CliWrapperB
fun printHelp() = cmd.usage(System.out)
fun printlnErr(message: String) = System.err.println(message)
fun printlnWarn(message: String) = System.err.println(message)
}
fun printWarning(message: String) = System.err.println("${ShellConstants.YELLOW}$message${ShellConstants.RESET}")
fun printError(message: String) = System.err.println("${ShellConstants.RED}$message${ShellConstants.RESET}")
/**
* Useful commonly used constants applicable to many CLI tools
*/

View File

@ -94,7 +94,7 @@ private class ShellExtensionsGenerator(val parent: CordaCliWrapper) {
val semanticParts = declaredBashVersion().split(".")
semanticParts.firstOrNull()?.toIntOrNull()?.let { major ->
if (major < minSupportedBashVersion) {
parent.printlnWarn("Cannot install shell extension for bash major version earlier than $minSupportedBashVersion. Please upgrade your bash version. Aliases should still work.")
printWarning("Cannot install shell extension for bash major version earlier than $minSupportedBashVersion. Please upgrade your bash version. Aliases should still work.")
generateAutoCompleteFile = false
}
}