mirror of
https://github.com/corda/corda.git
synced 2025-03-14 16:26:36 +00:00
Merge remote-tracking branch 'open/master' into os-merge-b97af47
# Conflicts: # .idea/compiler.xml # docs/source/changelog.rst # docs/source/release-notes.rst # node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt # tools/bootstrapper/build.gradle
This commit is contained in:
commit
98cd8be160
@ -98,6 +98,7 @@ buildscript {
|
||||
ext.snappy_version = '0.4'
|
||||
ext.fast_classpath_scanner_version = '2.12.3'
|
||||
ext.jcabi_manifests_version = '1.1'
|
||||
ext.picocli_version = '3.0.0'
|
||||
|
||||
ext.deterministic_rt_version = '1.0-SNAPSHOT'
|
||||
// Name of the IntelliJ SDK created for the deterministic Java rt.jar.
|
||||
|
@ -50,7 +50,7 @@ class AMQPClientSerializationScheme(
|
||||
target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
|
||||
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader()).apply {
|
||||
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply {
|
||||
register(RpcClientObservableSerializer)
|
||||
register(RpcClientCordaFutureSerializer(this))
|
||||
register(RxNotificationSerializer(this))
|
||||
|
@ -71,12 +71,18 @@ data class NotarisationPayload(val transaction: Any, val requestSignature: Notar
|
||||
* A helper for automatically casting the underlying [transaction] payload to a [SignedTransaction].
|
||||
* Should only be used by validating notaries.
|
||||
*/
|
||||
val signedTransaction get() = transaction as SignedTransaction
|
||||
val signedTransaction get() = transaction as? SignedTransaction ?: throw exception()
|
||||
|
||||
/**
|
||||
* A helper for automatically casting the underlying [transaction] payload to a [CoreTransaction].
|
||||
* Should only be used by non-validating notaries.
|
||||
*/
|
||||
val coreTransaction get() = transaction as CoreTransaction
|
||||
val coreTransaction get() = transaction as? CoreTransaction ?: throw exception()
|
||||
|
||||
private fun exception() = IllegalArgumentException("Unexpected transaction type in the notarisation payload: " +
|
||||
"${transaction::class.java}, it may be that there is a discrepancy between the configured notary type " +
|
||||
"(validating/non-validating) and the one advertised on the network parameters."
|
||||
)
|
||||
}
|
||||
|
||||
/** Payload returned by the notary service flow to the client. */
|
||||
|
@ -24,6 +24,7 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -396,12 +397,19 @@ fun <T, U : T> uncheckedCast(obj: T) = obj as U
|
||||
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
|
||||
|
||||
/** Provide access to internal method for AttachmentClassLoaderTests */
|
||||
@DeleteForDJVM fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
|
||||
@DeleteForDJVM
|
||||
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
|
||||
return toWireTransactionWithContext(services, serializationContext)
|
||||
}
|
||||
|
||||
/** Provide access to internal method for AttachmentClassLoaderTests */
|
||||
@DeleteForDJVM fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
|
||||
@DeleteForDJVM
|
||||
fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
|
||||
return toLedgerTransactionWithContext(services, serializationContext)
|
||||
}
|
||||
|
||||
/** Returns the location of this class. */
|
||||
val Class<*>.location: URL get() = protectionDomain.codeSource.location
|
||||
|
||||
/** Convenience method to get the package name of a class literal. */
|
||||
val KClass<*>.packageName: String get() = java.packageName
|
||||
|
@ -81,6 +81,9 @@ fun Path.lastModifiedTime(vararg options: LinkOption): FileTime = Files.getLastM
|
||||
/** @see Files.isDirectory */
|
||||
fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
|
||||
|
||||
/** @see Files.isSameFile */
|
||||
fun Path.isSameAs(other: Path): Boolean = Files.isSameFile(this, other)
|
||||
|
||||
/**
|
||||
* Same as [Files.list] except it also closes the [Stream].
|
||||
* @return the output of [block]
|
||||
|
@ -152,6 +152,14 @@ interface SerializationContext {
|
||||
* otherwise they appear as new copies of the object.
|
||||
*/
|
||||
val objectReferencesEnabled: Boolean
|
||||
/**
|
||||
* If true the carpenter will happily synthesis classes that implement interfaces containing methods that are not
|
||||
* getters for any AMQP fields. Invoking these methods will throw an [AbstractMethodError]. If false then an exception
|
||||
* will be thrown during deserialization instead.
|
||||
*
|
||||
* The default is false.
|
||||
*/
|
||||
val lenientCarpenterEnabled: Boolean
|
||||
/**
|
||||
* The use case we are serializing or deserializing for. See [UseCase].
|
||||
*/
|
||||
@ -167,6 +175,12 @@ interface SerializationContext {
|
||||
*/
|
||||
fun withoutReferences(): SerializationContext
|
||||
|
||||
/**
|
||||
* Return a new context based on this one but with a lenient carpenter.
|
||||
* @see lenientCarpenterEnabled
|
||||
*/
|
||||
fun withLenientCarpenter(): SerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the deserialization class loader changed.
|
||||
*/
|
||||
|
@ -9,6 +9,10 @@ Unreleased
|
||||
|
||||
* Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code.
|
||||
|
||||
* The class carpenter has a "lenient" mode where it will, during deserialisation, happily synthesis classes that implement
|
||||
interfaces that will have unimplemented methods. This is useful, for example, for object viewers. This can be turned on
|
||||
with ``SerializationContext.withLenientCarpenter``.
|
||||
|
||||
* Introduced a grace period before the initial node registration fails if the node cannot connect to the Doorman.
|
||||
It retries 10 times with a 1 minute interval in between each try. At the moment this is not configurable.
|
||||
|
||||
@ -273,7 +277,7 @@ Corda Enterprise 3.0 Developer Preview
|
||||
* Cordform (which is the ``deployNodes`` gradle task) does this copying automatically for the demos. The ``NetworkMap``
|
||||
parameter is no longer needed.
|
||||
|
||||
* For test deployments we've introduced a bootstrapping tool (see :doc:`setting-up-a-corda-network`).
|
||||
* For test deployments we've introduced a bootstrapping tool (see :doc:`network-bootstrapper`).
|
||||
|
||||
* ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` configs have been
|
||||
removed. The configuration of notaries has been simplified into a single ``notary`` config object. See
|
||||
|
@ -238,6 +238,14 @@ latex_documents = [('index', u'corda-developer-site.tex', u'Corda Developer Docu
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
_PREAMBLE = r"""
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{pmboxdraw}
|
||||
\DeclareUnicodeCharacter{2514}{\textSFii}
|
||||
\DeclareUnicodeCharacter{251C}{\textSFviii}
|
||||
\DeclareUnicodeCharacter{2705}{\checkmark}
|
||||
"""
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
@ -251,7 +259,9 @@ latex_elements = {
|
||||
# Latex figure (float) alignment
|
||||
# 'figure_align': 'htbp',
|
||||
|
||||
'maxlistdepth': 2000,
|
||||
'maxlistdepth' : '2000',
|
||||
|
||||
'extraclassoptions': 'openany',
|
||||
'preamble' : _PREAMBLE,
|
||||
|
||||
'extraclassoptions' : 'openany',
|
||||
}
|
||||
|
101
docs/source/network-bootstrapper.rst
Normal file
101
docs/source/network-bootstrapper.rst
Normal file
@ -0,0 +1,101 @@
|
||||
Network Bootstrapper
|
||||
====================
|
||||
|
||||
Test deployments
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Nodes within a network see each other using the network map. This is a collection of statically signed node-info files,
|
||||
one for each node. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
|
||||
|
||||
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
|
||||
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
|
||||
in its local network map cache. The node generates its own node-info file on startup.
|
||||
|
||||
In addition to the network map, all the nodes must also use the same set of network parameters. These are a set of constants
|
||||
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
|
||||
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.
|
||||
|
||||
You can find out more about network maps and network parameters from :doc:`network-map`.
|
||||
|
||||
Bootstrapping a test network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The bootstrapper can be downloaded from https://downloads.corda.net/network-bootstrapper-VERSION.jar, where ``VERSION``
|
||||
is the Corda version.
|
||||
|
||||
Create a directory containing a node config file, ending in "_node.conf", for each node you want to create. Then run the
|
||||
following command:
|
||||
|
||||
``java -jar network-bootstrapper-VERSION.jar --dir <nodes-root-dir>``
|
||||
|
||||
For example running the command on a directory containing these files:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary_node.conf // The notary's node.conf file
|
||||
├── partya_node.conf // Party A's node.conf file
|
||||
└── partyb_node.conf // Party B's node.conf file
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary
|
||||
│ └── node.conf
|
||||
├── partya
|
||||
│ └── node.conf
|
||||
└── partyb
|
||||
└── node.conf
|
||||
|
||||
Similarly, each node directory may contain its own ``corda.jar``, which the bootstrapper will use instead.
|
||||
|
||||
Synchronisation
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This tool only bootstraps a network. It cannot dynamically update if a new node needs to join the network or if an existing
|
||||
one has changed something in their node-info, e.g. their P2P address. For this the new node-info file will need to be placed
|
||||
in the other nodes' ``additional-node-infos`` directory. A simple way to do this is to use `rsync <https://en.wikipedia.org/wiki/Rsync>`_.
|
||||
However, if it's known beforehand the set of nodes that will eventually form part of the network then all the node directories
|
||||
can be pregenerated 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
|
||||
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.
|
||||
|
||||
Whitelisting contracts
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The CorDapp JARs are also automatically used to create the *Zone whitelist* (see :doc:`api-contract-constraints`) for
|
||||
the network.
|
||||
|
||||
.. note:: If you only wish to whitelist the CorDapps but not copy them to each node then run with the ``--no-copy`` flag.
|
||||
|
||||
The CorDapp JARs will be hashed and scanned for ``Contract`` classes. These contract class implementations will become part
|
||||
of the whitelisted contracts in the network parameters (see ``NetworkParameters.whitelistedContractImplementations`` :doc:`network-map`).
|
||||
If the network already has a set of network parameters defined (i.e. the node directories all contain the same network-parameters
|
||||
file) then the new set of contracts will be appended to the current whitelist.
|
||||
|
||||
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
|
||||
|
||||
By default the bootstrapper will whitelist all the contracts found in all the CorDapp JARs. To prevent certain
|
||||
contracts from being whitelisted, add their fully qualified class name in the ``exclude_whitelist.txt``. These will instead
|
||||
use the more restrictive ``HashAttachmentConstraint``.
|
||||
|
||||
For example:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
net.corda.finance.contracts.asset.Cash
|
||||
net.corda.finance.contracts.asset.CommercialPaper
|
@ -72,7 +72,7 @@ the network, along with the network parameters file and identity certificates. G
|
||||
online at once - an offline node that isn't being interacted with doesn't impact the network in any way. So a test
|
||||
cluster generated like this can be sized for the maximum size you may need, and then scaled up and down as necessary.
|
||||
|
||||
More information can be found in :doc:`setting-up-a-corda-network`.
|
||||
More information can be found in :doc:`network-bootstrapper`.
|
||||
|
||||
Network parameters
|
||||
------------------
|
||||
|
@ -564,6 +564,11 @@ without the supporting classes being present on the classpath. This can be usef
|
||||
be able to use reflection over the deserialized data, for scripting languages that run on the JVM, and also for
|
||||
ensuring classes not on the classpath can be deserialized without loading potentially malicious code.
|
||||
|
||||
If the original class implements some interfaces then the carpenter will make sure that all of the interface methods are
|
||||
backed by feilds. If that's not the case then an exception will be thrown during deserialization. This check can
|
||||
be turned off with ``SerializationContext.withLenientCarpenter``. This can be useful if only the field getters are needed,
|
||||
say in an object viewer.
|
||||
|
||||
Possible future enhancements include:
|
||||
|
||||
#. Java singleton support. We will add support for identifying classes which are singletons and identifying the
|
||||
|
@ -46,81 +46,14 @@ The most important fields regarding network configuration are:
|
||||
and ``rpcAddress`` if they are on the same machine.
|
||||
* ``notary.serviceLegalName``: The name of the notary service, required to setup distributed notaries with the network-bootstrapper.
|
||||
|
||||
Bootstrapping the network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The nodes see each other using the network map. This is a collection of statically signed node-info files, one for each
|
||||
node in the network. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
|
||||
|
||||
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
|
||||
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
|
||||
in its local network map cache. The node generates its own node-info file on startup.
|
||||
|
||||
In addition to the network map, all the nodes on a network must use the same set of network parameters. These are a set
|
||||
of constants which guarantee interoperability between nodes. The HTTP network map distributes the network parameters
|
||||
which the node downloads automatically. In the absence of this the network parameters must be generated locally. This can
|
||||
be done with the network bootstrapper. This is a tool that scans all the node configurations from a common directory to
|
||||
generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file
|
||||
to every other node so that they can all transact with each other.
|
||||
|
||||
The bootstrapper tool can be downloaded from https://downloads.corda.net/network-bootstrapper-corda-X.Y.jar, where ``X``
|
||||
is the major Corda version and ``Y`` is the minor Corda version.
|
||||
|
||||
To use it, create a directory containing a node config file, ending in "_node.conf", for each node you want to create.
|
||||
Then run the following command:
|
||||
|
||||
``java -jar network-bootstrapper-corda-X.Y.jar <nodes-root-dir>``
|
||||
|
||||
For example running the command on a directory containing these files :
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── notary_node.conf // The notary's node.conf file
|
||||
├── partya_node.conf // Party A's node.conf file
|
||||
└── partyb_node.conf // Party B's node.conf file
|
||||
|
||||
Would generate directories containing three nodes: notary, partya and partyb.
|
||||
|
||||
This tool only bootstraps a network. It cannot dynamically update if a new node needs to join the network or if an existing
|
||||
one has changed something in their node-info, e.g. their P2P address. For this the new node-info file will need to be placed
|
||||
in the other nodes' ``additional-node-infos`` directory. A simple way to do this is to use `rsync <https://en.wikipedia.org/wiki/Rsync>`_.
|
||||
However, if it's known beforehand the set of nodes that will eventually the node folders can be pregenerated in the bootstrap
|
||||
and only started when needed.
|
||||
|
||||
Whitelisting Contracts
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to create a *Zone whitelist* (see :doc:`api-contract-constraints`), you can pass in a list of CorDapp jars:
|
||||
|
||||
``java -jar network-bootstrapper.jar <nodes-root-dir> <1st CorDapp jar> <2nd CorDapp jar> ..``
|
||||
|
||||
The CorDapp jars will be hashed and scanned for ``Contract`` classes. These contract class implementations will become part
|
||||
of the whitelisted contracts in the network parameters (see ``NetworkParameters.whitelistedContractImplementations`` :doc:`network-map`).
|
||||
If the network already has a set of network parameters defined (i.e. the node directories all contain the same network-parameters
|
||||
file) then the new set of contracts will be appended to the current whitelist.
|
||||
|
||||
.. note:: The whitelist can only ever be appended to. Once added a contract implementation can never be removed.
|
||||
|
||||
By default the bootstrapper tool 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
|
||||
use the more restrictive ``HashAttachmentConstraint``.
|
||||
|
||||
For example:
|
||||
|
||||
.. sourcecode:: none
|
||||
|
||||
net.corda.finance.contracts.asset.Cash
|
||||
net.corda.finance.contracts.asset.CommercialPaper
|
||||
|
||||
In addition to using the CorDapp jars to update the whitelist, the bootstrapper will also copy them to all the nodes'
|
||||
``cordapps`` directory.
|
||||
|
||||
Starting the nodes
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You may now start the nodes in any order. You should see a banner, some log lines and eventually ``Node started up and registered``,
|
||||
indicating that the node is fully started.
|
||||
You will first need to create the local network by bootstrapping it with the bootstrapper. Details of how to do that can
|
||||
be found in :doc:`network-bootstrapper`.
|
||||
|
||||
Once that's done you may now start the nodes in any order. You should see a banner, some log lines and eventually
|
||||
``Node started up and registered``, indicating that the node is fully started.
|
||||
|
||||
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.
|
||||
|
||||
|
@ -4,6 +4,7 @@ Tools
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
network-bootstrapper
|
||||
blob-inspector
|
||||
demobench
|
||||
node-explorer
|
||||
|
@ -418,7 +418,7 @@ be moved to another machine open its config file and change the Artemis messagin
|
||||
where the node will run (e.g. ``p2pAddress="10.18.0.166:10007"``).
|
||||
|
||||
These changes require new node-info files to be distributed amongst the nodes. Use the network bootstrapper tool
|
||||
(see :doc:`setting-up-a-corda-network` for more information on this and how to built it) to update the files and have
|
||||
(see :doc:`network-bootstrapper` for more information on this and how to built it) to update the files and have
|
||||
them distributed locally.
|
||||
|
||||
``java -jar network-bootstrapper.jar kotlin-source/build/nodes``
|
||||
|
@ -135,7 +135,7 @@ With the re-designed network map service the following changes need to be made:
|
||||
* The network map is no longer provided by a node and thus the ``networkMapService`` config is ignored. Instead the
|
||||
network map is either provided by the compatibility zone (CZ) operator (who operates the doorman) and available
|
||||
using the ``compatibilityZoneURL`` config, or is provided using signed node info files which are copied locally.
|
||||
See :doc:`network-map` for more details, and :doc:`setting-up-a-corda-network` on how to use the network
|
||||
See :doc:`network-map` for more details, and :doc:`network-bootstrapper` on how to use the network
|
||||
bootstrapper for deploying a local network.
|
||||
|
||||
* Configuration for a notary has been simplified. ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses``
|
||||
|
@ -50,7 +50,7 @@ curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgr
|
||||
curl -L "https://github.com/Microsoft/mssql-jdbc/releases/download/v6.2.2/mssql-jdbc-6.2.2.jre8.jar" > ${DRIVERS_DIR}/mssql-jdbc-6.2.2.jre8.jar
|
||||
|
||||
# Build Network Bootstrapper
|
||||
./gradlew buildBootstrapperJar
|
||||
./gradlew tools:bootstrapper:jar
|
||||
cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) ${CORDA_DIR}/network-bootstrapper.jar
|
||||
|
||||
# TODO: resolve Doorman/NMS artifacts from new artifactory location.
|
||||
|
@ -37,8 +37,8 @@ import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -53,7 +53,21 @@ import kotlin.streams.toList
|
||||
/**
|
||||
* Class to bootstrap a local network of Corda nodes on the same filesystem.
|
||||
*/
|
||||
class NetworkBootstrapper {
|
||||
// TODO Move this to tools:bootstrapper
|
||||
class NetworkBootstrapper
|
||||
@VisibleForTesting
|
||||
internal constructor(private val initSerEnv: Boolean,
|
||||
private val embeddedCordaJar: () -> InputStream,
|
||||
private val nodeInfosGenerator: (List<Path>) -> List<Path>,
|
||||
private val contractsJarConverter: (Path) -> ContractsJar) {
|
||||
|
||||
constructor() : this(
|
||||
initSerEnv = true,
|
||||
embeddedCordaJar = Companion::extractEmbeddedCordaJar,
|
||||
nodeInfosGenerator = Companion::generateNodeInfos,
|
||||
contractsJarConverter = ::ContractsJarFile
|
||||
)
|
||||
|
||||
companion object {
|
||||
// TODO This will probably need to change once we start using a bundled JVM
|
||||
private val nodeInfoGenCmd = listOf(
|
||||
@ -65,11 +79,42 @@ class NetworkBootstrapper {
|
||||
|
||||
private const val LOGS_DIR_NAME = "logs"
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val baseNodeDirectory = requireNotNull(args.firstOrNull()) { "Expecting first argument which is the nodes' parent directory" }
|
||||
val cordappJars = if (args.size > 1) args.asList().drop(1).map { Paths.get(it) } else emptyList()
|
||||
NetworkBootstrapper().bootstrap(Paths.get(baseNodeDirectory).toAbsolutePath().normalize(), cordappJars)
|
||||
private fun extractEmbeddedCordaJar(): InputStream {
|
||||
return Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar")
|
||||
}
|
||||
|
||||
private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> {
|
||||
val numParallelProcesses = Runtime.getRuntime().availableProcessors()
|
||||
val timePerNode = 40.seconds // On the test machine, generating the node info takes 7 seconds for a single node.
|
||||
val tExpected = maxOf(timePerNode, timePerNode * nodeDirs.size.toLong() / numParallelProcesses.toLong())
|
||||
val warningTimer = Timer("WarnOnSlowMachines", false).schedule(tExpected.toMillis()) {
|
||||
println("... still waiting. If this is taking longer than usual, check the node logs.")
|
||||
}
|
||||
val executor = Executors.newFixedThreadPool(numParallelProcesses)
|
||||
return try {
|
||||
nodeDirs.map { executor.fork { generateNodeInfo(it) } }.transpose().getOrThrow()
|
||||
} finally {
|
||||
warningTimer.cancel()
|
||||
executor.shutdownNow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateNodeInfo(nodeDir: Path): Path {
|
||||
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
|
||||
val process = ProcessBuilder(nodeInfoGenCmd)
|
||||
.directory(nodeDir.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.redirectOutput((logsDir / "node-info-gen.log").toFile())
|
||||
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
|
||||
.start()
|
||||
if (!process.waitFor(3, TimeUnit.MINUTES)) {
|
||||
process.destroyForcibly()
|
||||
throw IllegalStateException("Error while generating node info file. Please check the logs in $logsDir.")
|
||||
}
|
||||
check(process.exitValue() == 0) { "Error while generating node info file. Please check the logs in $logsDir." }
|
||||
return nodeDir.list { paths ->
|
||||
paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,29 +147,61 @@ class NetworkBootstrapper {
|
||||
private fun generateServiceIdentitiesForNotaryClusters(configs: Map<Path, Config>) {
|
||||
notaryClusters(configs).forEach { (cluster, directories) ->
|
||||
when (cluster) {
|
||||
is NotaryCluster.BFT ->
|
||||
DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(directories, cluster.name, threshold = 1 + 2 * directories.size / 3)
|
||||
is NotaryCluster.CFT ->
|
||||
DevIdentityGenerator.generateDistributedNotarySingularIdentity(directories, cluster.name)
|
||||
is NotaryCluster.BFT -> DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(
|
||||
directories,
|
||||
cluster.name,
|
||||
threshold = 1 + 2 * directories.size / 3
|
||||
)
|
||||
is NotaryCluster.CFT -> DevIdentityGenerator.generateDistributedNotarySingularIdentity(directories, cluster.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Entry point for Cordform */
|
||||
fun bootstrap(directory: Path, cordappJars: List<Path>) {
|
||||
bootstrap(directory, cordappJars, copyCordapps = true, fromCordform = true)
|
||||
}
|
||||
|
||||
/** Entry point for the tool */
|
||||
fun bootstrap(directory: Path, copyCordapps: Boolean) {
|
||||
// Don't accidently 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()
|
||||
}
|
||||
bootstrap(directory, cordappJars, copyCordapps, fromCordform = false)
|
||||
}
|
||||
|
||||
private fun bootstrap(directory: Path, cordappJars: List<Path>, copyCordapps: Boolean, fromCordform: Boolean) {
|
||||
directory.createDirectories()
|
||||
println("Bootstrapping local network in $directory")
|
||||
generateDirectoriesIfNeeded(directory, cordappJars)
|
||||
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
|
||||
println("Bootstrapping local test network in $directory")
|
||||
if (!fromCordform) {
|
||||
println("Found the following CorDapps: ${cordappJars.map { it.fileName }}")
|
||||
}
|
||||
createNodeDirectoriesIfNeeded(directory, fromCordform)
|
||||
val nodeDirs = gatherNodeDirectories(directory)
|
||||
|
||||
require(nodeDirs.isNotEmpty()) { "No nodes found" }
|
||||
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
|
||||
if (!fromCordform) {
|
||||
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
|
||||
}
|
||||
|
||||
val configs = nodeDirs.associateBy({ it }, { ConfigFactory.parseFile((it / "node.conf").toFile()) })
|
||||
checkForDuplicateLegalNames(configs.values)
|
||||
if (copyCordapps && cordappJars.isNotEmpty()) {
|
||||
println("Copying CorDapp JARs into node directories")
|
||||
for (nodeDir in nodeDirs) {
|
||||
val cordappsDir = (nodeDir / "cordapps").createDirectories()
|
||||
cordappJars.forEach { it.copyToDirectory(cordappsDir) }
|
||||
}
|
||||
}
|
||||
generateServiceIdentitiesForNotaryClusters(configs)
|
||||
initialiseSerialization()
|
||||
if (initSerEnv) {
|
||||
initialiseSerialization()
|
||||
}
|
||||
try {
|
||||
println("Waiting for all nodes to generate their node-info files...")
|
||||
val nodeInfoFiles = generateNodeInfos(nodeDirs)
|
||||
println("Checking for duplicate nodes")
|
||||
checkForDuplicateLegalNames(nodeInfoFiles)
|
||||
val nodeInfoFiles = nodeInfosGenerator(nodeDirs)
|
||||
println("Distributing all node-info files to all nodes")
|
||||
distributeNodeInfos(nodeDirs, nodeInfoFiles)
|
||||
print("Loading existing network parameters... ")
|
||||
@ -133,72 +210,70 @@ class NetworkBootstrapper {
|
||||
println("Gathering notary identities")
|
||||
val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs)
|
||||
println("Generating contract implementations whitelist")
|
||||
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.map(::ContractsJarFile))
|
||||
val netParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs)
|
||||
println("${if (existingNetParams == null) "New" else "Updated"} $netParams")
|
||||
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.map(contractsJarConverter))
|
||||
val newNetParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs)
|
||||
if (newNetParams != existingNetParams) {
|
||||
println("${if (existingNetParams == null) "New" else "Updated"} $newNetParams")
|
||||
} else {
|
||||
println("Network parameters unchanged")
|
||||
}
|
||||
println("Bootstrapping complete!")
|
||||
} finally {
|
||||
_contextSerializationEnv.set(null)
|
||||
if (initSerEnv) {
|
||||
_contextSerializationEnv.set(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> {
|
||||
val numParallelProcesses = Runtime.getRuntime().availableProcessors()
|
||||
val timePerNode = 40.seconds // On the test machine, generating the node info takes 7 seconds for a single node.
|
||||
val tExpected = maxOf(timePerNode, timePerNode * nodeDirs.size.toLong() / numParallelProcesses.toLong())
|
||||
val warningTimer = Timer("WarnOnSlowMachines", false).schedule(tExpected.toMillis()) {
|
||||
println("...still waiting. If this is taking longer than usual, check the node logs.")
|
||||
private fun createNodeDirectoriesIfNeeded(directory: Path, fromCordform: Boolean) {
|
||||
val cordaJar = directory / "corda.jar"
|
||||
var usingEmbedded = false
|
||||
if (!cordaJar.exists()) {
|
||||
embeddedCordaJar().use { it.copyTo(cordaJar) }
|
||||
usingEmbedded = true
|
||||
} else if (!fromCordform) {
|
||||
println("Using corda.jar in root directory")
|
||||
}
|
||||
val executor = Executors.newFixedThreadPool(numParallelProcesses)
|
||||
return try {
|
||||
nodeDirs.map { executor.fork { generateNodeInfo(it) } }.transpose().getOrThrow()
|
||||
} finally {
|
||||
warningTimer.cancel()
|
||||
executor.shutdownNow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateNodeInfo(nodeDir: Path): Path {
|
||||
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
|
||||
val process = ProcessBuilder(nodeInfoGenCmd)
|
||||
.directory(nodeDir.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.redirectOutput((logsDir / "node-info-gen.log").toFile())
|
||||
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
|
||||
.start()
|
||||
if (!process.waitFor(3, TimeUnit.MINUTES)) {
|
||||
process.destroyForcibly()
|
||||
throw IllegalStateException("Error while generating node info file. Please check the logs in $logsDir.")
|
||||
}
|
||||
check(process.exitValue() == 0) { "Error while generating node info file. Please check the logs in $logsDir." }
|
||||
return nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() }
|
||||
}
|
||||
|
||||
private fun generateDirectoriesIfNeeded(directory: Path, cordappJars: List<Path>) {
|
||||
val confFiles = directory.list { it.filter { it.toString().endsWith("_node.conf") }.toList() }
|
||||
val webServerConfFiles = directory.list { it.filter { it.toString().endsWith("_web-server.conf") }.toList() }
|
||||
if (confFiles.isEmpty()) return
|
||||
println("Node config files found in the root directory - generating node directories and copying CorDapp jars into them")
|
||||
val cordaJar = extractCordaJarTo(directory)
|
||||
|
||||
for (confFile in confFiles) {
|
||||
val nodeName = confFile.fileName.toString().removeSuffix("_node.conf")
|
||||
println("Generating directory for $nodeName")
|
||||
println("Generating node directory for $nodeName")
|
||||
val nodeDir = (directory / nodeName).createDirectories()
|
||||
confFile.moveTo(nodeDir / "node.conf", REPLACE_EXISTING)
|
||||
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.moveTo(nodeDir / "web-server.conf", REPLACE_EXISTING)
|
||||
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)
|
||||
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
|
||||
val cordappsDir = (nodeDir / "cordapps").createDirectories()
|
||||
cordappJars.forEach { it.copyToDirectory(cordappsDir) }
|
||||
}
|
||||
cordaJar.delete()
|
||||
|
||||
directory.list { paths ->
|
||||
paths.filter { (it / "node.conf").exists() && !(it / "corda.jar").exists() }.forEach {
|
||||
println("Copying corda.jar into node directory ${it.fileName}")
|
||||
cordaJar.copyToDirectory(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (fromCordform) {
|
||||
confFiles.forEach(Path::delete)
|
||||
webServerConfFiles.forEach(Path::delete)
|
||||
}
|
||||
|
||||
if (fromCordform || usingEmbedded) {
|
||||
cordaJar.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractCordaJarTo(directory: Path): Path {
|
||||
val cordaJarPath = directory / "corda.jar"
|
||||
if (!cordaJarPath.exists()) {
|
||||
Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar").use { it.copyTo(cordaJarPath) }
|
||||
private fun gatherNodeDirectories(directory: Path): List<Path> {
|
||||
return directory.list { paths ->
|
||||
paths.filter {
|
||||
val exists = (it / "corda.jar").exists()
|
||||
if (exists) {
|
||||
require((it / "node.conf").exists()) { "Missing node.conf in node directory ${it.fileName}" }
|
||||
}
|
||||
exists
|
||||
}.toList()
|
||||
}
|
||||
return cordaJarPath
|
||||
}
|
||||
|
||||
private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) {
|
||||
@ -210,20 +285,13 @@ class NetworkBootstrapper {
|
||||
}
|
||||
}
|
||||
|
||||
/*the function checks for duplicate myLegalName in the all the *_node.conf files
|
||||
All the myLegalName values are added to a HashSet - this helps detect duplicate values.
|
||||
If a duplicate name is found the process is aborted with an error message
|
||||
*/
|
||||
private fun checkForDuplicateLegalNames(nodeInfoFiles: List<Path>) {
|
||||
val legalNames = HashSet<String>()
|
||||
for (nodeInfoFile in nodeInfoFiles) {
|
||||
val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
|
||||
val legalName = nodeConfig.getString("myLegalName")
|
||||
if(!legalNames.add(legalName)){
|
||||
println("Duplicate Node Found - ensure every node has a unique legal name");
|
||||
throw IllegalArgumentException("Duplicate Node Found - $legalName");
|
||||
private fun checkForDuplicateLegalNames(nodeConfigs: Collection<Config>) {
|
||||
val duplicateLegalNames = nodeConfigs
|
||||
.groupBy { it.getString("myLegalName") }
|
||||
.mapNotNull { if (it.value.size > 1) it.key else null }
|
||||
check(duplicateLegalNames.isEmpty()) {
|
||||
"Nodes must have unique legal names. The following are used more than once: $duplicateLegalNames"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNotaryInfos(nodeInfoFiles: List<Path>, configs: Map<Path, Config>): List<NotaryInfo> {
|
||||
@ -274,15 +342,19 @@ class NetworkBootstrapper {
|
||||
whitelist: Map<String, List<AttachmentId>>,
|
||||
existingNetParams: NetworkParameters?,
|
||||
nodeDirs: List<Path>): NetworkParameters {
|
||||
val networkParameters = if (existingNetParams != null) {
|
||||
existingNetParams.copy(
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
whitelistedContractImplementations = whitelist,
|
||||
epoch = existingNetParams.epoch + 1
|
||||
)
|
||||
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
|
||||
val netParams = if (existingNetParams != null) {
|
||||
if (existingNetParams.whitelistedContractImplementations == whitelist && existingNetParams.notaries == notaryInfos) {
|
||||
existingNetParams
|
||||
} else {
|
||||
existingNetParams.copy(
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
whitelistedContractImplementations = whitelist,
|
||||
epoch = existingNetParams.epoch + 1
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
|
||||
NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = notaryInfos,
|
||||
@ -294,9 +366,9 @@ class NetworkBootstrapper {
|
||||
eventHorizon = 30.days
|
||||
)
|
||||
}
|
||||
val copier = NetworkParametersCopier(networkParameters, overwriteFile = true)
|
||||
val copier = NetworkParametersCopier(netParams, overwriteFile = true)
|
||||
nodeDirs.forEach(copier::install)
|
||||
return networkParameters
|
||||
return netParams
|
||||
}
|
||||
|
||||
private fun NodeInfo.notaryIdentity(): Party {
|
||||
@ -311,7 +383,6 @@ class NetworkBootstrapper {
|
||||
}
|
||||
|
||||
// We need to to set serialization env, because generation of parameters is run from Cordform.
|
||||
// KryoServerSerializationScheme is not accessible from nodeapi.
|
||||
private fun initialiseSerialization() {
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
|
@ -1,141 +1,289 @@
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.nodeapi.internal.ContractsJar
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY
|
||||
import net.corda.core.crypto.secureRandomBytes
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||
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.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.createNodeInfoAndSigned
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Path
|
||||
import kotlin.streams.toList
|
||||
|
||||
class NetworkBootstrapperTest {
|
||||
@Test
|
||||
fun `no jars against empty whitelist`() {
|
||||
val whitelist = generateWhitelist(emptyMap(), emptyList(), emptyList())
|
||||
assertThat(whitelist).isEmpty()
|
||||
}
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `no jars against single whitelist`() {
|
||||
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), emptyList())
|
||||
assertThat(newWhitelist).isEqualTo(existingWhitelist)
|
||||
}
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `empty jar against empty whitelist`() {
|
||||
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
|
||||
assertThat(whitelist).isEmpty()
|
||||
}
|
||||
private val fakeEmbeddedCordaJar = fakeFileBytes()
|
||||
|
||||
@Test
|
||||
fun `empty jar against single whitelist`() {
|
||||
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
|
||||
assertThat(newWhitelist).isEqualTo(existingWhitelist)
|
||||
}
|
||||
private val contractsJars = HashMap<Path, TestContractsJar>()
|
||||
|
||||
@Test
|
||||
fun `jar with single contract against empty whitelist`() {
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(jar))
|
||||
assertThat(whitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(jar.hash)
|
||||
))
|
||||
}
|
||||
private val bootstrapper = NetworkBootstrapper(
|
||||
initSerEnv = false,
|
||||
embeddedCordaJar = fakeEmbeddedCordaJar::inputStream,
|
||||
nodeInfosGenerator = { nodeDirs ->
|
||||
nodeDirs.map { nodeDir ->
|
||||
val name = nodeDir.fakeNodeConfig.myLegalName
|
||||
val file = nodeDir / "$NODE_INFO_FILE_NAME_PREFIX${name.serialize().hash}"
|
||||
if (!file.exists()) {
|
||||
createNodeInfoAndSigned(name).signed.serialize().open().copyTo(file)
|
||||
}
|
||||
file
|
||||
}
|
||||
},
|
||||
contractsJarConverter = { contractsJars[it]!! }
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `single contract jar against single whitelist of different contract`() {
|
||||
val class1JarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(class1JarHash))
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class2"))
|
||||
val whitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
|
||||
assertThat(whitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(class1JarHash),
|
||||
"class2" to listOf(jar.hash)
|
||||
))
|
||||
}
|
||||
private val aliceConfig = FakeNodeConfig(ALICE_NAME)
|
||||
private val bobConfig = FakeNodeConfig(BOB_NAME)
|
||||
private val notaryConfig = FakeNodeConfig(DUMMY_NOTARY_NAME, NotaryConfig(validating = true))
|
||||
|
||||
@Test
|
||||
fun `same jar with single contract`() {
|
||||
val jarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(jarHash))
|
||||
val jar = TestContractsJar(hash = jarHash, contractClassNames = listOf("class1"))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
|
||||
assertThat(newWhitelist).isEqualTo(existingWhitelist)
|
||||
}
|
||||
private var providedCordaJar: ByteArray? = null
|
||||
private val configFiles = HashMap<Path, String>()
|
||||
|
||||
@Test
|
||||
fun `jar with updated contract`() {
|
||||
val previousJarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
|
||||
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
|
||||
assertThat(newWhitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(previousJarHash, newContractsJar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with one existing contract and one new one`() {
|
||||
val previousJarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
|
||||
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
|
||||
assertThat(newWhitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(previousJarHash, newContractsJar.hash),
|
||||
"class2" to listOf(newContractsJar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two versions of the same contract`() {
|
||||
val version1Jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val version2Jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val newWhitelist = generateWhitelist(emptyMap(), emptyList(), listOf(version1Jar, version2Jar))
|
||||
assertThat(newWhitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(version1Jar.hash, version2Jar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with single new contract that's excluded`() {
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
|
||||
assertThat(whitelist).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with two new contracts, one of which is excluded`() {
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
|
||||
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
|
||||
assertThat(whitelist).isEqualTo(mapOf(
|
||||
"class2" to listOf(jar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with updated contract but it's excluded`() {
|
||||
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
assertThatIllegalArgumentException().isThrownBy {
|
||||
generateWhitelist(existingWhitelist, listOf("class1"), listOf(jar))
|
||||
@After
|
||||
fun `check config files are preserved`() {
|
||||
configFiles.forEach { file, text ->
|
||||
assertThat(file).hasContent(text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateWhitelist(existingWhitelist: Map<String, List<AttachmentId>>,
|
||||
excludeContracts: List<ContractClassName>,
|
||||
contractJars: List<TestContractsJar>): Map<String, List<AttachmentId>> {
|
||||
return generateWhitelist(
|
||||
testNetworkParameters(whitelistedContractImplementations = existingWhitelist),
|
||||
excludeContracts,
|
||||
contractJars
|
||||
)
|
||||
@After
|
||||
fun `check provided corda jar is preserved`() {
|
||||
if (providedCordaJar == null) {
|
||||
// Make sure we clean up if we used the embedded jar
|
||||
assertThat(rootDir / "corda.jar").doesNotExist()
|
||||
} else {
|
||||
// Make sure we don't delete it if it was provided by the user
|
||||
assertThat(rootDir / "corda.jar").hasBinaryContent(providedCordaJar)
|
||||
}
|
||||
}
|
||||
|
||||
data class TestContractsJar(override val hash: SecureHash = SecureHash.randomSHA256(),
|
||||
private val contractClassNames: List<ContractClassName>) : ContractsJar {
|
||||
override fun scan(): List<ContractClassName> = contractClassNames
|
||||
@Test
|
||||
fun `empty dir`() {
|
||||
assertThatThrownBy {
|
||||
bootstrap()
|
||||
}.hasMessage("No nodes found")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single node conf file`() {
|
||||
createNodeConfFile("node1", bobConfig)
|
||||
bootstrap()
|
||||
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "node1" to bobConfig)
|
||||
networkParameters.run {
|
||||
assertThat(epoch).isEqualTo(1)
|
||||
assertThat(notaries).isEmpty()
|
||||
assertThat(whitelistedContractImplementations).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `node conf file and corda jar`() {
|
||||
createNodeConfFile("node1", bobConfig)
|
||||
val fakeCordaJar = fakeFileBytes(rootDir / "corda.jar")
|
||||
bootstrap()
|
||||
assertBootstrappedNetwork(fakeCordaJar, "node1" to bobConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single node directory with just node conf file`() {
|
||||
createNodeDir("bob", bobConfig)
|
||||
bootstrap()
|
||||
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "bob" to bobConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single node directory with node conf file and corda jar`() {
|
||||
val nodeDir = createNodeDir("bob", bobConfig)
|
||||
val fakeCordaJar = fakeFileBytes(nodeDir / "corda.jar")
|
||||
bootstrap()
|
||||
assertBootstrappedNetwork(fakeCordaJar, "bob" to bobConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single node directory with just corda jar`() {
|
||||
val nodeCordaJar = (rootDir / "alice").createDirectories() / "corda.jar"
|
||||
val fakeCordaJar = fakeFileBytes(nodeCordaJar)
|
||||
assertThatThrownBy {
|
||||
bootstrap()
|
||||
}.hasMessageStartingWith("Missing node.conf in node directory alice")
|
||||
assertThat(nodeCordaJar).hasBinaryContent(fakeCordaJar) // Make sure the corda.jar is left untouched
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two node conf files, one of which is a notary`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
createNodeConfFile("notary", notaryConfig)
|
||||
bootstrap()
|
||||
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig)
|
||||
networkParameters.assertContainsNotary("notary")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two node conf files with the same legal name`() {
|
||||
createNodeConfFile("node1", aliceConfig)
|
||||
createNodeConfFile("node2", aliceConfig)
|
||||
assertThatThrownBy {
|
||||
bootstrap()
|
||||
}.hasMessageContaining("Nodes must have unique legal names")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `one node directory and one node conf file`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
createNodeDir("bob", bobConfig)
|
||||
bootstrap()
|
||||
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `node conf file and CorDapp jar`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
|
||||
bootstrap()
|
||||
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
|
||||
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").hasBinaryContent(cordappBytes)
|
||||
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
|
||||
"contract.class" to listOf(cordappBytes.sha256())
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no copy CorDapps`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
|
||||
bootstrap(copyCordapps = false)
|
||||
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
|
||||
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").doesNotExist()
|
||||
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
|
||||
"contract.class" to listOf(cordappBytes.sha256())
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add node to existing network`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
bootstrap()
|
||||
val networkParameters1 = (rootDir / "alice").networkParameters
|
||||
createNodeConfFile("bob", bobConfig)
|
||||
bootstrap()
|
||||
val networkParameters2 = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig)
|
||||
assertThat(networkParameters1).isEqualTo(networkParameters2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add notary to existing network`() {
|
||||
createNodeConfFile("alice", aliceConfig)
|
||||
bootstrap()
|
||||
createNodeConfFile("notary", notaryConfig)
|
||||
bootstrap()
|
||||
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig)
|
||||
networkParameters.assertContainsNotary("notary")
|
||||
assertThat(networkParameters.epoch).isEqualTo(2)
|
||||
}
|
||||
|
||||
private val rootDir get() = tempFolder.root.toPath()
|
||||
|
||||
private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
|
||||
val bytes = secureRandomBytes(128)
|
||||
writeToFile?.write(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
private fun bootstrap(copyCordapps: Boolean = true) {
|
||||
providedCordaJar = (rootDir / "corda.jar").let { if (it.exists()) it.readAll() else null }
|
||||
bootstrapper.bootstrap(rootDir, copyCordapps)
|
||||
}
|
||||
|
||||
private fun createNodeConfFile(nodeDirName: String, config: FakeNodeConfig) {
|
||||
writeNodeConfFile(rootDir / "${nodeDirName}_node.conf", config)
|
||||
}
|
||||
|
||||
private fun createNodeDir(nodeDirName: String, config: FakeNodeConfig): Path {
|
||||
val nodeDir = (rootDir / nodeDirName).createDirectories()
|
||||
writeNodeConfFile(nodeDir / "node.conf", config)
|
||||
return nodeDir
|
||||
}
|
||||
|
||||
private fun writeNodeConfFile(file: Path, config: FakeNodeConfig) {
|
||||
val configText = config.toConfig().root().render()
|
||||
file.writeText(configText)
|
||||
configFiles[file] = configText
|
||||
}
|
||||
|
||||
private fun createFakeCordappJar(cordappName: String, contractClassNames: List<String>): ByteArray {
|
||||
val cordappJarFile = rootDir / "$cordappName.jar"
|
||||
val cordappBytes = fakeFileBytes(cordappJarFile)
|
||||
contractsJars[cordappJarFile] = TestContractsJar(cordappBytes.sha256(), contractClassNames)
|
||||
return cordappBytes
|
||||
}
|
||||
|
||||
private val Path.networkParameters: NetworkParameters get() {
|
||||
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>().verifiedNetworkMapCert(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.nodeInfo: NodeInfo get() = nodeInfoFile.readObject<SignedNodeInfo>().verified()
|
||||
|
||||
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
|
||||
val allNodeInfoFiles = nodes.map { (rootDir / it.first).nodeInfoFile }.associateBy({ it }, { it.readAll() })
|
||||
|
||||
for ((nodeDirName, config) in nodes) {
|
||||
val nodeDir = rootDir / nodeDirName
|
||||
assertThat(nodeDir / "corda.jar").hasBinaryContent(cordaJar)
|
||||
assertThat(nodeDir.fakeNodeConfig).isEqualTo(config)
|
||||
assertThat(nodeDir.networkParameters).isEqualTo(networkParameters)
|
||||
// Make sure all the nodes have all of each others' node-info files
|
||||
allNodeInfoFiles.forEach { nodeInfoFile, bytes ->
|
||||
assertThat(nodeDir / NODE_INFO_DIRECTORY / nodeInfoFile.fileName.toString()).hasBinaryContent(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return networkParameters
|
||||
}
|
||||
|
||||
private fun NetworkParameters.assertContainsNotary(dirName: String) {
|
||||
val notaryParty = (rootDir / dirName).nodeInfo.legalIdentities.single()
|
||||
assertThat(notaries).hasSize(1)
|
||||
notaries[0].run {
|
||||
assertThat(validating).isTrue()
|
||||
assertThat(identity.name).isEqualTo(notaryParty.name)
|
||||
assertThat(identity.owningKey).isEqualTo(notaryParty.owningKey)
|
||||
}
|
||||
}
|
||||
|
||||
data class FakeNodeConfig(val myLegalName: CordaX500Name, val notary: NotaryConfig? = null)
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.nodeapi.internal.ContractsJar
|
||||
|
||||
data class TestContractsJar(override val hash: SecureHash = SecureHash.randomSHA256(),
|
||||
private val contractClassNames: List<ContractClassName>) : ContractsJar {
|
||||
override fun scan(): List<ContractClassName> = contractClassNames
|
||||
}
|
@ -1,41 +1,135 @@
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.verify
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.nodeapi.internal.ContractsJar
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class WhitelistGeneratorTest {
|
||||
@Test
|
||||
fun `no jars against empty whitelist`() {
|
||||
val whitelist = generateWhitelist(emptyMap(), emptyList(), emptyList())
|
||||
assertThat(whitelist).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `whitelist generator builds the correct whitelist map`() {
|
||||
// given
|
||||
val jars = (0..9).map {
|
||||
val index = it
|
||||
mock<ContractsJar> {
|
||||
val secureHash = SecureHash.randomSHA256()
|
||||
on { scan() }.then {
|
||||
listOf(index.toString())
|
||||
}
|
||||
on { hash }.then {
|
||||
secureHash
|
||||
}
|
||||
}
|
||||
}
|
||||
fun `no jars against single whitelist`() {
|
||||
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), emptyList())
|
||||
assertThat(newWhitelist).isEqualTo(existingWhitelist)
|
||||
}
|
||||
|
||||
// when
|
||||
val result = generateWhitelist(null, emptyList(), jars)
|
||||
@Test
|
||||
fun `empty jar against empty whitelist`() {
|
||||
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
|
||||
assertThat(whitelist).isEmpty()
|
||||
}
|
||||
|
||||
// then
|
||||
jars.forEachIndexed { index, item ->
|
||||
verify(item).scan()
|
||||
val attachmentIds = requireNotNull(result[index.toString()])
|
||||
assertEquals(1, attachmentIds.size)
|
||||
assertTrue { attachmentIds.contains(item.hash) }
|
||||
@Test
|
||||
fun `empty jar against single whitelist`() {
|
||||
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(TestContractsJar(contractClassNames = emptyList())))
|
||||
assertThat(newWhitelist).isEqualTo(existingWhitelist)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with single contract against empty whitelist`() {
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val whitelist = generateWhitelist(emptyMap(), emptyList(), listOf(jar))
|
||||
assertThat(whitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(jar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single contract jar against single whitelist of different contract`() {
|
||||
val class1JarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(class1JarHash))
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class2"))
|
||||
val whitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
|
||||
assertThat(whitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(class1JarHash),
|
||||
"class2" to listOf(jar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `same jar with single contract`() {
|
||||
val jarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(jarHash))
|
||||
val jar = TestContractsJar(hash = jarHash, contractClassNames = listOf("class1"))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(jar))
|
||||
assertThat(newWhitelist).isEqualTo(existingWhitelist)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with updated contract`() {
|
||||
val previousJarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
|
||||
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
|
||||
assertThat(newWhitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(previousJarHash, newContractsJar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with one existing contract and one new one`() {
|
||||
val previousJarHash = SecureHash.randomSHA256()
|
||||
val existingWhitelist = mapOf("class1" to listOf(previousJarHash))
|
||||
val newContractsJar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
|
||||
val newWhitelist = generateWhitelist(existingWhitelist, emptyList(), listOf(newContractsJar))
|
||||
assertThat(newWhitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(previousJarHash, newContractsJar.hash),
|
||||
"class2" to listOf(newContractsJar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two versions of the same contract`() {
|
||||
val version1Jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val version2Jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val newWhitelist = generateWhitelist(emptyMap(), emptyList(), listOf(version1Jar, version2Jar))
|
||||
assertThat(newWhitelist).isEqualTo(mapOf(
|
||||
"class1" to listOf(version1Jar.hash, version2Jar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with single new contract that's excluded`() {
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
|
||||
assertThat(whitelist).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with two new contracts, one of which is excluded`() {
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1", "class2"))
|
||||
val whitelist = generateWhitelist(emptyMap(), listOf("class1"), listOf(jar))
|
||||
assertThat(whitelist).isEqualTo(mapOf(
|
||||
"class2" to listOf(jar.hash)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with updated contract but it's excluded`() {
|
||||
val existingWhitelist = mapOf("class1" to listOf(SecureHash.randomSHA256()))
|
||||
val jar = TestContractsJar(contractClassNames = listOf("class1"))
|
||||
assertThatIllegalArgumentException().isThrownBy {
|
||||
generateWhitelist(existingWhitelist, listOf("class1"), listOf(jar))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private fun generateWhitelist(existingWhitelist: Map<String, List<AttachmentId>>,
|
||||
excludeContracts: List<ContractClassName>,
|
||||
contractJars: List<TestContractsJar>): Map<String, List<AttachmentId>> {
|
||||
return generateWhitelist(
|
||||
testNetworkParameters(whitelistedContractImplementations = existingWhitelist),
|
||||
excludeContracts,
|
||||
contractJars
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ open class NodeStartup(val args: Array<String>) {
|
||||
logger.info("Revision: ${versionInfo.revision}")
|
||||
val info = ManagementFactory.getRuntimeMXBean()
|
||||
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
|
||||
logger.info("Main class: ${NodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
|
||||
logger.info("Main class: ${NodeConfiguration::class.java.location.toURI().path}")
|
||||
logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
|
||||
logger.info("Application Args: ${args.joinToString(" ")}")
|
||||
logger.info("bootclasspath: ${info.bootClassPath}")
|
||||
|
@ -315,7 +315,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
|
||||
private fun findPlugins(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
|
||||
return ServiceLoader.load(SerializationWhitelist::class.java, URLClassLoader(arrayOf(cordappJarPath.url), appClassLoader)).toList().filter {
|
||||
it.javaClass.protectionDomain.codeSource.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
|
||||
it.javaClass.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
|
||||
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||
}
|
||||
|
||||
|
@ -26,15 +26,13 @@ class AMQPServerSerializationScheme(
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext) =
|
||||
SerializerFactory(
|
||||
context.whitelist,
|
||||
context.deserializationClassLoader
|
||||
).apply {
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
|
||||
register(RpcServerObservableSerializer())
|
||||
register(RpcServerCordaFutureSerializer(this))
|
||||
register(RxNotificationSerializer(this))
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||
return canDeserializeVersion(magic) &&
|
||||
|
@ -446,7 +446,7 @@ val Class<out FlowLogic<*>>.flowVersionAndInitiatingClass: Pair<Int, Class<out F
|
||||
|
||||
val Class<out FlowLogic<*>>.appName: String
|
||||
get() {
|
||||
val jarFile = protectionDomain.codeSource.location.toPath()
|
||||
val jarFile = location.toPath()
|
||||
return if (jarFile.isRegularFile() && jarFile.toString().endsWith(".jar")) {
|
||||
jarFile.fileName.toString().removeSuffix(".jar")
|
||||
} else {
|
||||
|
@ -40,7 +40,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
||||
override val objectReferencesEnabled: Boolean,
|
||||
override val useCase: SerializationContext.UseCase,
|
||||
override val encoding: SerializationEncoding?,
|
||||
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext {
|
||||
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
|
||||
override val lenientCarpenterEnabled: Boolean = false) : SerializationContext {
|
||||
private val builder = AttachmentsClassLoaderBuilder(properties, deserializationClassLoader)
|
||||
|
||||
/**
|
||||
@ -62,6 +63,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
||||
return copy(objectReferencesEnabled = false)
|
||||
}
|
||||
|
||||
override fun withLenientCarpenter(): SerializationContext = copy(lenientCarpenterEnabled = true)
|
||||
|
||||
override fun withClassLoader(classLoader: ClassLoader): SerializationContext {
|
||||
return copy(deserializationClassLoader = classLoader)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import net.corda.core.serialization.SerializationContext
|
||||
fun createSerializerFactoryFactory(): SerializerFactoryFactory = SerializerFactoryFactoryImpl()
|
||||
|
||||
open class SerializerFactoryFactoryImpl : SerializerFactoryFactory {
|
||||
override fun make(context: SerializationContext) =
|
||||
SerializerFactory(context.whitelist, context.deserializationClassLoader)
|
||||
override fun make(context: SerializationContext): SerializerFactory {
|
||||
return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled)
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ import net.corda.serialization.internal.amqp.SerializerFactory.Companion.nameFor
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.reflect.jvm.javaConstructor
|
||||
|
||||
@ -39,8 +41,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
|
||||
private val typeName = nameForType(clazz)
|
||||
|
||||
override val typeDescriptor = Symbol.valueOf(
|
||||
"$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")!!
|
||||
override val typeDescriptor: Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||
|
||||
// We restrict to only those annotated or whitelisted
|
||||
private val interfaces = interfacesForSerialization(clazz, factory)
|
||||
@ -129,7 +130,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) {
|
||||
logger.trace { "Calling setter based construction for ${clazz.typeName}" }
|
||||
|
||||
val instance: Any = javaConstructor?.newInstance() ?: throw NotSerializableException(
|
||||
val instance: Any = javaConstructor?.newInstanceUnwrapped() ?: throw NotSerializableException(
|
||||
"Failed to instantiate instance of object $clazz")
|
||||
|
||||
// read the properties out of the serialised form, since we're invoking the setters the order we
|
||||
@ -163,7 +164,15 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
||||
+ "serialized properties.")
|
||||
}
|
||||
|
||||
return javaConstructor?.newInstance(*properties.toTypedArray())
|
||||
return javaConstructor?.newInstanceUnwrapped(*properties.toTypedArray())
|
||||
?: throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.")
|
||||
}
|
||||
|
||||
private fun <T> Constructor<T>.newInstanceUnwrapped(vararg args: Any?): T {
|
||||
try {
|
||||
return newInstance(*args)
|
||||
} catch (e: InvocationTargetException) {
|
||||
throw e.cause!!
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import net.corda.core.StubOutForDJVM
|
||||
import net.corda.core.internal.kotlinObjectInstance
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
@ -63,40 +64,43 @@ open class SerializerFactory(
|
||||
private val serializersByType: MutableMap<Type, AMQPSerializer<Any>>,
|
||||
val serializersByDescriptor: MutableMap<Any, AMQPSerializer<Any>>,
|
||||
private val customSerializers: MutableList<SerializerFor>,
|
||||
val transformsCache: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
|
||||
val transformsCache: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>
|
||||
) {
|
||||
@DeleteForDJVM
|
||||
constructor(whitelist: ClassWhitelist,
|
||||
classCarpenter: ClassCarpenter,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
|
||||
) : this(whitelist, classCarpenter, evolutionSerializerGetter, fingerPrinter,
|
||||
serializersByType = ConcurrentHashMap(),
|
||||
serializersByDescriptor = ConcurrentHashMap(),
|
||||
customSerializers = CopyOnWriteArrayList(),
|
||||
transformsCache = ConcurrentHashMap())
|
||||
) : this(
|
||||
whitelist,
|
||||
classCarpenter,
|
||||
evolutionSerializerGetter,
|
||||
fingerPrinter,
|
||||
ConcurrentHashMap(),
|
||||
ConcurrentHashMap(),
|
||||
CopyOnWriteArrayList(),
|
||||
ConcurrentHashMap()
|
||||
)
|
||||
|
||||
@DeleteForDJVM
|
||||
constructor(whitelist: ClassWhitelist,
|
||||
classLoader: ClassLoader,
|
||||
lenientCarpenter: Boolean = false,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
fingerPrinter: FingerPrinter = SerializerFingerPrinter()
|
||||
) : this(whitelist, ClassCarpenterImpl(classLoader, whitelist), evolutionSerializerGetter, fingerPrinter,
|
||||
serializersByType = ConcurrentHashMap(),
|
||||
serializersByDescriptor = ConcurrentHashMap(),
|
||||
customSerializers = CopyOnWriteArrayList(),
|
||||
transformsCache = ConcurrentHashMap())
|
||||
) : this(whitelist, ClassCarpenterImpl(classLoader, whitelist, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
|
||||
|
||||
init {
|
||||
fingerPrinter.setOwner(this)
|
||||
}
|
||||
|
||||
val classloader: ClassLoader
|
||||
get() = classCarpenter.classloader
|
||||
val classloader: ClassLoader get() = classCarpenter.classloader
|
||||
|
||||
private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer<Any>,
|
||||
schemas: SerializationSchemas) = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
|
||||
|
||||
private val logger = loggerFor<SerializerFactory>()
|
||||
private fun getEvolutionSerializer(typeNotation: TypeNotation,
|
||||
newSerializer: AMQPSerializer<Any>,
|
||||
schemas: SerializationSchemas): AMQPSerializer<Any> {
|
||||
return evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas)
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up, and manufacture if necessary, a serializer for the given type.
|
||||
@ -390,6 +394,8 @@ open class SerializerFactory(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
|
||||
fun isPrimitive(type: Type): Boolean = primitiveTypeName(type) != null
|
||||
|
||||
fun primitiveTypeName(type: Type): String? {
|
||||
@ -479,4 +485,3 @@ open class SerializerFactory(
|
||||
override fun toString(): String = "?"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ fun CompositeType.carpenterSchema(classloader: ClassLoader,
|
||||
}
|
||||
|
||||
try {
|
||||
providesList.add(classloader.loadClass(it))
|
||||
providesList.add(classloader.loadClass(it.stripGenerics()))
|
||||
} catch (e: ClassNotFoundException) {
|
||||
carpenterSchemas.addDepPair(this, name, it)
|
||||
isCreatable = false
|
||||
|
@ -16,6 +16,8 @@ import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
@ -112,8 +114,9 @@ interface ClassCarpenter {
|
||||
* Equals/hashCode methods are not yet supported.
|
||||
*/
|
||||
@DeleteForDJVM
|
||||
class ClassCarpenterImpl(cl: ClassLoader, override val whitelist: ClassWhitelist) : ClassCarpenter {
|
||||
constructor(whitelist: ClassWhitelist) : this(Thread.currentThread().contextClassLoader, whitelist)
|
||||
class ClassCarpenterImpl(cl: ClassLoader = Thread.currentThread().contextClassLoader,
|
||||
override val whitelist: ClassWhitelist,
|
||||
private val lenient: Boolean = false) : ClassCarpenter {
|
||||
|
||||
// TODO: Generics.
|
||||
// TODO: Sandbox the generated code when a security manager is in use.
|
||||
@ -449,25 +452,36 @@ class ClassCarpenterImpl(cl: ClassLoader, override val whitelist: ClassWhitelist
|
||||
// actually called, which is a bit too dynamic for my tastes.
|
||||
val allFields = schema.fieldsIncludingSuperclasses()
|
||||
for (itf in schema.interfaces) {
|
||||
itf.methods.forEach {
|
||||
val fieldNameFromItf = when {
|
||||
it.name.startsWith("get") -> it.name.substring(3).decapitalize()
|
||||
else -> throw InterfaceMismatchNonGetterException(itf, it)
|
||||
methodLoop@
|
||||
for (method in itf.methods) {
|
||||
val fieldNameFromItf = if (method.name.startsWith("get")) {
|
||||
method.name.substring(3).decapitalize()
|
||||
} else if (lenient) {
|
||||
logger.debug { "Ignoring interface $method which is not a getter" }
|
||||
continue@methodLoop
|
||||
} else {
|
||||
throw InterfaceMismatchNonGetterException(itf, method)
|
||||
}
|
||||
|
||||
// If we're trying to carpent a class that prior to serialisation / deserialization
|
||||
// was made by a carpenter then we can ignore this (it will implement a plain get
|
||||
// method from SimpleFieldAccess).
|
||||
if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach
|
||||
if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) continue@methodLoop
|
||||
|
||||
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields)) {
|
||||
throw InterfaceMismatchMissingAMQPFieldException(itf, fieldNameFromItf)
|
||||
if (lenient) {
|
||||
logger.debug { "Ignoring interface $method which is not backed by an AMQP field" }
|
||||
} else {
|
||||
throw InterfaceMismatchMissingAMQPFieldException(itf, fieldNameFromItf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
|
||||
@JvmStatic
|
||||
@Suppress("UNUSED")
|
||||
fun getField(obj: Any, name: String): Any? = obj.javaClass.getMethod("get" + name.capitalize()).invoke(obj)
|
||||
|
@ -18,6 +18,9 @@ import org.junit.Test;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@Ignore("Current behaviour allows for the serialization of objects with private members, this will be disallowed at some point in the future")
|
||||
public class ErrorMessageTests {
|
||||
private String errMsg(String property, String testname) {
|
||||
@ -42,19 +45,10 @@ public class ErrorMessageTests {
|
||||
|
||||
@Test
|
||||
public void testJavaConstructorAnnotations() {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializationOutput ser = new SerializationOutput(testDefaultFactory());
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
|
||||
Assertions.assertThatThrownBy(() -> ser.serialize(new C(1), TestSerializationContext.testSerializationContext))
|
||||
assertThatThrownBy(() -> ser.serialize(new C(1), TestSerializationContext.testSerializationContext))
|
||||
.isInstanceOf(NotSerializableException.class)
|
||||
.hasMessage(errMsg("a", getClass().getName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.SerializationCustomSerializer;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -9,6 +8,8 @@ import java.io.NotSerializableException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
|
||||
public class JavaCustomSerializerTests {
|
||||
/**
|
||||
* The class lacks a public constructor that takes parameters it can associate
|
||||
@ -87,10 +88,8 @@ public class JavaCustomSerializerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeExample() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
public void serializeExample() throws NotSerializableException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
|
||||
List<Integer> l = new ArrayList<Integer>(2);
|
||||
@ -102,7 +101,5 @@ public class JavaCustomSerializerTests {
|
||||
factory.registerExternal(ccs);
|
||||
|
||||
ser.serialize(e, TestSerializationContext.testSerializationContext);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,11 @@
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Test;
|
||||
import java.io.NotSerializableException;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.jgroups.util.Util.assertEquals;
|
||||
|
||||
public class JavaGenericsTest {
|
||||
@ -37,11 +37,7 @@ public class JavaGenericsTest {
|
||||
public void basicGeneric() throws NotSerializableException {
|
||||
A a1 = new A(1);
|
||||
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
SerializedBytes<?> bytes = ser.serialize(a1, TestSerializationContext.testSerializationContext);
|
||||
@ -53,13 +49,9 @@ public class JavaGenericsTest {
|
||||
}
|
||||
|
||||
private SerializedBytes<?> forceWildcardSerialize(A<?> a) throws NotSerializableException {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
return (new SerializationOutput(factory)).serialize(a, TestSerializationContext.testSerializationContext);
|
||||
return (new SerializationOutput(factory)).serialize(a, TestSerializationContext.testSerializationContext);
|
||||
}
|
||||
|
||||
private SerializedBytes<?> forceWildcardSerializeFactory(
|
||||
@ -69,11 +61,7 @@ public class JavaGenericsTest {
|
||||
}
|
||||
|
||||
private A<?> forceWildcardDeserialize(SerializedBytes<?> bytes) throws NotSerializableException {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
return des.deserialize(bytes, A.class, TestSerializationContext.testSerializationContext);
|
||||
@ -95,11 +83,7 @@ public class JavaGenericsTest {
|
||||
|
||||
@Test
|
||||
public void forceWildcardSharedFactory() throws NotSerializableException {
|
||||
SerializerFactory factory = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializedBytes<?> bytes = forceWildcardSerializeFactory(new A(new Inner(29)), factory);
|
||||
Inner i = (Inner)forceWildcardDeserializeFactory(bytes, factory).getT();
|
||||
|
@ -4,28 +4,25 @@ import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class OuterClass1 {
|
||||
protected SerializationOutput ser;
|
||||
DeserializationInput desExisting;
|
||||
DeserializationInput desRegen;
|
||||
|
||||
OuterClass1() {
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory2 = testDefaultFactory();
|
||||
|
||||
this.ser = new SerializationOutput(factory1);
|
||||
this.desExisting = new DeserializationInput(factory1);
|
||||
@ -59,13 +56,9 @@ class OuterClass2 {
|
||||
DeserializationInput desRegen;
|
||||
|
||||
OuterClass2() {
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory2 = testDefaultFactory();
|
||||
|
||||
this.ser = new SerializationOutput(factory1);
|
||||
this.desExisting = new DeserializationInput(factory1);
|
||||
@ -104,9 +97,7 @@ abstract class AbstractClass2 {
|
||||
protected SerializationOutput ser;
|
||||
|
||||
AbstractClass2() {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
this.ser = new SerializationOutput(factory);
|
||||
}
|
||||
@ -134,9 +125,7 @@ abstract class AbstractClass3 {
|
||||
|
||||
class Inherator5 extends AbstractClass3 {
|
||||
public void run() throws NotSerializableException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext);
|
||||
@ -154,9 +143,7 @@ class Inherator6 extends AbstractClass3 {
|
||||
}
|
||||
|
||||
public void run() throws NotSerializableException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
ser.serialize(new Wrapper(new DummyState()), TestSerializationContext.testSerializationContext);
|
||||
@ -166,57 +153,57 @@ class Inherator6 extends AbstractClass3 {
|
||||
public class JavaNestedClassesTests {
|
||||
@Test
|
||||
public void publicNested() {
|
||||
Assertions.assertThatThrownBy(() -> new OuterClass1().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new OuterClass1().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateNested() {
|
||||
Assertions.assertThatThrownBy(() -> new OuterClass2().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new OuterClass2().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedInherited() {
|
||||
Assertions.assertThatThrownBy(() -> new Inherator1().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator1().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
|
||||
Assertions.assertThatThrownBy(() -> new Inherator1().iRun()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator1().iRun()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedNestedInherited() {
|
||||
Assertions.assertThatThrownBy(() -> new Inherator2().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator2().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
|
||||
Assertions.assertThatThrownBy(() -> new Inherator2().iRun()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator2().iRun()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void abstractNested() {
|
||||
Assertions.assertThatThrownBy(() -> new Inherator4().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator4().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void abstractNestedFactoryOnNested() {
|
||||
Assertions.assertThatThrownBy(() -> new Inherator5().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator5().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void abstractNestedFactoryOnNestedInWrapper() {
|
||||
Assertions.assertThatThrownBy(() -> new Inherator6().run()).isInstanceOf(
|
||||
assertThatThrownBy(() -> new Inherator6().run()).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
@ -3,15 +3,16 @@ package net.corda.serialization.internal.amqp;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
abstract class JavaNestedInheritenceTestsBase {
|
||||
class DummyState implements ContractState {
|
||||
@Override @NotNull public List<AbstractParty> getParticipants() {
|
||||
@ -33,38 +34,32 @@ class TemplateWrapper<T> {
|
||||
public class JavaNestedInheritenceTests extends JavaNestedInheritenceTestsBase {
|
||||
@Test
|
||||
public void serializeIt() {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
|
||||
Assertions.assertThatThrownBy(() -> ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
assertThatThrownBy(() -> ser.serialize(new DummyState(), TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeIt2() {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
Assertions.assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
assertThatThrownBy(() -> ser.serialize(new Wrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeIt3() throws NotSerializableException {
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
public void serializeIt3() {
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
|
||||
Assertions.assertThatThrownBy(() -> ser.serialize(new TemplateWrapper<ContractState> (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
assertThatThrownBy(() -> ser.serialize(new TemplateWrapper<ContractState> (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
NotSerializableException.class).hasMessageContaining(
|
||||
"has synthetic fields and is likely a nested inner class");
|
||||
}
|
||||
|
@ -10,15 +10,16 @@
|
||||
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class JavaPrivatePropertyTests {
|
||||
static class C {
|
||||
private String a;
|
||||
@ -86,10 +87,8 @@ public class JavaPrivatePropertyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePrivateBooleanWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
public void singlePrivateBooleanWithConstructor() throws NotSerializableException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -99,10 +98,8 @@ public class JavaPrivatePropertyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
@ -114,10 +111,8 @@ public class JavaPrivatePropertyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCapitilsationOfIs() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
public void testCapitilsationOfIs() throws NotSerializableException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -130,10 +125,8 @@ public class JavaPrivatePropertyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePrivateIntWithBoolean() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
public void singlePrivateIntWithBoolean() throws NotSerializableException {
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
||||
@ -146,9 +139,7 @@ public class JavaPrivatePropertyTests {
|
||||
|
||||
@Test
|
||||
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
@ -176,10 +167,7 @@ public class JavaPrivatePropertyTests {
|
||||
@Test
|
||||
public void singlePrivateWithConstructorAndGetter()
|
||||
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializerFactory factory = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory);
|
||||
DeserializationInput des = new DeserializationInput(factory);
|
||||
|
@ -10,14 +10,14 @@
|
||||
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
|
||||
public class JavaSerialiseEnumTests {
|
||||
|
||||
public enum Bras {
|
||||
@ -40,10 +40,7 @@ public class JavaSerialiseEnumTests {
|
||||
public void testJavaConstructorAnnotations() throws NotSerializableException {
|
||||
Bra bra = new Bra(Bras.UNDERWIRE);
|
||||
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
new EvolutionSerializerGetter(),
|
||||
new SerializerFingerPrinter());
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
SerializedBytes<Object> bytes = ser.serialize(bra, TestSerializationContext.testSerializationContext);
|
||||
SerializationOutput ser = new SerializationOutput(testDefaultFactory());
|
||||
ser.serialize(bra, TestSerializationContext.testSerializationContext);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.ConstructorForDeserialization;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.apache.qpid.proton.codec.DecoderImpl;
|
||||
@ -23,6 +22,7 @@ import java.io.NotSerializableException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JavaSerializationOutputTests {
|
||||
@ -186,14 +186,8 @@ public class JavaSerializationOutputTests {
|
||||
}
|
||||
|
||||
private Object serdes(Object obj) throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory2 = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
SerializerFactory factory2 = testDefaultFactory();
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
SerializedBytes<Object> bytes = ser.serialize(obj, TestSerializationContext.testSerializationContext);
|
||||
|
||||
|
@ -12,7 +12,6 @@ package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable;
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@ -20,6 +19,8 @@ import org.junit.Test;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
|
||||
public class ListsSerializationJavaTest {
|
||||
|
||||
@CordaSerializable
|
||||
@ -136,12 +137,7 @@ public class ListsSerializationJavaTest {
|
||||
|
||||
// Have to have own version as Kotlin inline functions cannot be easily called from Java
|
||||
private static <T> void assertEqualAfterRoundTripSerialization(T container, Class<T> clazz) throws Exception {
|
||||
EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
SerializedBytes<Object> bytes = ser.serialize(container, TestSerializationContext.testSerializationContext);
|
||||
DeserializationInput des = new DeserializationInput(factory1);
|
||||
|
@ -11,16 +11,17 @@
|
||||
package net.corda.serialization.internal.amqp;
|
||||
|
||||
import net.corda.core.serialization.SerializedBytes;
|
||||
import net.corda.serialization.internal.AllWhitelist;
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.NotSerializableException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt.testDefaultFactory;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class SetterConstructorTests {
|
||||
|
||||
static class C {
|
||||
@ -129,13 +130,7 @@ public class SetterConstructorTests {
|
||||
// despite having no constructor we should still be able to serialise an instance of C
|
||||
@Test
|
||||
public void serialiseC() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
SerializationOutput ser = new SerializationOutput(factory1);
|
||||
|
||||
@ -205,13 +200,7 @@ public class SetterConstructorTests {
|
||||
|
||||
@Test
|
||||
public void deserialiseC() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
C cPre1 = new C();
|
||||
|
||||
@ -278,13 +267,7 @@ public class SetterConstructorTests {
|
||||
|
||||
@Test
|
||||
public void serialiseOuterAndInner() throws NotSerializableException {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
Inner1 i1 = new Inner1("Hello");
|
||||
Inner2 i2 = new Inner2();
|
||||
@ -308,38 +291,26 @@ public class SetterConstructorTests {
|
||||
|
||||
@Test
|
||||
public void typeMistmatch() {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
TypeMismatch tm = new TypeMismatch();
|
||||
tm.setA(10);
|
||||
assertEquals("10", tm.getA());
|
||||
|
||||
Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
|
||||
assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
|
||||
TestSerializationContext.testSerializationContext)).isInstanceOf (
|
||||
NotSerializableException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeMistmatch2() {
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
TypeMismatch2 tm = new TypeMismatch2();
|
||||
tm.setA("10");
|
||||
assertEquals((Integer)10, tm.getA());
|
||||
|
||||
Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
|
||||
assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm,
|
||||
TestSerializationContext.testSerializationContext)).isInstanceOf(
|
||||
NotSerializableException.class);
|
||||
}
|
||||
@ -356,13 +327,7 @@ public class SetterConstructorTests {
|
||||
|
||||
cil.setL(l);
|
||||
|
||||
EvolutionSerializerGetterBase evolutionSerialiserGetter = new EvolutionSerializerGetter();
|
||||
FingerPrinter fingerPrinter = new SerializerFingerPrinter();
|
||||
SerializerFactory factory1 = new SerializerFactory(
|
||||
AllWhitelist.INSTANCE,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerialiserGetter,
|
||||
fingerPrinter);
|
||||
SerializerFactory factory1 = testDefaultFactory();
|
||||
|
||||
// if we've got super / sub types on the setter vs the underlying type the wrong way around this will
|
||||
// explode. See CORDA-1229 (https://r3-cev.atlassian.net/browse/CORDA-1229)
|
||||
|
@ -11,11 +11,8 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.testutils.*
|
||||
import net.corda.serialization.internal.carpenter.*
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import org.junit.Test
|
||||
import kotlin.test.*
|
||||
|
||||
|
@ -11,15 +11,13 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.serialization.internal.carpenter.*
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryWithWhitelist
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.*
|
||||
import net.corda.serialization.internal.carpenter.*
|
||||
import org.junit.Test
|
||||
import kotlin.test.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@CordaSerializable
|
||||
interface I {
|
||||
|
@ -56,8 +56,8 @@ class FingerPrinterTestingTests {
|
||||
val factory = SerializerFactory(
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
EvolutionSerializerGetterTesting(),
|
||||
FingerPrinterTesting())
|
||||
evolutionSerializerGetter = EvolutionSerializerGetterTesting(),
|
||||
fingerPrinter = FingerPrinterTesting())
|
||||
|
||||
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))
|
||||
|
||||
|
@ -17,13 +17,7 @@ import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.client.rpc.RPCException
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.secureRandomBytes
|
||||
@ -32,48 +26,25 @@ import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.EncodingWhitelist
|
||||
import net.corda.core.serialization.MissingAttachmentsException
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.CordaSerializationEncoding
|
||||
import net.corda.serialization.internal.EmptyWhitelist
|
||||
import net.corda.serialization.internal.GeneratedAttachment
|
||||
import net.corda.serialization.internal.*
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.isPrimitive
|
||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
|
||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
||||
import net.corda.serialization.internal.amqp.testutils.testSerializationContext
|
||||
import net.corda.serialization.internal.encodingNotPermittedFormat
|
||||
import net.corda.serialization.internal.amqp.testutils.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.qpid.proton.amqp.Decimal128
|
||||
import org.apache.qpid.proton.amqp.Decimal32
|
||||
import org.apache.qpid.proton.amqp.Decimal64
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.amqp.UnsignedByte
|
||||
import org.apache.qpid.proton.amqp.UnsignedInteger
|
||||
import org.apache.qpid.proton.amqp.UnsignedLong
|
||||
import org.apache.qpid.proton.amqp.UnsignedShort
|
||||
import org.apache.qpid.proton.amqp.*
|
||||
import org.apache.qpid.proton.codec.DecoderImpl
|
||||
import org.apache.qpid.proton.codec.EncoderImpl
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.assertj.core.api.Assertions.catchThrowable
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.bouncycastle.cert.X509v2CRLBuilder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
@ -89,20 +60,7 @@ import java.io.NotSerializableException
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.security.cert.X509CRL
|
||||
import java.time.DayOfWeek
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.Month
|
||||
import java.time.MonthDay
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.OffsetTime
|
||||
import java.time.Period
|
||||
import java.time.Year
|
||||
import java.time.YearMonth
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.*
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
import kotlin.reflect.full.superclasses
|
||||
@ -259,9 +217,13 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression)
|
||||
}
|
||||
|
||||
private fun defaultFactory() = SerializerFactory(
|
||||
AllWhitelist, ClassLoader.getSystemClassLoader(),
|
||||
EvolutionSerializerGetterTesting())
|
||||
private fun defaultFactory(): SerializerFactory {
|
||||
return SerializerFactory(
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter = EvolutionSerializerGetterTesting()
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any> serdes(obj: T,
|
||||
factory: SerializerFactory = defaultFactory(),
|
||||
|
@ -1,16 +1,25 @@
|
||||
package net.corda.serialization.internal.amqp.testutils
|
||||
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.EmptyWhitelist
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.testing.common.internal.ProjectStructure
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import org.junit.Test
|
||||
import java.io.File.separatorChar
|
||||
import java.io.NotSerializableException
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
|
||||
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
fun testDefaultFactoryNoEvolution() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(),
|
||||
EvolutionSerializerGetterTesting())
|
||||
fun testDefaultFactoryNoEvolution(): SerializerFactory {
|
||||
return SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader(), evolutionSerializerGetter = EvolutionSerializerGetterTesting())
|
||||
}
|
||||
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
||||
|
||||
class TestSerializationOutput(
|
||||
@ -41,8 +50,25 @@ class TestSerializationOutput(
|
||||
}
|
||||
}
|
||||
|
||||
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
||||
fun testName(): String {
|
||||
val classLoader = Thread.currentThread().contextClassLoader
|
||||
return Thread.currentThread().stackTrace.first {
|
||||
try {
|
||||
classLoader.loadClass(it.className).getMethod(it.methodName).isAnnotationPresent(Test::class.java)
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}.methodName
|
||||
}
|
||||
|
||||
fun Any.testResourceName(): String = "${javaClass.simpleName}.${testName()}"
|
||||
|
||||
fun Any.writeTestResource(bytes: OpaqueBytes) {
|
||||
val dir = ProjectStructure.projectRootDir / "serialization" / "src" / "test" / "resources" / javaClass.packageName.replace('.', separatorChar)
|
||||
bytes.open().copyTo(dir / testResourceName(), REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
fun Any.readTestResource(): ByteArray = javaClass.getResourceAsStream(testResourceName()).readBytes()
|
||||
|
||||
@Throws(NotSerializableException::class)
|
||||
inline fun <reified T : Any> DeserializationInput.deserializeAndReturnEnvelope(
|
||||
|
@ -12,6 +12,7 @@ package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Test
|
||||
import java.beans.Introspector
|
||||
import java.lang.reflect.Field
|
||||
@ -155,21 +156,40 @@ class ClassCarpenterTest {
|
||||
assertEquals(1, i.b)
|
||||
}
|
||||
|
||||
@Test(expected = InterfaceMismatchException::class)
|
||||
fun `mismatched interface`() {
|
||||
val schema1 = ClassSchema(
|
||||
@Test
|
||||
fun `unimplemented interface method with lenient = false`() {
|
||||
val schemaA = ClassSchema(
|
||||
"gen.A",
|
||||
mapOf("a" to NonNullableField(String::class.java)))
|
||||
|
||||
val schema2 = ClassSchema(
|
||||
val schemaB = ClassSchema(
|
||||
"gen.B",
|
||||
mapOf("c" to NonNullableField(Int::class.java)),
|
||||
schema1,
|
||||
schemaA,
|
||||
interfaces = listOf(DummyInterface::class.java))
|
||||
|
||||
val clazz = cc.build(schema2)
|
||||
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||
assertEquals(1, i.b)
|
||||
assertThatExceptionOfType(InterfaceMismatchException::class.java).isThrownBy { cc.build(schemaB) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unimplemented interface method with lenient = true`() {
|
||||
val cc = ClassCarpenterImpl(whitelist = AllWhitelist, lenient = true)
|
||||
|
||||
val schemaA = ClassSchema(
|
||||
"gen.A",
|
||||
mapOf("a" to NonNullableField(String::class.java)))
|
||||
|
||||
val schemaB = ClassSchema(
|
||||
"gen.B",
|
||||
mapOf("c" to NonNullableField(Int::class.java)),
|
||||
schemaA,
|
||||
interfaces = listOf(DummyInterface::class.java))
|
||||
|
||||
val classB = cc.build(schemaB)
|
||||
val b = classB.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||
assertEquals("xa", b.a)
|
||||
assertEquals(1, classB.getMethod("getC").invoke(b))
|
||||
assertThatExceptionOfType(AbstractMethodError::class.java).isThrownBy { b.b }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -15,6 +15,7 @@ import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.serialization.internal.amqp.Field
|
||||
import net.corda.serialization.internal.amqp.Schema
|
||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
||||
import net.corda.serialization.internal.amqp.testutils.testName
|
||||
|
||||
fun mangleName(name: String) = "${name}__carpenter"
|
||||
|
||||
@ -57,7 +58,6 @@ open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
||||
var factory = SerializerFactoryExternalCarpenter(cc)
|
||||
|
||||
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
||||
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package net.corda.serialization.internal.carpenter
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.serialization.internal.amqp.testutils.readTestResource
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class SerDeserCarpentryTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun implementingGenericInterface() {
|
||||
// Original class that was serialised
|
||||
// data class GenericData(val a: Int) : GenericInterface<String>
|
||||
// writeTestResource(GenericData(123).serialize())
|
||||
|
||||
val data = readTestResource().deserialize<GenericInterface<*>>()
|
||||
assertThat(data.javaClass.getMethod("getA").invoke(data)).isEqualTo(123)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lenientCarpenter() {
|
||||
// Original class that was serialised
|
||||
// data class Data(val b: Int) : AInterface {
|
||||
// override val a: Int get() = b
|
||||
// }
|
||||
// writeTestResource(Data(123).serialize())
|
||||
|
||||
val data = readTestResource().deserialize<AInterface>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
|
||||
assertThat(data.javaClass.getMethod("getB").invoke(data)).isEqualTo(123)
|
||||
assertThatExceptionOfType(AbstractMethodError::class.java).isThrownBy { data.a }
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@CordaSerializable
|
||||
interface GenericInterface<T>
|
||||
|
||||
@CordaSerializable
|
||||
interface AInterface {
|
||||
val a: Int
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
@ -5,7 +5,7 @@ apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
dependencies {
|
||||
compile project(':client:jackson')
|
||||
compile 'info.picocli:picocli:3.0.0'
|
||||
compile "info.picocli:picocli:$picocli_version"
|
||||
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||
|
@ -7,6 +7,7 @@ import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.core.internal.isRegularFile
|
||||
import net.corda.core.internal.rootMessage
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
@ -33,7 +34,7 @@ fun main(args: Array<String>) {
|
||||
if (main.verbose) {
|
||||
throwable.printStackTrace()
|
||||
} else {
|
||||
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
|
||||
System.err.println("*ERROR*: ${throwable.rootMessage}. Use --verbose for more details")
|
||||
}
|
||||
exitProcess(1)
|
||||
}
|
||||
@ -41,7 +42,7 @@ fun main(args: Array<String>) {
|
||||
|
||||
@Command(
|
||||
name = "Blob Inspector",
|
||||
versionProvider = VersionProvider::class,
|
||||
versionProvider = CordaVersionProvider::class,
|
||||
mixinStandardHelpOptions = true, // add --help and --version options,
|
||||
showDefaultValues = true,
|
||||
description = ["Inspect AMQP serialised binary blobs"]
|
||||
@ -64,7 +65,9 @@ class Main : Runnable {
|
||||
var verbose: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
System.setProperty("logLevel", if (verbose) "trace" else "off")
|
||||
if (verbose) {
|
||||
System.setProperty("logLevel", "trace")
|
||||
}
|
||||
|
||||
val bytes = source!!.readBytes().run {
|
||||
require(size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
|
||||
@ -89,7 +92,8 @@ class Main : Runnable {
|
||||
}
|
||||
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
||||
|
||||
val deserialized = bytes.deserialize<Any>()
|
||||
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
||||
val deserialized = bytes.deserialize<Any>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
|
||||
println(deserialized.javaClass.name)
|
||||
mapper.writeValue(System.out, deserialized)
|
||||
}
|
||||
@ -124,8 +128,13 @@ private class SourceConverter : ITypeConverter<URL> {
|
||||
}
|
||||
}
|
||||
|
||||
private class VersionProvider : IVersionProvider {
|
||||
override fun getVersion(): Array<String> = arrayOf(Manifests.read("Corda-Release-Version"))
|
||||
private class CordaVersionProvider : IVersionProvider {
|
||||
override fun getVersion(): Array<String> {
|
||||
return arrayOf(
|
||||
"Version: ${Manifests.read("Corda-Release-Version")}",
|
||||
"Revision: ${Manifests.read("Corda-Revision")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private enum class FormatType { YAML, JSON }
|
||||
|
@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info">
|
||||
<Properties>
|
||||
<Property name="logLevel">off</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
|
||||
<PatternLayout pattern="[%C{1}.%M] %m%n"/>
|
||||
|
@ -8,54 +8,39 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
apply plugin: 'us.kirchmeier.capsule'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
description 'Network bootstrapper'
|
||||
|
||||
configurations {
|
||||
runtimeArtifacts
|
||||
dependencies {
|
||||
compile project(':node-api')
|
||||
compile "info.picocli:picocli:$picocli_version"
|
||||
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName "corda-tools-network-bootstrapper"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.slf4j:slf4j-nop:$slf4j_version"
|
||||
}
|
||||
|
||||
task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').compileJava) {
|
||||
applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper'
|
||||
archiveName "tools-network-bootstrapper-${corda_release_version}.jar"
|
||||
capsuleManifest {
|
||||
applicationVersion = corda_release_version
|
||||
systemProperties['visualvm.display.name'] = 'Network Bootstrapper'
|
||||
minJavaVersion = '1.8.0'
|
||||
jvmArgs = ['-XX:+UseG1GC']
|
||||
from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
|
||||
exclude "META-INF/*.SF"
|
||||
exclude "META-INF/*.DSA"
|
||||
exclude "META-INF/*.RSA"
|
||||
}
|
||||
from(project(':node:capsule').tasks['buildCordaJAR']) {
|
||||
rename 'corda-(.*)', 'corda.jar'
|
||||
}
|
||||
applicationSource = files(
|
||||
project(':node-api').configurations.runtime,
|
||||
project(':node-api').jar
|
||||
)
|
||||
}
|
||||
|
||||
artifacts {
|
||||
runtimeArtifacts buildBootstrapperJar
|
||||
publish buildBootstrapperJar {
|
||||
classifier ""
|
||||
archiveName = "network-bootstrapper-${corda_release_version}.jar"
|
||||
manifest {
|
||||
attributes(
|
||||
'Automatic-Module-Name': 'net.corda.bootstrapper',
|
||||
'Main-Class': 'net.corda.bootstrapper.MainKt'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
classifier "ignore"
|
||||
}
|
||||
|
||||
publish {
|
||||
disableDefaultJar = true
|
||||
name 'corda-tools-network-bootstrapper'
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package net.corda.bootstrapper
|
||||
|
||||
import com.jcabi.manifests.Manifests
|
||||
import net.corda.core.internal.rootMessage
|
||||
import net.corda.nodeapi.internal.network.NetworkBootstrapper
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.*
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val main = Main()
|
||||
try {
|
||||
CommandLine.run(main, *args)
|
||||
} catch (e: ExecutionException) {
|
||||
val throwable = e.cause ?: e
|
||||
if (main.verbose) {
|
||||
throwable.printStackTrace()
|
||||
} else {
|
||||
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
|
||||
}
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "Network Bootstrapper",
|
||||
versionProvider = CordaVersionProvider::class,
|
||||
mixinStandardHelpOptions = true,
|
||||
showDefaultValues = true,
|
||||
description = [ "Bootstrap a local test Corda network using a set of node conf files and CorDapp JARs" ]
|
||||
)
|
||||
class Main : Runnable {
|
||||
@Option(
|
||||
names = ["--dir"],
|
||||
description = [
|
||||
"Root directory containing the node conf files and CorDapp JARs that will form the test network.",
|
||||
"It may also contain existing node directories."
|
||||
]
|
||||
)
|
||||
private var dir: Path = Paths.get(".")
|
||||
|
||||
@Option(names = ["--no-copy"], description = ["""Don't copy the CorDapp JARs into the nodes' "cordapps" directories."""])
|
||||
private var noCopy: Boolean = false
|
||||
|
||||
@Option(names = ["--verbose"], description = ["Enable verbose output."])
|
||||
var verbose: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
if (verbose) {
|
||||
System.setProperty("logLevel", "trace")
|
||||
}
|
||||
NetworkBootstrapper().bootstrap(dir.toAbsolutePath().normalize(), copyCordapps = !noCopy)
|
||||
}
|
||||
}
|
||||
|
||||
private class CordaVersionProvider : IVersionProvider {
|
||||
override fun getVersion(): Array<String> {
|
||||
return arrayOf(
|
||||
"Version: ${Manifests.read("Corda-Release-Version")}",
|
||||
"Revision: ${Manifests.read("Corda-Revision")}"
|
||||
)
|
||||
}
|
||||
}
|
16
tools/bootstrapper/src/main/resources/log4j2.xml
Normal file
16
tools/bootstrapper/src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info">
|
||||
<Properties>
|
||||
<Property name="logLevel">off</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
|
||||
<PatternLayout pattern="[%C{1}.%M] %m%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="${sys:logLevel}">
|
||||
<AppenderRef ref="STDOUT"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
@ -14,6 +14,7 @@ package net.corda.webserver
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.location
|
||||
import net.corda.core.internal.rootCause
|
||||
import net.corda.webserver.internal.NodeWebServer
|
||||
import org.slf4j.LoggerFactory
|
||||
@ -58,7 +59,7 @@ fun main(args: Array<String>) {
|
||||
exitProcess(2)
|
||||
}
|
||||
|
||||
log.info("Main class: ${WebServerConfig::class.java.protectionDomain.codeSource.location.toURI().path}")
|
||||
log.info("Main class: ${WebServerConfig::class.java.location.toURI().path}")
|
||||
val info = ManagementFactory.getRuntimeMXBean()
|
||||
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
|
||||
log.info("Application Args: ${args.joinToString(" ")}")
|
||||
|
Loading…
x
Reference in New Issue
Block a user