From 366af50150747ded91bff76db66fc6f629fecc4e Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Thu, 21 Jun 2018 15:53:29 +0100 Subject: [PATCH 1/4] CORDA-1644: Add a more descriptive error message when the notary type is mis-configured. --- .../kotlin/net/corda/core/flows/NotaryWireFormat.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt index 2871cec4d3..2183904143 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt @@ -61,12 +61,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. */ From 3046843d40d898b7a74c07d753dba3dd2b5079f3 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Sat, 23 Jun 2018 11:36:10 +0100 Subject: [PATCH 2/4] CORDA-1602: Added cmd line flags to the network bootstrapper (#3419) The list of CorDapps jars is no longer passed in via the cmd line but is now expected to be placed in the bootstrapped directory. Ended up being a bit of a refactor to cater for unit testing, and also tidied up the bootstrapper docs. --- .idea/compiler.xml | 19 + build.gradle | 1 + .../net/corda/core/internal/InternalUtils.kt | 12 +- .../net/corda/core/internal/PathUtils.kt | 3 + docs/source/changelog.rst | 2 +- docs/source/network-bootstrapper.rst | 101 +++++ docs/source/network-map.rst | 2 +- docs/source/release-notes.rst | 2 +- docs/source/setting-up-a-corda-network.rst | 77 +--- docs/source/tools-index.rst | 1 + docs/source/tutorial-cordapp.rst | 2 +- docs/source/upgrade-notes.rst | 2 +- experimental/behave/prepare.sh | 2 +- .../internal/network/NetworkBootstrapper.kt | 255 +++++++----- .../network/NetworkBootstrapperTest.kt | 390 ++++++++++++------ .../internal/network/TestContractsJar.kt | 10 + .../network/WhitelistGeneratorTest.kt | 150 +++++-- .../net/corda/node/internal/NodeStartup.kt | 2 +- .../node/internal/cordapp/CordappLoader.kt | 4 +- .../statemachine/FlowStateMachineImpl.kt | 2 +- tools/blobinspector/build.gradle | 2 +- .../kotlin/net/corda/blobinspector/Main.kt | 15 +- .../src/main/resources/log4j2.xml | 3 + tools/bootstrapper/build.gradle | 51 +-- .../kotlin/net/corda/bootstrapper/Main.kt | 65 +++ .../src/main/resources/log4j2.xml | 16 + .../kotlin/net/corda/webserver/WebServer.kt | 3 +- 27 files changed, 830 insertions(+), 364 deletions(-) create mode 100644 docs/source/network-bootstrapper.rst create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/network/TestContractsJar.kt create mode 100644 tools/bootstrapper/src/main/kotlin/net/corda/bootstrapper/Main.kt create mode 100644 tools/bootstrapper/src/main/resources/log4j2.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 5dbfa353df..356d4cafaa 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -26,6 +26,8 @@ + + @@ -58,11 +60,15 @@ + + + + @@ -106,6 +112,10 @@ + + + + @@ -148,8 +158,13 @@ + + + + + @@ -175,6 +190,8 @@ + + @@ -182,6 +199,8 @@ + + diff --git a/build.gradle b/build.gradle index 07d610fb99..7ded190f54 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,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. diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 9eb005d3ce..45be94b6ff 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -14,6 +14,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 @@ -386,12 +387,19 @@ fun uncheckedCast(obj: T) = obj as U fun Iterable>.toMultiMap(): Map> = 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 diff --git a/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt b/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt index 7ef532e06b..50e05c06b6 100644 --- a/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt @@ -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] diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 40a796ebce..131b06a46e 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -243,7 +243,7 @@ Version 3.0 * 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 diff --git a/docs/source/network-bootstrapper.rst b/docs/source/network-bootstrapper.rst new file mode 100644 index 0000000000..e0c3900927 --- /dev/null +++ b/docs/source/network-bootstrapper.rst @@ -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 `` + +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 `_. +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 diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index c6f83ae8b4..b302a2d426 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -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 ------------------ diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 8f1e86ade2..c918aff957 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -169,7 +169,7 @@ Significant Changes in 3.0 .. important:: This replaces the Network Map service that was present in Corda 1.0 and Corda 2.0. - Further information can be found in the :doc:`changelog`, :doc:`network-map` and :doc:`setting-up-a-corda-network` documentation. + Further information can be found in the :doc:`changelog`, :doc:`network-map` and :doc:`network-bootstrapper` documentation. * **Contract Upgrade** diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index ce0d70a8b6..b8946a2841 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -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 `` - -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 `_. -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 <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. diff --git a/docs/source/tools-index.rst b/docs/source/tools-index.rst index f2213c7496..2285b38d9d 100644 --- a/docs/source/tools-index.rst +++ b/docs/source/tools-index.rst @@ -4,6 +4,7 @@ Tools .. toctree:: :maxdepth: 1 + network-bootstrapper blob-inspector demobench node-explorer diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index 2c2447d448..961b17c6dd 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -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`` diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index b9dd1a64bf..c7f90a8eca 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -117,7 +117,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`` diff --git a/experimental/behave/prepare.sh b/experimental/behave/prepare.sh index bdd41c4a1b..1d78595971 100755 --- a/experimental/behave/prepare.sh +++ b/experimental/behave/prepare.sh @@ -31,5 +31,5 @@ curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/ curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgresql-42.1.4.jar" > ${DRIVERS_DIR}/postgresql-42.1.4.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 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index 5438953cf5..3dbc6589bf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -27,8 +27,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.* @@ -43,7 +43,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) -> List, + 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( @@ -55,11 +69,42 @@ class NetworkBootstrapper { private const val LOGS_DIR_NAME = "logs" - @JvmStatic - fun main(args: Array) { - 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): List { + 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() + } } } @@ -92,29 +137,61 @@ class NetworkBootstrapper { private fun generateServiceIdentitiesForNotaryClusters(configs: Map) { 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) { + 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, 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... ") @@ -123,72 +200,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): List { - 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) { 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 { + 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, nodeInfoFiles: List) { @@ -200,20 +275,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) { - val legalNames = HashSet() - 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) { + 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, configs: Map): List { @@ -264,15 +332,19 @@ class NetworkBootstrapper { whitelist: Map>, existingNetParams: NetworkParameters?, nodeDirs: List): 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, @@ -284,9 +356,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 { @@ -301,7 +373,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 { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt index b57c177d61..04744c9376 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapperTest.kt @@ -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() - @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() - @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>, - excludeContracts: List, - contractJars: List): Map> { - 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) : ContractsJar { - override fun scan(): List = 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): 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().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().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): 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) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/TestContractsJar.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/TestContractsJar.kt new file mode 100644 index 0000000000..ea1a4ffc88 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/TestContractsJar.kt @@ -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) : ContractsJar { + override fun scan(): List = contractClassNames +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt index 67f4bb9641..4bf6960c85 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/network/WhitelistGeneratorTest.kt @@ -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 { - 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)) } } -} \ No newline at end of file + private fun generateWhitelist(existingWhitelist: Map>, + excludeContracts: List, + contractJars: List): Map> { + return generateWhitelist( + testNetworkParameters(whitelistedContractImplementations = existingWhitelist), + excludeContracts, + contractJars + ) + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 9ede8f9af9..c2e326f048 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -272,7 +272,7 @@ open class NodeStartup(val args: Array) { 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}") diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 109063eba4..288eb74ddc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -204,7 +204,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List { 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. } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index d650a8c565..b38ca474ab 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -436,7 +436,7 @@ val Class>.flowVersionAndInitiatingClass: Pair>.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 { diff --git a/tools/blobinspector/build.gradle b/tools/blobinspector/build.gradle index c20270831d..26a15d84dc 100644 --- a/tools/blobinspector/build.gradle +++ b/tools/blobinspector/build.gradle @@ -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" diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt index c65b02c6f8..31368eb228 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt @@ -41,7 +41,7 @@ fun main(args: Array) { @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 +64,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" } @@ -124,8 +126,13 @@ private class SourceConverter : ITypeConverter { } } -private class VersionProvider : IVersionProvider { - override fun getVersion(): Array = arrayOf(Manifests.read("Corda-Release-Version")) +private class CordaVersionProvider : IVersionProvider { + override fun getVersion(): Array { + return arrayOf( + "Version: ${Manifests.read("Corda-Release-Version")}", + "Revision: ${Manifests.read("Corda-Revision")}" + ) + } } private enum class FormatType { YAML, JSON } diff --git a/tools/blobinspector/src/main/resources/log4j2.xml b/tools/blobinspector/src/main/resources/log4j2.xml index a9885efca9..98b3648e6b 100644 --- a/tools/blobinspector/src/main/resources/log4j2.xml +++ b/tools/blobinspector/src/main/resources/log4j2.xml @@ -1,5 +1,8 @@ + + off + diff --git a/tools/bootstrapper/build.gradle b/tools/bootstrapper/build.gradle index 04962dbe2f..19ad5c3adf 100644 --- a/tools/bootstrapper/build.gradle +++ b/tools/bootstrapper/build.gradle @@ -1,51 +1,36 @@ -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' } diff --git a/tools/bootstrapper/src/main/kotlin/net/corda/bootstrapper/Main.kt b/tools/bootstrapper/src/main/kotlin/net/corda/bootstrapper/Main.kt new file mode 100644 index 0000000000..f80699ee65 --- /dev/null +++ b/tools/bootstrapper/src/main/kotlin/net/corda/bootstrapper/Main.kt @@ -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) { + 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 { + return arrayOf( + "Version: ${Manifests.read("Corda-Release-Version")}", + "Revision: ${Manifests.read("Corda-Revision")}" + ) + } +} diff --git a/tools/bootstrapper/src/main/resources/log4j2.xml b/tools/bootstrapper/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..98b3648e6b --- /dev/null +++ b/tools/bootstrapper/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + off + + + + + + + + + + + + \ No newline at end of file diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt b/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt index 44dc37ad3a..3c0f2ec00a 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebServer.kt @@ -4,6 +4,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 @@ -48,7 +49,7 @@ fun main(args: Array) { 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(" ")}") From 68d4ed0cd91ce611dbfd041b83189834d365cb52 Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Sun, 24 Jun 2018 11:04:08 +0100 Subject: [PATCH 3/4] [ENT-1955] Fix LaTeX errors when building docs PDF (#3426) * Fix latex errors when building docs PDF * Experimenting --- docs/source/conf.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 38cee0e3af..f97b5735fe 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -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', } From b97af477b242a57cd5255b9449faf6492000d020 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 25 Jun 2018 11:33:37 +0100 Subject: [PATCH 4/4] CORDA-1603: Class carpenter able to synthesis Cash.State objects (#3400) This requires the class carpenter to be able to run in a "lenient" mode where it permits synthesised classes to implement interfaces with unimplemented methods. --- .../amqp/AMQPClientSerializationScheme.kt | 2 +- .../core/serialization/SerializationAPI.kt | 14 ++++ docs/source/changelog.rst | 4 ++ docs/source/serialization.rst | 5 ++ .../amqp/AMQPServerSerializationScheme.kt | 8 +-- .../internal/SerializationScheme.kt | 5 +- .../internal/amqp/AMQPSerializerFactories.kt | 5 +- .../internal/amqp/ObjectSerializer.kt | 17 +++-- .../internal/amqp/SerializerFactory.kt | 41 ++++++----- .../carpenter/AMQPSchemaExtensions.kt | 2 +- .../internal/carpenter/ClassCarpenter.kt | 30 +++++--- .../internal/amqp/ErrorMessageTests.java | 16 ++--- .../amqp/JavaCustomSerializerTests.java | 11 ++- .../internal/amqp/JavaGenericsTest.java | 28 ++------ .../internal/amqp/JavaNestedClassesTests.java | 51 +++++--------- .../amqp/JavaNestedInheritenceTests.java | 25 +++---- .../amqp/JavaPrivatePropertyTests.java | 38 ++++------ .../internal/amqp/JavaSerialiseEnumTests.java | 13 ++-- .../amqp/JavaSerializationOutputTests.java | 12 +--- .../amqp/ListsSerializationJavaTest.java | 10 +-- .../internal/amqp/SetterConstructorTests.java | 59 ++++------------ ...erializeNeedingCarpentrySimpleTypesTest.kt | 5 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 12 ++-- .../internal/amqp/FingerPrinterTesting.kt | 4 +- .../internal/amqp/SerializationOutputTests.kt | 66 ++++-------------- .../internal/amqp/testutils/AMQPTestUtils.kt | 34 +++++++-- .../internal/carpenter/ClassCarpenterTest.kt | 36 +++++++--- .../carpenter/ClassCarpenterTestUtils.kt | 2 +- .../carpenter/SerDeserCarpentryTest.kt | 49 +++++++++++++ ...CarpentryTest.implementingGenericInterface | Bin 0 -> 662 bytes .../SerDeserCarpentryTest.lenientCarpenter | Bin 0 -> 595 bytes .../kotlin/net/corda/blobinspector/Main.kt | 6 +- 32 files changed, 307 insertions(+), 303 deletions(-) create mode 100644 serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.kt create mode 100644 serialization/src/test/resources/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.implementingGenericInterface create mode 100644 serialization/src/test/resources/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.lenientCarpenter diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt index d6538d1ffc..389609f84c 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/amqp/AMQPClientSerializationScheme.kt @@ -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)) diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index 7e92f3cf15..cb6a4f86d0 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -142,6 +142,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]. */ @@ -157,6 +165,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. */ diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 131b06a46e..3381a3fd01 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,10 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* 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. diff --git a/docs/source/serialization.rst b/docs/source/serialization.rst index 5a26d81cec..2ae4495202 100644 --- a/docs/source/serialization.rst +++ b/docs/source/serialization.rst @@ -557,6 +557,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 diff --git a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt index 70bcaaf0da..2f28d692a2 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/amqp/AMQPServerSerializationScheme.kt @@ -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) && diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt index f37091df49..c3f9f1e31e 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt @@ -30,7 +30,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) /** @@ -52,6 +53,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) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt index ac5aab1ed8..aacde49dac 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt @@ -7,6 +7,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) + } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt index 97ff897798..aa00063ad3 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt @@ -7,6 +7,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 @@ -29,8 +31,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) @@ -119,7 +120,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 @@ -153,7 +154,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 Constructor.newInstanceUnwrapped(vararg args: Any?): T { + try { + return newInstance(*args) + } catch (e: InvocationTargetException) { + throw e.cause!! + } + } } \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 32c958d0d2..b45336bc2c 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -8,6 +8,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 @@ -53,40 +54,43 @@ open class SerializerFactory( private val serializersByType: MutableMap>, val serializersByDescriptor: MutableMap>, private val customSerializers: MutableList, - val transformsCache: MutableMap>>) { + val transformsCache: MutableMap>> +) { @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, - schemas: SerializationSchemas) = evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) - - private val logger = loggerFor() + private fun getEvolutionSerializer(typeNotation: TypeNotation, + newSerializer: AMQPSerializer, + schemas: SerializationSchemas): AMQPSerializer { + return evolutionSerializerGetter.getEvolutionSerializer(this, typeNotation, newSerializer, schemas) + } /** * Look up, and manufacture if necessary, a serializer for the given type. @@ -380,6 +384,8 @@ open class SerializerFactory( } companion object { + private val logger = contextLogger() + fun isPrimitive(type: Type): Boolean = primitiveTypeName(type) != null fun primitiveTypeName(type: Type): String? { @@ -469,4 +475,3 @@ open class SerializerFactory( override fun toString(): String = "?" } } - diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/AMQPSchemaExtensions.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/AMQPSchemaExtensions.kt index 174a4f666b..37736abcde 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/AMQPSchemaExtensions.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/AMQPSchemaExtensions.kt @@ -63,7 +63,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 diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt index 7545d97c47..742f34482a 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt @@ -6,6 +6,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.* @@ -102,8 +104,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. @@ -439,25 +442,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) diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/ErrorMessageTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/ErrorMessageTests.java index 24a64ff8fb..df9cb5b046 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/ErrorMessageTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/ErrorMessageTests.java @@ -8,6 +8,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) { @@ -32,19 +35,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())); } - } diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java index b6248d9c16..8c1bd4e665 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java @@ -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 l = new ArrayList(2); @@ -102,7 +101,5 @@ public class JavaCustomSerializerTests { factory.registerExternal(ccs); ser.serialize(e, TestSerializationContext.testSerializationContext); - - } } diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaGenericsTest.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaGenericsTest.java index d3ab33fbc0..d788948b98 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaGenericsTest.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaGenericsTest.java @@ -1,11 +1,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 { @@ -27,11 +27,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); @@ -43,13 +39,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( @@ -59,11 +51,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); @@ -85,11 +73,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(); diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedClassesTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedClassesTests.java index 40fdc40cfe..603f6c1590 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedClassesTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedClassesTests.java @@ -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"); } diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedInheritenceTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedInheritenceTests.java index 039dacd688..5331b95096 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedInheritenceTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaNestedInheritenceTests.java @@ -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 getParticipants() { @@ -33,38 +34,32 @@ class TemplateWrapper { 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 (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf( + assertThatThrownBy(() -> ser.serialize(new TemplateWrapper (new DummyState()), TestSerializationContext.testSerializationContext)).isInstanceOf( NotSerializableException.class).hasMessageContaining( "has synthetic fields and is likely a nested inner class"); } diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java index 656b2224d7..72b8395b6b 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java @@ -1,14 +1,15 @@ 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; @@ -76,10 +77,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); @@ -89,10 +88,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); @@ -104,10 +101,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); @@ -120,10 +115,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); @@ -136,9 +129,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); @@ -166,10 +157,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); diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerialiseEnumTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerialiseEnumTests.java index 6037440a0c..e09d1f4da2 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerialiseEnumTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerialiseEnumTests.java @@ -1,13 +1,13 @@ 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 { @@ -30,10 +30,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 bytes = ser.serialize(bra, TestSerializationContext.testSerializationContext); + SerializationOutput ser = new SerializationOutput(testDefaultFactory()); + ser.serialize(bra, TestSerializationContext.testSerializationContext); } } diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerializationOutputTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerializationOutputTests.java index 375e894d2c..37c3afa53f 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerializationOutputTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerializationOutputTests.java @@ -1,7 +1,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; @@ -13,6 +12,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 { @@ -176,14 +176,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 bytes = ser.serialize(obj, TestSerializationContext.testSerializationContext); diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/ListsSerializationJavaTest.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/ListsSerializationJavaTest.java index a521960e55..7e46adef38 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/ListsSerializationJavaTest.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/ListsSerializationJavaTest.java @@ -2,7 +2,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; @@ -10,6 +9,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 @@ -126,12 +127,7 @@ public class ListsSerializationJavaTest { // Have to have own version as Kotlin inline functions cannot be easily called from Java private static void assertEqualAfterRoundTripSerialization(T container, Class 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 bytes = ser.serialize(container, TestSerializationContext.testSerializationContext); DeserializationInput des = new DeserializationInput(factory1); diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/SetterConstructorTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/SetterConstructorTests.java index 66df2726b8..25a28c7115 100644 --- a/serialization/src/test/java/net/corda/serialization/internal/amqp/SetterConstructorTests.java +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/SetterConstructorTests.java @@ -1,16 +1,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 { @@ -119,13 +120,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); @@ -195,13 +190,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(); @@ -268,13 +257,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(); @@ -298,38 +281,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); } @@ -346,13 +317,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) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt index 67c6cf25d5..d8e3fc57df 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt @@ -1,11 +1,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.* diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentryTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentryTests.kt index d2f8947bcf..1b6642bdd5 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentryTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentryTests.kt @@ -1,15 +1,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 { diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt index 288b95e912..cc180e52c2 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt @@ -46,8 +46,8 @@ class FingerPrinterTestingTests { val factory = SerializerFactory( AllWhitelist, ClassLoader.getSystemClassLoader(), - EvolutionSerializerGetterTesting(), - FingerPrinterTesting()) + evolutionSerializerGetter = EvolutionSerializerGetterTesting(), + fingerPrinter = FingerPrinterTesting()) val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L)) diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt index 536c29eaef..cab56596d1 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt @@ -7,13 +7,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 @@ -22,48 +16,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 @@ -79,20 +50,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 @@ -249,9 +207,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 serdes(obj: T, factory: SerializerFactory = defaultFactory(), diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt index 8d78ed61c6..025bebdfe0 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/testutils/AMQPTestUtils.kt @@ -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 DeserializationInput.deserializeAndReturnEnvelope( diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTest.kt index 123df44182..e74206e5b0 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTest.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTest.kt @@ -2,6 +2,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 @@ -145,21 +146,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 diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt index bb52d4c4e6..22f93a5b16 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt @@ -5,6 +5,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" @@ -47,7 +48,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" } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.kt new file mode 100644 index 0000000000..fe4c06dfb9 --- /dev/null +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.kt @@ -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 +// writeTestResource(GenericData(123).serialize()) + + val data = readTestResource().deserialize>() + 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(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 + + @CordaSerializable + interface AInterface { + val a: Int + } +} diff --git a/serialization/src/test/resources/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.implementingGenericInterface b/serialization/src/test/resources/net/corda/serialization/internal/carpenter/SerDeserCarpentryTest.implementingGenericInterface new file mode 100644 index 0000000000000000000000000000000000000000..6b20aa9377aedc30dad35bd3fe5236f4bc4c68f6 GIT binary patch literal 662 zcmYe!FG@*dWME)uIGO|`85kHZFfcIH0~yQ=ic(jwBlBE7tW z$~{xujgpETZEX)QGlo>dH8X)Uo5D=d0Wz52a;!i(MtPtR>%vU9xq8K^MVW~?nN^7; znfZBonRz9tMR|!iddZ1J1*ss8UT|uWODa&6Geo4QG9dH8TM$w|%21j<7NQ;Sqw5=#;t z4)`%H^rqNsM^BK&X^F|Ha91-QP-7w{92^eFGQu^n91vh%$e6T{8R#q5g^UIcj!tkn u)&rMVsp?ev`Q61J(A>@4I5j<|D9_NnBqF)WD8s1S*4Dul?mNzdj0^zS@5iD5 literal 0 HcmV?d00001 diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt index 31368eb228..9590f39d8e 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt @@ -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) { 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) } @@ -91,7 +92,8 @@ class Main : Runnable { } val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties) - val deserialized = bytes.deserialize() + // Deserialise with the lenient carpenter as we only care for the AMQP field getters + val deserialized = bytes.deserialize(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter()) println(deserialized.javaClass.name) mapper.writeValue(System.out, deserialized) }