mirror of
https://github.com/corda/corda.git
synced 2025-01-13 08:20:01 +00:00
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:
commit
cb43091ff8
@ -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()
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -71,7 +71,8 @@ a:visited {
|
||||
}
|
||||
|
||||
.wy-nav-content {
|
||||
background-color: #fff max-width: 1000px;
|
||||
background-color: #fff;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.wy-nav-side {
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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")
|
@ -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
|
@ -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")
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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}")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user