Merge Open Source into Enterprise

# Resolved conflicts:
#	core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt
#	node/src/integration-test/kotlin/net/corda/node/BootTests.kt
#	node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt
#	node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt
#	node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
#	node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
#	node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt
#	node/src/main/kotlin/net/corda/node/Corda.kt
#	node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
#	node/src/main/kotlin/net/corda/node/internal/StartedNode.kt
#	node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt
#	samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt
#	samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
This commit is contained in:
szymonsztuka 2017-12-05 17:35:54 +00:00
commit cb43091ff8
164 changed files with 1319 additions and 615 deletions

View File

@ -1899,7 +1899,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
@org.jetbrains.annotations.NotNull public final java.time.Clock getClock()
public final boolean isValid(net.corda.core.contracts.TimeWindow)
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage extends net.corda.core.node.StateLoader
@org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()

View File

@ -47,7 +47,7 @@ buildscript {
ext.dependency_checker_version = '3.0.1'
ext.commons_collections_version = '4.1'
ext.beanutils_version = '1.9.3'
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e'
ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e'
ext.jsr305_version = constants.getProperty("jsr305Version")
ext.spring_jdbc_version ='5.0.0.RELEASE'

View File

@ -28,7 +28,7 @@ import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.driver.driver
import org.junit.ClassRule

View File

@ -10,7 +10,7 @@ import net.corda.finance.flows.CashPaymentFlow;
import net.corda.finance.schemas.CashSchemaV1;
import net.corda.node.internal.Node;
import net.corda.node.internal.StartedNode;
import net.corda.nodeapi.User;
import net.corda.nodeapi.internal.config.User;
import net.corda.testing.CoreTestUtils;
import net.corda.testing.IntegrationTestKt;
import net.corda.testing.IntegrationTestSchemas;

View File

@ -20,7 +20,7 @@ import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.internal.NodeBasedTest
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException

View File

@ -15,7 +15,7 @@ import net.corda.core.utilities.*
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.TransportConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient

View File

@ -9,7 +9,7 @@ import net.corda.core.messaging.FlowHandle;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow;
import net.corda.nodeapi.User;
import net.corda.nodeapi.internal.config.User;
import net.corda.smoketesting.NodeConfig;
import net.corda.smoketesting.NodeProcess;
import org.junit.After;

View File

@ -21,7 +21,7 @@ import net.corda.finance.contracts.getCashBalance
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import org.apache.commons.io.output.NullOutputStream

View File

@ -5,7 +5,7 @@ import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.map
import net.corda.core.messaging.RPCOps
import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.internal.RPCDriverExposedDSLInterface
import net.corda.testing.internal.rpcTestUser

View File

@ -4,7 +4,7 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.messaging.rpcContext
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.internal.RPCDriverExposedDSLInterface
import net.corda.testing.internal.rpcDriver
import org.junit.Test

View File

@ -2,9 +2,9 @@
package net.corda.core.internal
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.TransactionBuilder
@ -295,15 +295,15 @@ fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ i
* Provide access to internal method for AttachmentClassLoaderTests
* @suppress
*/
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(services, serializationContext)
fun TransactionBuilder.toWireTransaction(cordappProvider: CordappProvider, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(cordappProvider, serializationContext)
}
/**
* Provide access to internal method for AttachmentClassLoaderTests
* @suppress
*/
fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
/** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName: String get() = java.`package`.name

View File

@ -38,7 +38,9 @@ interface StateLoader {
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
// as the existing transaction store will become encrypted at some point
@Throws(TransactionResolutionException::class)
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>>
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
}
}
/**

View File

@ -1,9 +1,13 @@
package net.corda.core.node.services
import net.corda.core.DoNotImplement
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionResolutionException
import net.corda.core.contracts.TransactionState
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.messaging.DataFeed
import net.corda.core.node.StateLoader
import net.corda.core.transactions.SignedTransaction
import rx.Observable
@ -11,12 +15,18 @@ import rx.Observable
* Thread-safe storage of transactions.
*/
@DoNotImplement
interface TransactionStorage {
interface TransactionStorage : StateLoader {
/**
* Return the transaction with the given [id], or null if no such transaction exists.
*/
fun getTransaction(id: SecureHash): SignedTransaction?
@Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> {
val stx = getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
}
/**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
* incorporate the update.

View File

@ -2,6 +2,7 @@ package net.corda.core.transactions
import co.paralleluniverse.strands.Strand
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
@ -82,16 +83,16 @@ open class TransactionBuilder(
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*/
@Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services.cordappProvider)
internal fun toWireTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction {
internal fun toWireTransactionWithContext(cordappProvider: CordappProvider, serializationContext: SerializationContext? = null): WireTransaction {
// Resolves the AutomaticHashConstraints to HashAttachmentConstraints for convenience. The AutomaticHashConstraint
// allows for less boiler plate when constructing transactions since for the typical case the named contract
// will be available when building the transaction. In exceptional cases the TransactionStates must be created
// with an explicit [AttachmentConstraint]
val resolvedOutputs = outputs.map { state ->
if (state.constraint is AutomaticHashConstraint) {
services.cordappProvider.getContractAttachmentID(state.contract)?.let {
cordappProvider.getContractAttachmentID(state.contract)?.let {
state.copy(constraint = HashAttachmentConstraint(it))
} ?: throw MissingContractAttachments(listOf(state))
} else {
@ -106,8 +107,7 @@ open class TransactionBuilder(
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services)
internal fun toLedgerTransactionWithContext(services: ServiceHub, serializationContext: SerializationContext) = toWireTransactionWithContext(services, serializationContext).toLedgerTransaction(services)
internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext) = toWireTransactionWithContext(services.cordappProvider, serializationContext).toLedgerTransaction(services)
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) {
toLedgerTransaction(services).verify()

View File

@ -7,7 +7,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import net.corda.testing.common.internal.ProjectStructure

View File

@ -11,7 +11,7 @@ import net.corda.core.internal.list
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
import net.corda.smoketesting.NodeProcess.Companion.CORDAPPS_DIR_NAME

View File

@ -19,7 +19,7 @@ import net.corda.finance.flows.CashIssueFlow
import net.corda.node.internal.SecureCordaRPCOps
import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2

View File

@ -20,6 +20,42 @@ class ReceiveMultipleFlowTests {
mockNet.stopNodes()
}
@Test
fun showcase_flows_as_closures() {
val answer = 10.0
val message = "Hello Ivan"
val counterParty = nodes[1].info.singleIdentity()
val initiatingFlow = @InitiatingFlow object : FlowLogic<Any>() {
@Suspendable
override fun call(): Any {
val session = initiateFlow(counterParty)
return session.sendAndReceive<Any>(message).unwrap { it }
}
}
nodes[1].registerInitiatedFlow(initiatingFlow::class) { session ->
object : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// this is a closure, meaning you can access variables outside its scope e.g., `answer`.
val receivedMessage = session.receive<String>().unwrap { it }
logger.info("Got message from counterParty: $receivedMessage.")
assertThat(receivedMessage).isEqualTo(message)
session.send(answer)
}
} as FlowLogic<Unit>
}
val flow = nodes[0].services.startFlow(initiatingFlow)
mockNet.runNetwork()
val receivedAnswer = flow.resultFuture.getOrThrow()
assertThat(receivedAnswer).isEqualTo(answer)
}
@Test
fun `receive all messages in parallel using map style`() {
val doubleValue = 5.0

View File

@ -71,7 +71,8 @@ a:visited {
}
.wy-nav-content {
background-color: #fff max-width: 1000px;
background-color: #fff;
max-width: none;
}
.wy-nav-side {

View File

@ -176,6 +176,17 @@ define a ``PageSpecification`` to correctly process results with efficient memor
place to alert API users to the need for pagination where a single query returns more than 200 results and no
``PageSpecification`` has been supplied.
Here's a query that extracts every unconsumed ``ContractState`` from the vault in pages of size 200, starting from the
default page number (page one):
.. container:: codeset
.. sourcecode:: kotlin
val vaultSnapshot = proxy.vaultQueryBy<ContractState>(
QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED),
PageSpecification(DEFAULT_PAGE_NUM, 200))
.. note:: A pages maximum size ``MAX_PAGE_SIZE`` is defined as ``Int.MAX_VALUE`` and should be used with extreme
caution as results returned may exceed your JVM's memory footprint.

View File

@ -7,9 +7,9 @@ Deploying a node
whether they have developed and tested a CorDapp following the instructions in :doc:`generating-a-node`
or are deploying a third-party CorDapp.
Linux (systemd): Installing and running Corda as a systemd service
------------------------------------------------------------------
We recommend creating systemd services to run a node and the optional webserver. This provides logging and service
Linux: Installing and running Corda as a system service
-------------------------------------------------------
We recommend creating system services to run a node and the optional webserver. This provides logging and service
handling, and ensures the Corda service is run at boot.
**Prerequisites**:
@ -27,10 +27,13 @@ handling, and ensures the Corda service is run at boot.
3. Download the `Corda jar <https://r3.bintray.com/corda/net/corda/corda/>`_
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
3. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
4. (Optional) Download the `Corda webserver jar <http://r3.bintray.com/corda/net/corda/corda-webserver/>`_
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
5. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
4. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
6. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
.. code-block:: json
@ -59,7 +62,7 @@ handling, and ensures the Corda service is run at boot.
}
]
5. Make the following changes to ``/opt/corda/node.conf``:
7. Make the following changes to ``/opt/corda/node.conf``:
* Change the ``p2pAddress`` and ``rpcAddress`` values to start with your server's hostname or external IP address.
This is the address other nodes or RPC interfaces will use to communicate with your node
@ -74,7 +77,12 @@ handling, and ensures the Corda service is run at boot.
* Country (``C=``) is the `ISO 3166-1 alpha-2 code <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_
* Change the RPC username and password
6. Create a ``corda.service`` file based on the example below and save it in the ``/etc/systemd/system/`` directory
.. note:: Ubuntu 16.04 and most current Linux distributions use SystemD, so if you are running one of these
distributions follow the steps marked **SystemD**.
If you are running Ubuntu 14.04, follow the instructions for **Upstart**.
8. **SystemD**: Create a ``corda.service`` file based on the example below and save it in the ``/etc/systemd/system/``
directory
.. code-block:: shell
@ -92,20 +100,41 @@ handling, and ensures the Corda service is run at boot.
[Install]
WantedBy=multi-user.target
7. Make the following changes to ``corda.service``:
8. **Upstart**: Create a ``corda.conf`` file based on the example below and save it in the ``/etc/init/`` directory
.. code-block:: shell
description "Corda Node - Bank of Breakfast Tea"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
setuid corda
chdir /opt/corda
exec java -Xmx2048m -jar /opt/corda/corda.jar
9. Make the following changes to ``corda.service`` or ``corda.conf``:
* Make sure the service description is informative - particularly if you plan to run multiple nodes.
* Change the username to the user account you want to use to run Corda. **We recommend that this is not root**
* Change the username to the user account you want to use to run Corda. **We recommend that this user account is
not root**
* Set the maximum amount of memory available to the Corda process by changing the ``-Xmx2048m`` parameter
* Make sure the ``corda.service`` file is owned by root with the correct permissions:
* **SystemD**: Make sure the ``corda.service`` file is owned by root with the correct permissions:
* ``sudo chown root:root /etc/systemd/system/corda.service``
* ``sudo chmod 644 /etc/systemd/system/corda.service``
* **Upstart**: Make sure the ``corda.conf`` file is owned by root with the correct permissions:
* ``sudo chown root:root /etc/init/corda.conf``
* ``sudo chmod 644 /etc/init/corda.conf``
.. note:: The Corda webserver provides a simple interface for interacting with your installed CorDapps in a browser.
Running the webserver is optional.
8. Create a ``corda-webserver.service`` file based on the example below and save it in the ``/etc/systemd/system/``
directory.
10. **SystemD**: Create a ``corda-webserver.service`` file based on the example below and save it in the ``/etc/systemd/system/``
directory
.. code-block:: shell
@ -115,7 +144,7 @@ handling, and ensures the Corda service is run at boot.
[Service]
Type=simple
User=username
User=corda
WorkingDirectory=/opt/corda
ExecStart=/usr/bin/java -jar /opt/corda/corda-webserver.jar
Restart=on-failure
@ -123,17 +152,40 @@ handling, and ensures the Corda service is run at boot.
[Install]
WantedBy=multi-user.target
9. Provision the required certificates to your node. Contact the network permissioning service or see
:doc:`permissioning`
10. **Upstart**: Create a ``corda-webserver.conf`` file based on the example below and save it in the ``/etc/init/``
directory
10. You can now start a node and its webserver by running the following ``systemctl`` commands:
.. code-block:: shell
description "Webserver for Corda Node - Bank of Breakfast Tea"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
setuid corda
chdir /opt/corda
exec java -jar /opt/corda/corda-webserver.jar
11. Provision the required certificates to your node. Contact the network permissioning service or see
:doc:`permissioning`
12. **SystemD**: You can now start a node and its webserver and set the services to start on boot by running the
following ``systemctl`` commands:
* ``sudo systemctl daemon-reload``
* ``sudo systemctl corda start``
* ``sudo systemctl corda-webserver start``
* ``sudo systemctl enable --now corda``
* ``sudo systemctl enable --now corda-webserver``
12. **Upstart**: You can now start a node and its webserver by running the following commands:
* ``sudo start corda``
* ``sudo start corda-webserver``
The Upstart configuration files created above tell Upstart to start the Corda services on boot so there is no need to explicitly enable them.
You can run multiple nodes by creating multiple directories and Corda services, modifying the ``node.conf`` and
``service`` files so they are unique.
SystemD or Upstart configuration files so they are unique.
Windows: Installing and running Corda as a Windows service
----------------------------------------------------------
@ -212,10 +264,11 @@ at boot, and means the Corda service stays running with no users connected to th
nssm install cordanode1 C:\ProgramData\Oracle\Java\javapath\java.exe
nssm set cordanode1 AppDirectory C:\Corda
nssm set cordanode1 AppParameters "-jar corda.jar -Xmx2048m --config-file=C:\corda\node.conf"
nssm set cordanode1 AppParameters "-Xmx2048m -jar corda.jar --config-file=C:\corda\node.conf"
nssm set cordanode1 AppStdout C:\Corda\service.log
nssm set cordanode1 AppStderr C:\Corda\service.log
nssm set cordanode1 Description Corda Node - Bank of Breakfast Tea
nssm set cordanode1 Start SERVICE_AUTO_START
sc start cordanode1
9. Modify the batch file:

View File

@ -13,7 +13,7 @@ import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.driver.driver
import org.junit.Test

View File

@ -17,7 +17,7 @@ import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.ALICE
import net.corda.testing.driver.driver
import org.graphstream.graph.Edge

View File

@ -6,6 +6,12 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased
----------
* **Enum Class Evolution**
With the addition of AMQP serialization Corda now supports enum constant evolution.
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
annotations applied, have older and newer instances of that enumeration be understood.
Release 2.0
----------
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates

View File

@ -22,6 +22,12 @@ Start the nodes with ``runnodes`` by running the following command from the root
.. warn:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may
fail to start.
If you receive an ``OutOfMemoryError`` exception when interacting with the nodes, you need to increase the amount of
Java heap memory available to them, which you can do when running them individually. See
:ref:`starting-an-individual-corda-node`.
.. _starting-an-individual-corda-node:
Starting an individual Corda node
---------------------------------
Run the node by opening a terminal window in the node's folder and running:
@ -30,9 +36,18 @@ Run the node by opening a terminal window in the node's folder and running:
java -jar corda.jar
.. warning:: By default, the node will look for a configuration file called ``node.conf`` and a CorDapps folder called
``cordapps`` in the current working directory. You can override the configuration file and workspace paths on the
command line (e.g. ``./corda.jar --config-file=test.conf --base-directory=/opt/r3corda/nodes/test``).
By default, the node will look for a configuration file called ``node.conf`` and a CorDapps folder called ``cordapps``
in the current working directory. You can override the configuration file and workspace paths on the command line (e.g.
``./corda.jar --config-file=test.conf --base-directory=/opt/corda/nodes/test``).
You can increase the amount of Java heap memory available to the node using the ``-Xmx`` command line argument. For
example, the following would run the node with a heap size of 2048MB:
.. code-block:: shell
java -Xmx2048m -jar corda.jar
You should do this if you receive an ``OutOfMemoryError`` exception when interacting with the node.
Optionally run the node's webserver as well by opening a terminal window in the node's folder and running:

View File

@ -16,6 +16,7 @@ import net.corda.testing.*
import net.corda.testing.contracts.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestIdentityService
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@ -232,7 +233,10 @@ class CommercialPaperTestsGeneric {
// @Test
@Ignore
fun `issue move and then redeem`() = withTestSerialization {
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY))
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(
listOf(ALICE_KEY),
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
initialIdentityName = MEGA_CORP.name)
val databaseAlice = aliceDatabaseAndServices.first
aliceServices = aliceDatabaseAndServices.second
aliceVaultService = aliceServices.vaultService
@ -241,8 +245,10 @@ class CommercialPaperTestsGeneric {
alicesVault = VaultFiller(aliceServices, DUMMY_NOTARY, DUMMY_NOTARY_KEY, rngFactory = ::Random).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER)
aliceVaultService = aliceServices.vaultService
}
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BIG_CORP_KEY))
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(
listOf(BIG_CORP_KEY),
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
initialIdentityName = MEGA_CORP.name)
val databaseBigCorp = bigCorpDatabaseAndServices.first
bigCorpServices = bigCorpDatabaseAndServices.second
bigCorpVaultService = bigCorpServices.vaultService

View File

@ -25,6 +25,7 @@ import net.corda.testing.contracts.DummyState
import net.corda.testing.contracts.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestIdentityService
import org.junit.After
import org.junit.Before
import org.junit.Rule
@ -70,9 +71,10 @@ class CashTests {
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices(
cordappPackages = listOf("net.corda.finance.contracts.asset"),
initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"),
keys = listOf(generateKeyPair()))
listOf(generateKeyPair()),
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
listOf("net.corda.finance.contracts.asset"),
CordaX500Name("Me", "London", "GB"))
database = databaseAndServices.first
ourServices = databaseAndServices.second

View File

@ -3,7 +3,8 @@ package net.corda.nodeapi
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.requireOnDefaultFileSystem
import org.apache.activemq.artemis.api.core.TransportConfiguration
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants

View File

@ -1,6 +1,6 @@
@file:JvmName("ArtemisUtils")
package net.corda.nodeapi
package net.corda.nodeapi.internal
import java.nio.file.FileSystems
import java.nio.file.Path

View File

@ -1,6 +1,6 @@
@file:JvmName("ConfigUtilities")
package net.corda.nodeapi.config
package net.corda.nodeapi.internal.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
@ -200,4 +200,4 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
}
}
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.config")
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config")

View File

@ -1,4 +1,4 @@
package net.corda.nodeapi.config
package net.corda.nodeapi.internal.config
import net.corda.core.internal.div
import java.nio.file.Path

View File

@ -1,7 +1,4 @@
package net.corda.nodeapi
import net.corda.nodeapi.config.OldConfig
import net.corda.nodeapi.config.toConfig
package net.corda.nodeapi.internal.config
data class User(
@OldConfig("user")

View File

@ -35,5 +35,5 @@ interface AMQPSerializer<out T> {
/**
* Read the given object from the input. The envelope is provided in case the schema is required.
*/
fun readObject(obj: Any, schema: SerializationSchemas, input: DeserializationInput): T
fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T
}

View File

@ -0,0 +1,136 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.UnsupportedOperationException
import java.lang.reflect.Type
import java.util.*
/**
* Used whenever a deserialized enums fingerprint doesn't match the fingerprint of the generated
* serializer object. I.e. the deserializing code has a different version of the code either newer or
* older). The changes will have been documented using the transformation annotations, a copy of which
* are encoded as part of the AMQP envelope.
*
* This function ascertains which version of the enumeration is newer by comparing the length of the
* transformations list. Since transformation annotations should only ever be added, never removed even
* when seemingly unneeded (such as repeated renaming of a single constant), the longer list will dictate
* which is more up to date.
*
* The list of transforms come from two places, the class as it exists on the current class path and the
* class as it exists as it was serialized. In the case of the former we can build the list by using
* reflection on the class. In the case of the latter the transforms are retrieved from the AMQP envelope.
*
* With a set of transforms chosen we calculate the set of all possible constants, then using the
* transformation rules we create a mapping between those values and the values that exist on the
* current class
*
* @property clazz The enum as it exists now, not as it did when it was serialized (either in the past
* or future).
* @property factory the [SerializerFactory] that is building this serialization object.
* @property conversions A mapping between all potential enum constants that could've been assigned to
* an instance of the enum as it existed at time of serialisation and those that exist now
* @property ordinals Convenience mapping of constant to ordinality
*/
class EnumEvolutionSerializer(
override val type: Type,
factory: SerializerFactory,
private val conversions: Map<String, String>,
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")!!
companion object {
private fun MutableMap<String, String>.mapInPlace(f: (String) -> String) {
val i = iterator()
while (i.hasNext()) {
val curr = i.next()
curr.setValue(f(curr.value))
}
}
/**
* Builds an Enum Evolver serializer.
*
* @param old The description of the enum as it existed at the time of serialisation taken from the
* received AMQP header
* @param new The Serializer object we built based on the current state of the enum class on our classpath
* @param factory the [SerializerFactory] that is building this serialization object.
* @param transformsFromBlob the transforms attached to the class in the AMQP header, i.e. the transforms
* known at serialization time
*/
fun make(old: RestrictedType,
new: AMQPSerializer<Any>,
factory: SerializerFactory,
schemas: SerializationSchemas): AMQPSerializer<Any> {
val wireTransforms = schemas.transforms.types[old.name] ?: EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
val localTransforms = TransformsSchema.get(old.name, factory)
// remember, the longer the list the newer we're assuming the transform set it as we assume
// evolution annotations are never removed, only added to
val transforms = if (wireTransforms.size > localTransforms.size) wireTransforms else localTransforms
// if either of these isn't of the cast type then something has gone terribly wrong
// elsewhere in the code
val defaultRules: List<EnumDefaultSchemaTransform>? = uncheckedCast(transforms[TransformTypes.EnumDefault])
val renameRules: List<RenameSchemaTransform>? = uncheckedCast(transforms[TransformTypes.Rename])
// What values exist on the enum as it exists on the class path
val localValues = new.type.asClass()!!.enumConstants.map { it.toString() }
val conversions: MutableMap<String, String> = localValues
.union(defaultRules?.map { it.new }?.toSet() ?: emptySet())
.union(renameRules?.map { it.to } ?: emptySet())
.associateBy({ it }, { it })
.toMutableMap()
val rules: MutableMap<String, String> = mutableMapOf()
rules.putAll(defaultRules?.associateBy({ it.new }, { it.old }) ?: emptyMap())
val renameRulesMap = renameRules?.associateBy({ it.to }, { it.from }) ?: emptyMap()
rules.putAll(renameRulesMap)
// take out set of all possible constants and build a map from those to the
// existing constants applying the rename and defaulting rules as defined
// in the schema
while (conversions.filterNot { it.value in localValues }.isNotEmpty()) {
conversions.mapInPlace { rules[it] ?: it }
}
// you'd think this was overkill to get access to the ordinal values for each constant but it's actually
// rather tricky when you don't have access to the actual type, so this is a nice way to be able
// to precompute and pass to the actual object
val ordinals = localValues.mapIndexed { i, s -> Pair(s, i) }.toMap()
// create a mapping between the ordinal value and the name as it was serialised converted
// to the name as it exists. We want to test any new constants have been added to the end
// of the enum class
val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices
.associateBy ({ it.value.toInt() }, { conversions[it.name] }))
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")
}
return EnumEvolutionSerializer(new.type, factory, conversions, ordinals)
}
}
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
val enumName = (obj as List<*>)[0] as String
if (enumName !in conversions) {
throw NotSerializableException("No rule to evolve enum constant $type::$enumName")
}
return type.asClass()!!.enumConstants[ordinals[conversions[enumName]]!!]
}
override fun writeClassInfo(output: SerializationOutput) {
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
}

View File

@ -109,7 +109,7 @@ class EvolutionSerializer(
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
throw IllegalAccessException("It should be impossible to write an evolution serializer")
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
/**

View File

@ -46,11 +46,11 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
private fun getEvolutionSerializer(
typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>,
transforms: TransformsSchema): AMQPSerializer<Any> {
schemas: SerializationSchemas): AMQPSerializer<Any> {
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
is RestrictedType -> throw NotSerializableException("Enum evolution is not currently supported")
is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, this, schemas)
}
}
}
@ -210,7 +210,7 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
// doesn't match that of the serialised object then we are dealing with different
// instance of the class, as such we need to build an EvolutionSerialiser
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas.transforms)
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
}
} catch (e: ClassNotFoundException) {
if (sentinel) throw e

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename
@ -27,14 +28,72 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType
Unknown({ UnknownTransform() }) {
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal
override fun validate(l : List<Transform>, constants: Map<String, Int>) { }
},
EnumDefault({ a -> EnumDefaultSchemaTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) {
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal
/**
* Validates a list of constant additions to an enumerated type. To be valid a default (the value
* that should be used when we cannot use the new value) must refer to a constant that exists in the
* enum class as it exists now and it cannot refer to itself.
*
* @param l The list of transforms representing new constants and the mapping from that constant to an
* existing value
* @param constants The list of enum constants on the type the transforms are being applied to
*/
override fun validate(list : List<Transform>, constants: Map<String, Int>) {
uncheckedCast<List<Transform>, List<EnumDefaultSchemaTransform>>(list).forEach {
if (!constants.contains(it.new)) {
throw NotSerializableException("Unknown enum constant ${it.new}")
}
if (!constants.contains(it.old)) {
throw NotSerializableException(
"Enum extension defaults must be to a valid constant: ${it.new} -> ${it.old}. ${it.old} " +
"doesn't exist in constant set $constants")
}
if (it.old == it.new) {
throw NotSerializableException("Enum extension ${it.new} cannot default to itself")
}
if (constants[it.old]!! >= constants[it.new]!!) {
throw NotSerializableException(
"Enum extensions must default to older constants. ${it.new}[${constants[it.new]}] " +
"defaults to ${it.old}[${constants[it.old]}] which is greater")
}
}
}
},
Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) {
override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal
/**
* Validates a list of rename transforms is valid. Such a list isn't valid if we detect a cyclic chain,
* that is a constant is renamed to something that used to exist in the enum. We do this for both
* the same constant (i.e. C -> D -> C) and multiple constants (C->D, B->C)
*
* @param l The list of transforms representing the renamed constants and the mapping between their new
* and old values
* @param constants The list of enum constants on the type the transforms are being applied to
*/
override fun validate(l : List<Transform>, constants: Map<String, Int>) {
object : Any() {
val from : MutableSet<String> = mutableSetOf()
val to : MutableSet<String> = mutableSetOf() }.apply {
@Suppress("UNCHECKED_CAST") (l as List<RenameSchemaTransform>).forEach { rename ->
if (rename.to in this.to || rename.from in this.from) {
throw NotSerializableException("Cyclic renames are not allowed (${rename.to})")
}
this.to.add(rename.from)
this.from.add(rename.to)
}
}
}
}
// Transform used to test the unknown handler, leave this at as the final constant, uncomment
// when regenerating test cases - if Java had a pre-processor this would be much neater
@ -45,6 +104,8 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType
//}
;
abstract fun validate(l: List<Transform>, constants: Map<String, Int>)
companion object : DescribedTypeConstructor<TransformTypes> {
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor

View File

@ -146,8 +146,8 @@ class EnumDefaultSchemaTransform(val old: String, val new: String) : Transform()
/**
* Transform applied to either a class or enum where a property is renamed
*
* @property from the name at time of change of the property
* @property to the new name of the property
* @property from the name of the property or constant prior to being changed, i.e. what it was
* @property to the new name of the property or constant after the change has been made, i.e. what it is now
*/
class RenameSchemaTransform(val from: String, val to: String) : Transform() {
companion object : DescribedTypeConstructor<RenameSchemaTransform> {
@ -192,6 +192,61 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
companion object : DescribedTypeConstructor<TransformsSchema> {
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor
/**
* Takes a class name and either returns a cached instance of the TransformSet for it or, on a cache miss,
* instantiates the transform set before inserting into the cache and returning it.
*
* @param name fully qualified class name to lookup transforms for
* @param sf the [SerializerFactory] building this transform set. Needed as each can define it's own
* class loader and this dictates which classes we can and cannot see
*/
fun get(name: String, sf: SerializerFactory) = sf.transformsCache.computeIfAbsent(name) {
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
try {
val clazz = sf.classloader.loadClass(name)
supportedTransforms.forEach { transform ->
clazz.getAnnotation(transform.type)?.let { list ->
transform.getAnnotations(list).forEach { annotation ->
val t = transform.enum.build(annotation)
// we're explicitly rejecting repeated annotations, whilst it's fine and we'd just
// ignore them it feels like a good thing to alert the user to since this is
// more than likely a typo in their code so best make it an actual error
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }
.filter { t == it }
.isNotEmpty()) {
throw NotSerializableException(
"Repeated unique transformation annotation of type ${t.name}")
}
transforms[transform.enum]!!.add(t)
}
transform.enum.validate(
transforms[transform.enum] ?: emptyList(),
clazz.enumConstants.mapIndexed { i, s -> Pair(s.toString(), i) }.toMap())
}
}
} catch (_: ClassNotFoundException) {
// if we can't load the class we'll end up caching an empty list which is fine as that
// list, on lookup, won't be included in the schema because it's empty
}
transforms
}
private fun getAndAdd(
type: String,
sf: SerializerFactory,
map: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
get(type, sf).apply {
if (isNotEmpty()) {
map[type] = this
}
}
}
/**
* Prepare a schema for encoding, takes all of the types being transmitted and inspects each
* one for any transform annotations. If there are any build up a set that can be
@ -200,48 +255,10 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
* @param schema should be a [Schema] generated for a serialised data structure
* @param sf should be provided by the same serialization context that generated the schema
*/
fun build(schema: Schema, sf: SerializerFactory): TransformsSchema {
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
schema.types.forEach { type ->
sf.transformsCache.computeIfAbsent(type.name) {
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
try {
val clazz = sf.classloader.loadClass(type.name)
supportedTransforms.forEach { transform ->
clazz.getAnnotation(transform.type)?.let { list ->
transform.getAnnotations(list).forEach { annotation ->
val t = transform.enum.build(annotation)
// we're explicitly rejecting repeated annotations, whilst it's fine and we'd just
// ignore them it feels like a good thing to alert the user to since this is
// more than likely a typo in their code so best make it an actual error
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }
.filter { t == it }.isNotEmpty()) {
throw NotSerializableException(
"Repeated unique transformation annotation of type ${t.name}")
}
transforms[transform.enum]!!.add(t)
}
}
}
} catch (_: ClassNotFoundException) {
// if we can't load the class we'll end up caching an empty list which is fine as that
// list, on lookup, won't be included in the schema because it's empty
}
transforms
}.apply {
if (isNotEmpty()) {
rtn[type.name] = this
}
}
}
return TransformsSchema(rtn)
}
fun build(schema: Schema, sf: SerializerFactory) = TransformsSchema(
mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>().apply {
schema.types.forEach { type -> getAndAdd(type.name, sf, this) }
})
override fun getTypeClass(): Class<*> = TransformsSchema::class.java
@ -286,6 +303,7 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
override fun getDescribed(): Any = types
@Suppress("NAME_SHADOWING")
override fun toString(): String {
data class Indent(val indent: String) {
@Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}")

View File

@ -1,16 +1,20 @@
package net.corda.nodeapi.internal
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.*
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -44,11 +48,8 @@ class AttachmentsClassLoaderStaticContractTests {
}
}
private lateinit var serviceHub: MockServices
@Before
fun `create service hub`() {
serviceHub = MockServices(cordappPackages = listOf("net.corda.nodeapi.internal"))
private val serviceHub = rigorousMock<ServicesForResolution>().also {
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockAttachmentStorage())).whenever(it).cordappProvider
}
@Test

View File

@ -19,10 +19,8 @@ import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPro
import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.*
import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockServices
import org.apache.commons.io.IOUtils
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayInputStream
@ -52,14 +50,13 @@ class AttachmentsClassLoaderTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private lateinit var serviceHub: DummyServiceHub
class DummyServiceHub : MockServices() {
override val cordappProvider: CordappProviderImpl
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
private val cordapp get() = cordappProvider.cordapps.first()
val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
val appContext get() = cordappProvider.getAppContext(cordapp)
private val attachments = MockAttachmentStorage()
private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
private val cordapp get() = cordappProvider.cordapps.first()
private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
private val appContext get() = cordappProvider.getAppContext(cordapp)
private val serviceHub = rigorousMock<ServiceHub>().also {
doReturn(attachments).whenever(it).attachments
}
// These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
@ -77,12 +74,6 @@ class AttachmentsClassLoaderTests {
}
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Before
fun `create service hub`() {
serviceHub = DummyServiceHub()
}
@Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
ClassLoaderForTests().use { child ->
@ -112,8 +103,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `test MockAttachmentStorage open as jar`() {
val storage = serviceHub.attachments
val key = serviceHub.attachmentId
val storage = attachments
val key = attachmentId
val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR()
@ -123,9 +114,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `test overlapping file exception`() {
val storage = serviceHub.attachments
val att0 = serviceHub.attachmentId
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data")))
@ -136,9 +126,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `basic`() {
val storage = serviceHub.attachments
val att0 = serviceHub.attachmentId
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -169,9 +158,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `loading class AnotherDummyContract`() {
val storage = serviceHub.attachments
val att0 = serviceHub.attachmentId
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -194,10 +182,8 @@ class AttachmentsClassLoaderTests {
val contract = createContract2Cash()
val bytes = contract.serialize()
val storage = serviceHub.attachments
val att0 = serviceHub.attachmentId
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -222,10 +208,8 @@ class AttachmentsClassLoaderTests {
val context2 = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(data.contract.javaClass)
val bytes = data.serialize(context = context2)
val storage = serviceHub.attachments
val att0 = serviceHub.attachmentId
val storage = attachments
val att0 = attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -276,7 +260,7 @@ class AttachmentsClassLoaderTests {
@Test
fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = serviceHub.appContext.classLoader
val child = appContext.classLoader
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
@ -288,7 +272,7 @@ class AttachmentsClassLoaderTests {
.withClassLoader(child)
val bytes = run {
val wireTransaction = tx.toWireTransaction(serviceHub, context)
val wireTransaction = tx.toWireTransaction(cordappProvider, context)
wireTransaction.serialize(context = context)
}
val copiedWireTransaction = bytes.deserialize(context = context)
@ -307,13 +291,12 @@ class AttachmentsClassLoaderTests {
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val attachmentRef = serviceHub.attachmentId
val attachmentRef = attachmentId
val bytes = run {
val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub)
.withClassLoader(child)
val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext)
val wireTransaction = tx.toWireTransaction(cordappProvider, outboundContext)
wireTransaction.serialize(context = outboundContext)
}
// use empty attachmentStorage
@ -340,7 +323,7 @@ class AttachmentsClassLoaderTests {
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
val attachmentRef = serviceHub.attachmentId
val attachmentRef = attachmentId
// We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory
.defaultFactory

View File

@ -1,4 +1,4 @@
package net.corda.nodeapi.config
package net.corda.nodeapi.internal.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory.empty

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.*
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
@ -10,8 +11,9 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EnumEvolvabilityTests {
var localPath = "file:///home/katelyn/srcs/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp"
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
companion object {
val VERBOSE = false
@ -21,11 +23,6 @@ class EnumEvolvabilityTests {
A, B, C, D
}
@CordaSerializationTransformEnumDefaults()
enum class MissingDefaults {
A, B, C, D
}
@CordaSerializationTransformRenames()
enum class MissingRenames {
A, B, C, D
@ -48,13 +45,6 @@ class EnumEvolvabilityTests {
A, B, C, E
}
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("E", "C"),
CordaSerializationTransformRename("F", "D"))
enum class RenameEnumTwice {
A, B, E, F
}
@Test
fun noAnnotation() {
data class C (val n: NotAnnotated)
@ -66,6 +56,11 @@ class EnumEvolvabilityTests {
assertEquals(0, bAndS.transformsSchema.types.size)
}
@CordaSerializationTransformEnumDefaults()
enum class MissingDefaults {
A, B, C, D
}
@Test
fun missingDefaults() {
data class C (val m: MissingDefaults)
@ -228,6 +223,13 @@ class EnumEvolvabilityTests {
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
}
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("E", "C"),
CordaSerializationTransformRename("F", "D"))
enum class RenameEnumTwice {
A, B, E, F
}
@Test
fun doubleRenameAnnotationIsAdded() {
data class C (val annotatedEnum: RenameEnumTwice)
@ -433,4 +435,98 @@ class EnumEvolvabilityTests {
assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name))
assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown))
}
//
// In this example we will have attempted to rename D back to C
//
// The life cycle of the class would've looked like this
//
// 1. enum class RejectCyclicRename { A, B, C }
// 2. enum class RejectCyclicRename { A, B, D }
// 3. enum class RejectCyclicRename { A, B, C }
//
// And we're not at 3. However, we ban this rename
//
@CordaSerializationTransformRenames (
CordaSerializationTransformRename("D", "C"),
CordaSerializationTransformRename("C", "D")
)
enum class RejectCyclicRename { A, B, C }
@Test
fun rejectCyclicRename() {
data class C (val e: RejectCyclicRename)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRename.A))
}.isInstanceOf(NotSerializableException::class.java)
}
//
// In this test, like the above, we're looking to ensure repeated renames are rejected as
// unserailzble. However, in this case, it isn't a struct cycle, rather one element
// is renamed to match what a different element used to be called
//
@CordaSerializationTransformRenames (
CordaSerializationTransformRename(from = "B", to = "C"),
CordaSerializationTransformRename(from = "C", to = "D")
)
enum class RejectCyclicRenameAlt { A, C, D }
@Test
fun rejectCyclicRenameAlt() {
data class C (val e: RejectCyclicRenameAlt)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRenameAlt.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformRenames (
CordaSerializationTransformRename("G", "C"),
CordaSerializationTransformRename("F", "G"),
CordaSerializationTransformRename("E", "F"),
CordaSerializationTransformRename("D", "E"),
CordaSerializationTransformRename("C", "D")
)
enum class RejectCyclicRenameRedux { A, B, C }
@Test
fun rejectCyclicRenameRedux() {
data class C (val e: RejectCyclicRenameRedux)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRenameRedux.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault (new = "D", old = "X")
enum class RejectBadDefault { A, B, C, D }
@Test
fun rejectBadDefault() {
data class C (val e: RejectBadDefault)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectBadDefault.D))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault (new = "D", old = "D")
enum class RejectBadDefaultToSelf { A, B, C, D }
@Test
fun rejectBadDefaultToSelf() {
data class C (val e: RejectBadDefaultToSelf)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectBadDefaultToSelf.D))
}.isInstanceOf(NotSerializableException::class.java)
}
}

View File

@ -1,18 +1,19 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.*
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
import java.net.URI
import kotlin.test.assertEquals
// NOTE: To recreate the test files used by these tests uncomment the original test classes and comment
// the new ones out, then change each test to write out the serialized bytes rather than read
// the file.
class EnumEvolveTests {
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
@ -26,7 +27,7 @@ class EnumEvolveTests {
@Test
fun deserialiseNewerSetToUnknown() {
val resource = "${this.javaClass.simpleName}.${testName()}"
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C (val e : DeserializeNewerSetToUnknown)
@ -35,9 +36,379 @@ class EnumEvolveTests {
// File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(C(DeserializeNewerSetToUnknown.D)).bytes)
val path = EvolvabilityTests::class.java.getResource(resource)
val obj = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path.toURI()).readBytes()))
assertEquals (DeserializeNewerSetToUnknown.C, obj.e)
}
// Version of the class as it was serialised
//
// @CordaSerializationTransformEnumDefaults (
// CordaSerializationTransformEnumDefault("D", "C"),
// CordaSerializationTransformEnumDefault("E", "D"))
// enum class DeserializeNewerSetToUnknown2 { A, B, C, D, E }
//
// Version of the class as it's used in the test
enum class DeserializeNewerSetToUnknown2 { A, B, C }
@Test
fun deserialiseNewerSetToUnknown2() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerSetToUnknown2)
// Uncomment to re-generate test files
// val so = SerializationOutput(sf)
// File(URI("$localPath/$resource.C")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.C)).bytes)
// File(URI("$localPath/$resource.D")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.D)).bytes)
// File(URI("$localPath/$resource.E")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.E)).bytes)
val path1 = EvolvabilityTests::class.java.getResource("$resource.C")
val path2 = EvolvabilityTests::class.java.getResource("$resource.D")
val path3 = EvolvabilityTests::class.java.getResource("$resource.E")
// C will just work
val obj1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1.toURI()).readBytes()))
// D will transform directly to C
val obj2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2.toURI()).readBytes()))
// E will have to transform from E -> D -> C to work, so this should exercise that part
// of the evolution code
val obj3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3.toURI()).readBytes()))
assertEquals (DeserializeNewerSetToUnknown2.C, obj1.e)
assertEquals (DeserializeNewerSetToUnknown2.C, obj2.e)
assertEquals (DeserializeNewerSetToUnknown2.C, obj3.e)
}
// Version of the class as it was serialised, evolve rule purposfuly not included to
// test failure conditions
//
// enum class DeserializeNewerWithNoRule { A, B, C, D }
//
// Class as it exists for the test
enum class DeserializeNewerWithNoRule { A, B, C }
// Lets test to see if they forgot to provide an upgrade rule
@Test
fun deserialiseNewerWithNoRule() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerWithNoRule)
// Uncomment to re-generate test files
// val so = SerializationOutput(sf)
// File(URI("$localPath/$resource")).writeBytes(so.serialize(C(DeserializeNewerWithNoRule.D)).bytes)
val path = EvolvabilityTests::class.java.getResource(resource)
Assertions.assertThatThrownBy {
DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path.toURI()).readBytes()))
}.isInstanceOf(NotSerializableException::class.java)
}
// Version of class as it was serialized, at some point in the "future" several
// values have been renamed
//
// First Change
// A -> AA
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from ="A", to = "AA")
// )
// enum class DeserializeWithRename { AA, B, C }
//
// Second Change
// B -> BB
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from = "B", to = "BB"),
// CordaSerializationTransformRename(from = "A", to = "AA")
// )
// enum class DeserializeWithRename { AA, BB, C }
//
// Third Change
// BB -> XX
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from = "B", to = "BB"),
// CordaSerializationTransformRename(from = "BB", to = "XX"),
// CordaSerializationTransformRename(from = "A", to = "AA")
// )
// enum class DeserializeWithRename { AA, XX, C }
//
// Finally, the version we're using to test with
enum class DeserializeWithRename { A, B, C }
@Test
fun deserializeWithRename() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeWithRename)
// Uncomment to re-generate test files, needs to be done in three stages
val so = SerializationOutput(sf)
// First change
// File(URI("$localPath/$resource.1.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.1.B")).writeBytes(so.serialize(C(DeserializeWithRename.B)).bytes)
// File(URI("$localPath/$resource.1.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
// Second change
// File(URI("$localPath/$resource.2.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.2.BB")).writeBytes(so.serialize(C(DeserializeWithRename.BB)).bytes)
// File(URI("$localPath/$resource.2.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
// Third change
// File(URI("$localPath/$resource.3.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.3.XX")).writeBytes(so.serialize(C(DeserializeWithRename.XX)).bytes)
// File(URI("$localPath/$resource.3.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
//
// Test we can deserialize instances of the class after its first transformation
//
val path1_AA = EvolvabilityTests::class.java.getResource("$resource.1.AA")
val path1_B = EvolvabilityTests::class.java.getResource("$resource.1.B")
val path1_C = EvolvabilityTests::class.java.getResource("$resource.1.C")
val obj1_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1_AA.toURI()).readBytes()))
val obj1_B = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1_B.toURI()).readBytes()))
val obj1_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1_C.toURI()).readBytes()))
assertEquals(DeserializeWithRename.A, obj1_AA.e)
assertEquals(DeserializeWithRename.B, obj1_B.e)
assertEquals(DeserializeWithRename.C, obj1_C.e)
//
// Test we can deserialize instances of the class after its second transformation
//
val path2_AA = EvolvabilityTests::class.java.getResource("$resource.2.AA")
val path2_BB = EvolvabilityTests::class.java.getResource("$resource.2.BB")
val path2_C = EvolvabilityTests::class.java.getResource("$resource.2.C")
val obj2_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2_AA.toURI()).readBytes()))
val obj2_BB = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2_BB.toURI()).readBytes()))
val obj2_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2_C.toURI()).readBytes()))
assertEquals(DeserializeWithRename.A, obj2_AA.e)
assertEquals(DeserializeWithRename.B, obj2_BB.e)
assertEquals(DeserializeWithRename.C, obj2_C.e)
//
// Test we can deserialize instances of the class after its third transformation
//
val path3_AA = EvolvabilityTests::class.java.getResource("$resource.3.AA")
val path3_XX = EvolvabilityTests::class.java.getResource("$resource.3.XX")
val path3_C = EvolvabilityTests::class.java.getResource("$resource.3.C")
val obj3_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3_AA.toURI()).readBytes()))
val obj3_XX = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3_XX.toURI()).readBytes()))
val obj3_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3_C.toURI()).readBytes()))
assertEquals(DeserializeWithRename.A, obj3_AA.e)
assertEquals(DeserializeWithRename.B, obj3_XX.e)
assertEquals(DeserializeWithRename.C, obj3_C.e)
}
// The origional version of the enum, what we'll be eventually deserialising into
// enum class MultiOperations { A, B, C }
//
// First alteration, add D
// @CordaSerializationTransformEnumDefault(old = "C", new = "D")
// enum class MultiOperations { A, B, C, D }
//
// Second, add E
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "C", new = "D"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E")
// )
// enum class MultiOperations { A, B, C, D, E }
//
// Third, Rename E to BOB
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "C", new = "D"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E")
// )
// @CordaSerializationTransformRename(to = "BOB", from = "E")
// enum class MultiOperations { A, B, C, D, BOB }
//
// Fourth, Rename C to CAT, ADD F and G
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "F", new = "G"),
// CordaSerializationTransformEnumDefault(old = "BOB", new = "F"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E"),
// CordaSerializationTransformEnumDefault(old = "C", new = "D")
// )
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(to = "CAT", from = "C"),
// CordaSerializationTransformRename(to = "BOB", from = "E")
// )
// enum class MultiOperations { A, B, CAT, D, BOB, F, G}
//
// Fifth, Rename F to FLUMP, Rename BOB to BBB, Rename A to APPLE
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "F", new = "G"),
// CordaSerializationTransformEnumDefault(old = "BOB", new = "F"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E"),
// CordaSerializationTransformEnumDefault(old = "C", new = "D")
// )
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(to = "APPLE", from = "A"),
// CordaSerializationTransformRename(to = "BBB", from = "BOB"),
// CordaSerializationTransformRename(to = "FLUMP", from = "F"),
// CordaSerializationTransformRename(to = "CAT", from = "C"),
// CordaSerializationTransformRename(to = "BOB", from = "E")
// )
// enum class MultiOperations { APPLE, B, CAT, D, BBB, FLUMP, G}
//
// Finally, the original version of teh class that we're going to be testing with
enum class MultiOperations { A, B, C }
@Test
fun multiOperations() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: MultiOperations)
// Uncomment to re-generate test files, needs to be done in three stages
val so = SerializationOutput(sf)
// First change
// File(URI("$localPath/$resource.1.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.1.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.1.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.1.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// Second change
// File(URI("$localPath/$resource.2.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.2.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.2.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.2.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.2.E")).writeBytes(so.serialize(C(MultiOperations.E)).bytes)
// Third change
// File(URI("$localPath/$resource.3.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.3.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.3.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.3.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.3.BOB")).writeBytes(so.serialize(C(MultiOperations.BOB)).bytes)
// Fourth change
// File(URI("$localPath/$resource.4.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.4.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.4.CAT")).writeBytes(so.serialize(C(MultiOperations.CAT)).bytes)
// File(URI("$localPath/$resource.4.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.4.BOB")).writeBytes(so.serialize(C(MultiOperations.BOB)).bytes)
// File(URI("$localPath/$resource.4.F")).writeBytes(so.serialize(C(MultiOperations.F)).bytes)
// File(URI("$localPath/$resource.4.G")).writeBytes(so.serialize(C(MultiOperations.G)).bytes)
// Fifth change - { APPLE, B, CAT, D, BBB, FLUMP, G}
// File(URI("$localPath/$resource.5.APPLE")).writeBytes(so.serialize(C(MultiOperations.APPLE)).bytes)
// File(URI("$localPath/$resource.5.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.5.CAT")).writeBytes(so.serialize(C(MultiOperations.CAT)).bytes)
// File(URI("$localPath/$resource.5.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.5.BBB")).writeBytes(so.serialize(C(MultiOperations.BBB)).bytes)
// File(URI("$localPath/$resource.5.FLUMP")).writeBytes(so.serialize(C(MultiOperations.FLUMP)).bytes)
// File(URI("$localPath/$resource.5.G")).writeBytes(so.serialize(C(MultiOperations.G)).bytes)
val stage1Resources = listOf(
Pair("$resource.1.A", MultiOperations.A),
Pair("$resource.1.B", MultiOperations.B),
Pair("$resource.1.C", MultiOperations.C),
Pair("$resource.1.D", MultiOperations.C))
val stage2Resources = listOf(
Pair("$resource.2.A", MultiOperations.A),
Pair("$resource.2.B", MultiOperations.B),
Pair("$resource.2.C", MultiOperations.C),
Pair("$resource.2.D", MultiOperations.C),
Pair("$resource.2.E", MultiOperations.C))
val stage3Resources = listOf(
Pair("$resource.3.A", MultiOperations.A),
Pair("$resource.3.B", MultiOperations.B),
Pair("$resource.3.C", MultiOperations.C),
Pair("$resource.3.D", MultiOperations.C),
Pair("$resource.3.BOB", MultiOperations.C))
val stage4Resources = listOf(
Pair("$resource.4.A", MultiOperations.A),
Pair("$resource.4.B", MultiOperations.B),
Pair("$resource.4.CAT", MultiOperations.C),
Pair("$resource.4.D", MultiOperations.C),
Pair("$resource.4.BOB", MultiOperations.C),
Pair("$resource.4.F", MultiOperations.C),
Pair("$resource.4.G", MultiOperations.C))
val stage5Resources = listOf(
Pair("$resource.5.APPLE", MultiOperations.A),
Pair("$resource.5.B", MultiOperations.B),
Pair("$resource.5.CAT", MultiOperations.C),
Pair("$resource.5.D", MultiOperations.C),
Pair("$resource.5.BBB", MultiOperations.C),
Pair("$resource.5.FLUMP", MultiOperations.C),
Pair("$resource.5.G", MultiOperations.C))
fun load(l: List<Pair<String, MultiOperations>>) = l.map {
Pair (DeserializationInput(sf).deserialize(SerializedBytes<C>(
File(EvolvabilityTests::class.java.getResource(it.first).toURI()).readBytes())), it.second)
}
load (stage1Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage2Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage3Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage4Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage5Resources).forEach { assertEquals(it.second, it.first.e) }
}
@CordaSerializationTransformEnumDefault(old = "A", new = "F")
enum class BadNewValue { A, B, C, D }
@Test
fun badNewValue() {
val sf = testDefaultFactory()
data class C (val e : BadNewValue)
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(BadNewValue.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault(new = "D", old = "E"),
CordaSerializationTransformEnumDefault(new = "E", old = "A")
)
enum class OutOfOrder { A, B, C, D, E}
@Test
fun outOfOrder() {
val sf = testDefaultFactory()
data class C (val e : OutOfOrder)
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(OutOfOrder.A))
}.isInstanceOf(NotSerializableException::class.java)
}
// class as it existed as it was serialized
//
// enum class ChangedOrdinality { A, B, C }
//
// class as it exists for the tests
@CordaSerializationTransformEnumDefault("D", "A")
enum class ChangedOrdinality { A, B, D, C }
@Test
fun changedOrdinality() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: ChangedOrdinality)
// Uncomment to re-generate test files, needs to be done in three stages
// File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(C(ChangedOrdinality.A)).bytes)
Assertions.assertThatThrownBy {
DeserializationInput(sf).deserialize(SerializedBytes<C>(
File(EvolvabilityTests::class.java.getResource(resource).toURI()).readBytes()))
}.isInstanceOf(NotSerializableException::class.java)
}
}
}

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.junit.Test
import java.io.File
import java.io.NotSerializableException
@ -18,7 +19,8 @@ import kotlin.test.assertEquals
// 5. Comment back out the generation code and uncomment the actual test
class EvolvabilityTests {
// When regenerating the test files this needs to be set to the file system location of the resource files
var localPath = "file:///<path>/<to>/<toplevel of>/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp"
var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
@Test
fun simpleOrderSwapSameType() {

View File

@ -8,7 +8,8 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.ALICE
import net.corda.testing.*
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.driver.driver

View File

@ -8,7 +8,10 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThat

View File

@ -13,7 +13,8 @@ import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.*
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver

View File

@ -10,7 +10,8 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.toHex
import net.corda.core.utilities.unwrap
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.ALICE
import net.corda.testing.driver.driver
import org.bouncycastle.util.io.Streams
import org.junit.Test
@ -33,7 +34,7 @@ class SSHServerTest : IntegrationTest() {
fun `ssh server does not start be default`() {
val user = User("u", "p", setOf())
// The driver will automatically pick up the annotated flows below
driver {
driver() {
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user))
node.getOrThrow()

View File

@ -1,8 +1,9 @@
package net.corda.node.services
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Contract
import net.corda.core.contracts.PartyAndReference
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.CordaX500Name
@ -11,6 +12,8 @@ import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.toLedgerTransaction
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.contextLogger
@ -24,9 +27,8 @@ import net.corda.testing.IntegrationTest
import net.corda.testing.driver.DriverDSLExposedInterface
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.ClassRule
import org.junit.Test
import java.net.URLClassLoader
@ -34,14 +36,11 @@ import java.nio.file.Files
import kotlin.test.assertFailsWith
class AttachmentLoadingTests : IntegrationTest() {
private class Services : MockServices() {
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
private val cordapp get() = provider.cordapps.first()
val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
val appContext get() = provider.getAppContext(cordapp)
override val cordappProvider: CordappProvider = provider
}
private val attachments = MockAttachmentStorage()
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
private val cordapp get() = provider.cordapps.first()
private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
private val appContext get() = provider.getAppContext(cordapp)
private companion object {
@ClassRule @JvmField
@ -77,17 +76,17 @@ class AttachmentLoadingTests : IntegrationTest() {
}
}
private lateinit var services: Services
@Before
fun setup() {
super.setUp()
services = Services()
private val services = rigorousMock<ServicesForResolution>().also {
doReturn(attachments).whenever(it).attachments
doReturn(provider).whenever(it).cordappProvider
doReturn(rigorousMock<IdentityService>().also {
doReturn(null).whenever(it).partyFromKey(DUMMY_BANK_A.owningKey)
}).whenever(it).identityService
}
@Test
fun `test a wire transaction has loaded the correct attachment`() = withTestSerialization {
val appClassLoader = services.appContext.classLoader
val appClassLoader = appContext.classLoader
val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java)
val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java)
val contract = contractClass.newInstance()
@ -97,8 +96,7 @@ class AttachmentLoadingTests : IntegrationTest() {
contract.verify(ledgerTx)
val actual = ledgerTx.attachments.first()
val expected = services.attachments.openAttachment(services.attachmentId)!!
val expected = attachments.openAttachment(attachmentId)!!
assertEquals(expected, actual)
}

View File

@ -14,7 +14,7 @@ import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver

View File

@ -13,6 +13,7 @@ import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.nodeapi.NodeInfoFilesCopier
import net.corda.testing.*
import net.corda.testing.node.MockKeyManagementService
import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.contentOf
import org.junit.Before
@ -47,9 +48,9 @@ class NodeInfoWatcherTest {
@Before
fun start() {
val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT)
val identityService = makeTestIdentityService()
keyManagementService = MockKeyManagementService(identityService, ALICE_KEY)
nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler = scheduler)
nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler)
nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY
}

View File

@ -8,7 +8,7 @@ import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.startFlow
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState

View File

@ -5,7 +5,7 @@ import net.corda.core.internal.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.MEGA_CORP
import net.corda.testing.MINI_CORP

View File

@ -1,6 +1,6 @@
package net.corda.services.messaging
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.messaging.SimpleMQClient
import org.junit.Test

View File

@ -22,8 +22,8 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATI
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.testing.*
import net.corda.testing.internal.NodeBasedTest
import net.corda.testing.messaging.SimpleMQClient

View File

@ -19,7 +19,8 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.User
import net.corda.nodeapi.internal.config.User
import net.corda.testing.chooseIdentity
import net.corda.testing.*
import net.corda.testing.driver.driver
import org.junit.Assume.assumeFalse

View File

@ -3,11 +3,12 @@
package net.corda.node
import kotlin.system.exitProcess
import net.corda.node.internal.EnterpriseNode
fun main(args: Array<String>) {
// Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass.
// It will exit the process in case of startup failure and is not intended to be used by embedders. If you want
// to embed Node in your own container, instantiate it directly and set up the configuration objects yourself.
EnterpriseNode.Startup(args).run()
}
exitProcess(if (EnterpriseNode.Startup(args).run()) 0 else 1)
}

View File

@ -69,6 +69,7 @@ import org.apache.activemq.artemis.utils.ReusableLatch
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
import org.slf4j.Logger
import rx.Observable
import rx.Scheduler
import java.io.IOException
import java.lang.management.ManagementFactory
import java.lang.reflect.InvocationTargetException
@ -193,8 +194,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
identityService.loadIdentities(info.legalIdentitiesAndCerts)
val transactionStorage = makeTransactionStorage(database)
val stateLoader = StateLoaderImpl(transactionStorage)
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info, identityService)
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, database, info, identityService)
val mutualExclusionConfiguration = configuration.enterpriseConfiguration.mutualExclusionConfiguration
if (mutualExclusionConfiguration.on) {
RunOnceService(database, mutualExclusionConfiguration.machineName,
@ -208,7 +208,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
platformClock,
database,
flowStarter,
stateLoader,
transactionStorage,
unfinishedSchedules = busyNodeLatch,
serverThread = serverThread)
if (serverThread is ExecutorService) {
@ -233,7 +233,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
val networkMapUpdater = NetworkMapUpdater(services.networkMapCache,
NodeInfoWatcher(configuration.baseDirectory, Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)),
NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)),
networkMapClient)
runOnStop += networkMapUpdater::close
@ -259,6 +259,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
}
/**
* Should be [rx.schedulers.Schedulers.io] for production,
* or [rx.internal.schedulers.CachedThreadScheduler] (with shutdown registered with [runOnStop]) for shared-JVM testing.
*/
protected abstract fun getRxIoScheduler(): Scheduler
open fun startShell(rpcOps: CordaRPCOps) {
InteractiveShell.startShell(configuration, rpcOps, userService, _services.identityService, _services.database)
}
@ -500,7 +506,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context.
*/
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo, identityService: IdentityService): MutableList<Any> {
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityService): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics)
@ -511,7 +517,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
keyManagementService,
schemaService,
transactionStorage,
stateLoader,
MonitoringService(metrics),
cordappProvider,
database,
@ -724,18 +729,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
override val keyManagementService: KeyManagementService,
override val schemaService: SchemaService,
override val validatedTransactions: WritableTransactionStorage,
private val stateLoader: StateLoader,
override val monitoringService: MonitoringService,
override val cordappProvider: CordappProviderInternal,
override val database: CordaPersistence,
override val myInfo: NodeInfo
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader {
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by validatedTransactions {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService()
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService) }
override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) }
override val vaultService by lazy { makeVaultService(keyManagementService, validatedTransactions, database.hibernateConfig) }
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
override val networkService: MessagingService get() = network

Some files were not shown because too many files have changed in this diff Show More