From 29b52f66dce3e4e3ccb68c138c05ce1841e7d4e0 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Fri, 12 Jan 2018 10:20:24 +0000 Subject: [PATCH 01/16] Updates the documentation on versioning. --- docs/source/building-a-cordapp-index.rst | 1 + docs/source/resources/flow-interface.png | Bin 0 -> 35805 bytes docs/source/upgrade-notes.rst | 4 +- docs/source/upgrading-cordapps.rst | 417 +++++++++++++++++++++++ docs/source/versioning.rst | 48 +-- 5 files changed, 421 insertions(+), 49 deletions(-) create mode 100644 docs/source/resources/flow-interface.png create mode 100644 docs/source/upgrading-cordapps.rst diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index e71d285e93..ef4d17d334 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -7,6 +7,7 @@ CorDapps cordapp-overview writing-a-cordapp upgrade-notes + upgrading-cordapps cordapp-build-systems building-against-master corda-api diff --git a/docs/source/resources/flow-interface.png b/docs/source/resources/flow-interface.png new file mode 100644 index 0000000000000000000000000000000000000000..ab38ad3d2b88313a57b14c120fc6756957c265d8 GIT binary patch literal 35805 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 literal 0 HcmV?d00001 diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index da66072fbe..b7add79336 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -1,5 +1,5 @@ -Upgrading a CorDapp to a new version -==================================== +Upgrading a CorDapp to a new platform version +============================================= These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our first public Beta (:ref:`Milestone 12 `), to :ref:`V1.0 `. diff --git a/docs/source/upgrading-cordapps.rst b/docs/source/upgrading-cordapps.rst new file mode 100644 index 0000000000..f7f8a6b038 --- /dev/null +++ b/docs/source/upgrading-cordapps.rst @@ -0,0 +1,417 @@ +Upgrading a CorDapp (outside of platform version upgrades) +========================================================== + +.. note:: This document only concerns the upgrading of CorDapps and not the Corda platform itself (wire format, node + database schemas, etc.). + +.. contents:: + +CorDapp versioning +------------------ +The Corda platform does not mandate a version number on a per-CorDapp basis. Different elements of a CorDapp are +allowed to evolve separately: + +* States +* Contracts +* Services +* Flows +* Utilities and library functions +* All, or a subset, of the above + +Sometimes, however, a change to one element will require changes to other elements. For example, changing a shared data +structure may require flow changes that are not backwards-compatible. + +Areas of consideration +---------------------- +This document will consider the following types of versioning: + +* Flow versioning +* State and contract versioning +* State and state schema versioning +* Serialisation of custom types + +Flow versioning +--------------- +Any flow that initiates other flows must be annotated with the ``@InitiatingFlow`` annotation, which is defined as: + +.. sourcecode:: kotlin + + annotation class InitiatingFlow(val version: Int = 1) + +The ``version`` property, which defaults to 1, specifies the flow's version. This integer value should be incremented +whenever there is a release of a flow which has changes that are not backwards-compatible. A non-backwards compatible +change is one that changes the interface of the flow. + +Currently, CorDapp developers have to explicitly write logic to handle these flow version numbers. In the future, +however, the platform will use prescribed rules for handling versions. + +What defines the interface of a flow? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The flow interface is defined by the sequence of ``send`` and ``receive`` calls between an ``InitiatingFlow`` and an +``InitiatedBy`` flow, including the types of the data sent and received. We can picture a flow's interface as follows: + +.. image:: resources/flow-interface.png + :scale: 50% + :align: center + +In the diagram above, the ``InitiatingFlow``: + +* Sends an ``Int`` +* Receives a ``String`` +* Sends a ``String`` +* Receives a ``CustomType`` + +The ``InitiatedBy`` flow does the opposite: + +* Receives an ``Int`` +* Sends a ``String`` +* Receives a ``String`` +* Sends a ``CustomType`` + +As long as both the ``IntiatingFlow`` and the ``InitiatedBy`` flows conform to the sequence of actions, the flows can +be implemented in any way you see fit (including adding proprietary business logic that is not shared with other +parties). + +What constitutes a non-backwards compatible flow change? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A flow can become backwards-incompatible in two main ways: + +* The sequence of ``send`` and ``receive`` calls changes: + + * A ``send`` or ``receive`` is added or removed from either the ``InitatingFlow`` or ``InitiatedBy`` flow + * The sequence of ``send`` and ``receive`` calls changes + +* The types of the ``send`` and ``receive`` calls changes + +What happens when running flows with incompatible versions? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Pairs of ``InitiatingFlow`` flows and ``InitiatedBy`` flows that have incompatible interfaces are likely to exhibit the +following behaviour: + +* The flows hang indefinitely and never terminate, usually because a flow expects a response which is never sent from + the other side +* One of the flow ends with an exception: "Expected Type X but Received Type Y", because the ``send`` or ``receive`` + types are incorrect +* One of the flows ends with an exception: "Counterparty flow terminated early on the other side", because one flow + sends some data to another flow, but the latter flow has already ended + +How do I upgrade my flows? +~~~~~~~~~~~~~~~~~~~~~~~~~~ +For flag-day upgrades, the process is simple. + +Assumptions +^^^^^^^^^^^ + +* All nodes in the business network can be shut down for a period of time +* All nodes retire the old flows and adopt the new flows at the same time + +Process +^^^^^^^ + +1. Update the flow and test the changes. Increment the flow version number in the ``InitiatingFlow`` annotation +2. Ensure that all versions of the existing flow have finished running and there are no pending ``SchedulableFlows`` on + any of the nodes on the business network +3. Shut down all the nodes +4. Replace the existing CorDapp JAR with the CorDapp JAR containing the new flow +5. Start the nodes + +From this point onwards, all the nodes will be using the updated flows. + +In situations where some nodes may still be using previous versions of a flow, the updated flows need to be +backwards-compatible. + +How do I ensure flow backwards-compatibility? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``InitiatingFlow`` version number is included in the flow session handshake and exposed to both parties via the +``FlowLogic.getFlowContext`` method. This method takes a ``Party`` and returns a ``FlowContext`` object which describes +the flow running on the other side. In particular, it has a ``flowVersion`` property which can be used to +programmatically evolve flows across versions. For example: + +.. sourcecode:: kotlin + + @Suspendable + override fun call() { + val otherFlowVersion = otherSession.getCounterpartyFlowInfo().flowVersion + val receivedString = if (otherFlowVersion == 1) { + receive(otherParty).unwrap { it.toString() } + } else { + receive(otherParty).unwrap { it } + } + } + +This code shows a flow that in its first version expected to receive an Int, but in subsequent versions was modified to +expect a String. This flow is still able to communicate with parties that are running the older CorDapp containing +the older flow. + +How do I deal with interface changes to inlined subflows? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Here is an example of an in-lined subflow: + +.. sourcecode:: kotlin + + @StartableByRPC + @InitiatingFlow + class FlowA(val recipient: Party) : FlowLogic() { + @Suspendable + override fun call() { + subFlow(FlowB(recipient)) + } + } + + @InitiatedBy(FlowA::class) + class FlowC(val otherSession: FlowSession) : FlowLogic() { + // Omitted. + } + + // Note: No annotations. This is used as an inlined subflow. + class FlowB(val recipient: Party) : FlowLogic() { + @Suspendable + override fun call() { + val message = "I'm an inlined subflow, so I inherit the @InitiatingFlow's session ID and type." + initiateFlow(recipient).send(message) + } + } + +Inlined subflows are treated as being the flow that invoked them when initiating a new flow session with a counterparty. +Suppose flow ``A`` calls inlined subflow B, which, in turn, initiates a session with a counterparty. The ``FlowLogic`` +type used by the counterparty to determine which counter-flow to invoke is determined by ``A``, and not by ``B``. This +means that the response logic for the inlined flow must be implemented explicitly in the ``InitiatedBy`` flow. This can +be done either by calling a matching inlined counter-flow, or by implementing the other side explicitly in the +initiated parent flow. Inlined subflows also inherit the session IDs of their parent flow. + +As such, an interface change to an inlined subflow must be considered a change to the parent flow interfaces. + +An example of an inlined subflow is ``CollectSignaturesFlow``. It has a response flow called ``SignTransactionFlow`` +that isn’t annotated with ``InitiatedBy``. This is because both of these flows are inlined. How these flows speak to +one another is defined by the parent flows that call ``CollectSignaturesFlow`` and ``SignTransactionFlow``. + +In code, inlined subflows appear as regular ``FlowLogic`` instances without either an ``InitiatingFlow`` or an +``InitiatedBy`` annotation. + +Inlined flows are not versioned, as they inherit the version of their parent ``InitiatingFlow`` or ``InitiatedBy`` +flow. + +Are there any other considerations? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Suspended flows +^^^^^^^^^^^^^^^ +Currently, serialised flow state machines persisted in the node's database cannot be updated. All flows must finish +before the updated flow classes are added to the node's plugins folder. + +Flows that don't create sessions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Flows which are not an ``InitiatingFlow`` or ``InitiatedBy`` flow, or inlined subflows that are not called from an +``InitiatingFlow`` or ``InitiatedBy`` flow, can be updated without consideration of backwards-compatibility. Flows of +this type include utility flows for querying the vault and flows for reaching out to external systems. + +Contract and state versioning +----------------------------- +Contracts and states can be upgraded if and only if all of the state's participants agree to the proposed upgrade. The +following combinations of upgrades are possible: + +* A contract is upgraded while the state definition remains the same +* A state is upgraded while the contract stays the same +* The state and the contract are updated simultaneously + +The procedure for updating a state or a contract using a flag-day approach is quite simple: + +* Update and test the state or contract +* Stop all the nodes on the business network +* Produce a new CorDapp JAR file and distribute it to all the relevant parties +* Start all nodes on the network +* Run the contract upgrade authorisation flow for each state that requires updating on every node +* For each state, one node should run the contract upgrade initiation flow + +Update Process +~~~~~~~~~~~~~~ + +Writing the new state and contract definitions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Start by updating the contract and/or state definitions. There are no restrictions on how states are updated. However, +upgraded contracts must implement the ``UpgradedContract`` interface. This interface is defined as: + +.. sourcecode:: kotlin + + interface UpgradedContract : Contract { + val legacyContract: ContractClassName + fun upgrade(state: OldState): NewState + } + +The ``upgrade`` method describes how the old state type is upgraded to the new state type. When the state isn't being +upgraded, the same state type can be used for both the old and new state type parameters. + +Authorising the upgrade +^^^^^^^^^^^^^^^^^^^^^^^ +Once the new states and contracts are on the classpath for all the relevant nodes, the next step is for all nodes to +run the ``ContractUpgradeFlow.Authorise`` flow. This flow takes a ``StateAndRef`` of the state to update as well as a +reference to the new contract, which must implement the ``UpgradedContract`` interface. + +At any point, a node administrator may de-authorise a contract upgrade by running the +``ContractUpgradeFlow.Deauthorise`` flow. + +Performing the upgrade +^^^^^^^^^^^^^^^^^^^^^^ +Once all nodes have performed the authorisation process, a participant must be chosen to initiate the upgrade via the +``ContractUpgradeFlow.Initiate`` flow for each state object. This flow has the following signature: + +.. sourcecode:: kotlin + + class Initiate( + originalState: StateAndRef, + newContractClass: Class> + ) : AbstractStateReplacementFlow.Instigator>>(originalState, newContractClass) + +This flow sub-classes ``AbstractStateReplacementFlow``, which can be used to upgrade state objects that do not need a +contract upgrade. + +One the flow ends successfully, all the participants of the old state object should have the upgraded state object +which references the new contract code. + +Points to note +~~~~~~~~~~~~~~ + +Capabilities of the contract upgrade flows +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Despite its name, the ``ContractUpgradeFlow`` also handles the update of state object definitions +* The state can completely change as part of an upgrade! For example, it is possible to transmute a ``Cat`` state into + a ``Dog`` state, provided that all participants in the ``Cat`` state agree to the change +* Equally, the state doesn't have to change at all +* If a node has not yet run the contract upgrade authorisation flow, they will not be able to upgrade the contract + and/or state objects +* Upgrade authorisations can subsequently be deauthorised +* Upgrades do not have to happen immediately. For a period, the two parties can use the old states and contracts + side-by-side +* State schema changes are handled separately + +Writing new states and contracts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* If a property is removed from a state, any references to it must be removed from the contract code. Otherwise, you + will not be able to compile your contract code. It is generally not advisable to remove properties from states. Mark + them as deprecated instead +* When adding properties to a state, consider how the new properties will affect transaction validation involving this + state. If the contract is not updated to add constraints over the new properties, they will be able to take on any + value +* Updated state objects can use the old contract code as long as there is no requirement to update it + +Dealing with old contract code JAR files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Currently, all parties **must** keep the old state and contract definitions on their node's classpath as they will + always be required to verify transactions involving previous versions of the state using previous versions of the + contract + + * This will change when the contract code as an attachment feature has been fully implemented. + +Permissioning +^^^^^^^^^^^^^ +* Only node administrators are able to run the contract upgrade authorisation and deauthorisation flows + +Logistics +^^^^^^^^^ +* All nodes need to run the contract upgrade authorisation flow +* Only one node should run the contract upgrade initiation flow. If multiple nodes run it for the same ``StateRef``, a + double-spend will occur for all but the first completed upgrade +* The supplied upgrade flows upgrade one state object at a time + +Serialisation +------------- + +Currently, the serialisation format for everything except flow checkpoints (which uses a Kryo-based format) is based +upon AMQP 1.0, a self-describing and controllable serialisation format. AMQP is desirable because it allows us to have +a schema describing what has been serialized alongside the data itself. This assists with versioning and deserialising +long-ago archived data, among other things. + +Writing classes +~~~~~~~~~~~~~~~ +Although not strictly related to versioning, AMQP serialisation dictates that we must write our classes in a particular way: + +* Your class must have a constructor that takes all the properties that you wish to record in the serialized form. This + is required in order for the serialization framework to reconstruct an instance of your class +* If more than one constructor is provided, the serialization framework needs to know which one to use. The + ``@ConstructorForDeserialization`` annotation can be used to indicate the chosen constructor. For a Kotlin class + without the ``@ConstructorForDeserialization`` annotation, the primary constructor is selected +* The class must be compiled with parameter names in the .class file. This is the default in Kotlin but must be turned + on in Java (using the ``-parameters`` command line option to ``javac``) +* Your class must provide a Java Bean getter for each of the properties in the constructor, with a matching name. For + example, if a class has the constructor parameter ``foo``, there must be a getter called ``getFoo()``. If ``foo`` is + a boolean, the getter may optionally be called ``isFoo()``. This is why the class must be compiled with parameter + names turned on +* The class must be annotated with ``@CordaSerializable`` +* The declared types of constructor arguments/getters must be supported, and where generics are used the generic + parameter must be a supported type, an open wildcard (*), or a bounded wildcard which is currently widened to an open + wildcard +* Any superclass must adhere to the same rules, but can be abstract +* Object graph cycles are not supported, so an object cannot refer to itself, directly or indirectly + +Writing enums +~~~~~~~~~~~~~ +Elements cannot be added to enums in a new version of the code. Hence, enums are only a good fit for genuinely static +data that will never change (e.g. days of the week). A ``Buy`` or ``Sell`` flag is another. However, something like +``Trade Type`` or ``Currency Code`` will likely change. For those, it is preferable to choose another representation, +such as a string. + +State schemas +------------- +By default, all state objects are serialised to the database as a string of bytes and referenced by their ``StateRef``. +However, it is also possible to define custom schemas for serialising particular properties or combinations of +properties, so that they can be queried from a source other than the Corda Vault. This is done by implementing the +``QueryableState`` interface and creating a custom object relational mapper for the state. See :doc:`api-persistence` +for details. + +For backwards compatible changes such as adding columns, the procedure for upgrading a state schema is to extend the +existing object relational mapper. For example, we can update: + +.. sourcecode:: kotlin + + object ObligationSchemaV1 : MappedSchema(Obligation::class.java, 1, listOf(ObligationEntity::class.java)) { + @Entity @Table(name = "obligations") + class ObligationEntity(obligation: Obligation) : PersistentState() { + @Column var currency: String = obligation.amount.token.toString() + @Column var amount: Long = obligation.amount.quantity + @Column @Lob var lender: ByteArray = obligation.lender.owningKey.encoded + @Column @Lob var borrower: ByteArray = obligation.borrower.owningKey.encoded + @Column var linear_id: String = obligation.linearId.id.toString() + } + } + +To: + +.. sourcecode:: kotlin + + object ObligationSchemaV1 : MappedSchema(Obligation::class.java, 1, listOf(ObligationEntity::class.java)) { + @Entity @Table(name = "obligations") + class ObligationEntity(obligation: Obligation) : PersistentState() { + @Column var currency: String = obligation.amount.token.toString() + @Column var amount: Long = obligation.amount.quantity + @Column @Lob var lender: ByteArray = obligation.lender.owningKey.encoded + @Column @Lob var borrower: ByteArray = obligation.borrower.owningKey.encoded + @Column var linear_id: String = obligation.linearId.id.toString() + @Column var defaulted: Bool = obligation.amount.inDefault // NEW COLUNM! + } + } + +Thus adding a new column with a default value. + +To make a non-backwards compatible change, the ``ContractUpgradeFlow`` or ``AbstractStateReplacementFlow`` must be +used, as changes to the state are required. To make a backwards-incompatible change such as deleting a column (e.g. +because a property was removed from a state object), the procedure is to define another object relational mapper, then +add it to the ``supportedSchemas`` property of your ``QueryableState``, like so: + +.. sourcecode:: kotlin + + override fun supportedSchemas(): Iterable = listOf(ExampleSchemaV1, ExampleSchemaV2) + +Then, in ``generateMappedObject``, add support for the new schema: + +.. sourcecode:: kotlin + + override fun generateMappedObject(schema: MappedSchema): PersistentState { + return when (schema) { + is DummyLinearStateSchemaV1 -> // Omitted. + is DummyLinearStateSchemaV2 -> // Omitted. + else -> throw IllegalArgumentException("Unrecognised schema $schema") + } + } + +With this approach, whenever the state object is stored in the vault, a representation of it will be stored in two +separate database tables where possible - one for each supported schema. \ No newline at end of file diff --git a/docs/source/versioning.rst b/docs/source/versioning.rst index ec6d4dfae1..62b7cdc7a2 100644 --- a/docs/source/versioning.rst +++ b/docs/source/versioning.rst @@ -8,9 +8,6 @@ friendly for a developer working on the platform. It first has to be parsed and which to determine API differences. The release version is still useful and every MQ message the node sends attaches it to the ``release-version`` header property for debugging purposes. -Platform Version ----------------- - It is much easier to use a single incrementing integer value to represent the API version of the Corda platform, which is called the Platform Version. It is similar to Android's `API Level `_. It starts at 1 and will increment by exactly 1 for each release which changes any of the publicly exposed APIs in the @@ -26,47 +23,4 @@ for the network. .. note:: A future release may introduce the concept of a target platform version, which would be similar to Android's ``targetSdkVersion``, and would provide a means of maintaining behavioural compatibility for the cases where the - platform's behaviour has changed. - -Flow versioning ---------------- - -In addition to the evolution of the platform, flows that run on top of the platform can also evolve. It may be that the -flow protocol between an initiating flow and its initiated flow changes from one CorDapp release to the next in such a -way to be backward incompatible with existing flows. For example, if a sequence of sends and receives needs to change -or if the semantics of a particular receive changes. - -The ``InitiatingFlow`` annotation (see :doc:`flow-state-machine` for more information on the flow annotations) has a ``version`` -property, which if not specified defaults to 1. This flow version is included in the flow session handshake and exposed -to both parties in the communication via ``FlowLogic.getFlowContext``. This takes in a ``Party`` and will return a -``FlowContext`` object which describes the flow running on the other side. In particular it has the ``flowVersion`` property -which can be used to programmatically evolve flows across versions. - -.. container:: codeset - - .. sourcecode:: kotlin - - @Suspendable - override fun call() { - val flowVersionOfOtherParty = getFlowContext(otherParty).flowVersion - val receivedString = if (flowVersionOfOtherParty == 1) { - receive(otherParty).unwrap { it.toString() } - } else { - receive(otherParty).unwrap { it } - } - } - -The above shows an example evolution of a flow which in the first version was expecting to receive an Int, but then -in subsequent versions was relaxed to receive a String. This flow is still able to communicate with parties which are -running the older flow (or rather older CorDapps containing the older flow). - -.. warning:: It's important that ``InitiatingFlow.version`` be incremented each time the flow protocol changes in an - incompatible way. - -``FlowContext`` also has ``appName`` which is the name of the CorDapp hosting the flow. This can be used to determine -implementation details of the CorDapp. See :doc:`cordapp-build-systems` for more information on the CorDapp filename. - -.. note:: Currently changing any of the properties of a ``CordaSerializable`` type is also backwards incompatible and - requires incrementing of ``InitiatingFlow.version``. This will be relaxed somewhat once the AMQP wire serialisation - format is implemented as it will automatically handle a lot of the data type migration cases. - + platform's behaviour has changed. \ No newline at end of file From 89b64da12b320c21ac7c7f310893e831e2ee86d0 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 12 Jan 2018 17:16:10 +0000 Subject: [PATCH 02/16] Increase shutdown action timeout. In some tests using driver, the shutdown can be invoked before the notary node gets started, and the node disposal action times out, leading to unclosed message brokers and port conflicts. --- .../kotlin/net/corda/testing/node/internal/ShutdownManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt index 57114425f6..6e5f7c02af 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt @@ -41,14 +41,14 @@ class ShutdownManager(private val executorService: ExecutorService) { } } - val shutdowns = shutdownActionFutures.map { Try.on { it.getOrThrow(1.seconds) } } + val shutdowns = shutdownActionFutures.map { Try.on { it.getOrThrow(60.seconds) } } shutdowns.reversed().forEach { when (it) { is Try.Success -> try { it.value() } catch (t: Throwable) { - log.warn("Exception while shutting down", t) + log.warn("Exception while calling a shutdown action, this might create resource leaks", t) } is Try.Failure -> log.warn("Exception while getting shutdown method, disregarding", it.exception) } From 219d6f8b12cf168781b9c8a34745d44d2508b6c5 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 12 Jan 2018 17:19:56 +0000 Subject: [PATCH 03/16] Un-ignore several integration tests --- .../net/corda/node/NodeKeystoreCheckTest.kt | 4 ++-- .../kotlin/net/corda/node/SSHServerTest.kt | 19 +++++++------------ .../node/services/AttachmentLoadingTests.kt | 10 +++++----- .../node/services/DistributedServiceTests.kt | 3 --- .../node/services/RaftNotaryServiceTests.kt | 2 -- 5 files changed, 14 insertions(+), 24 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt index 4a3992213e..d2268941d3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeKeystoreCheckTest.kt @@ -19,7 +19,7 @@ import kotlin.test.assertTrue class NodeKeystoreCheckTest { @Test fun `starting node in non-dev mode with no key store`() { - driver(startNodesInProcess = true) { + driver(startNodesInProcess = true, notarySpecs = emptyList()) { assertThatThrownBy { startNode(customOverrides = mapOf("devMode" to false)).getOrThrow() }.hasMessageContaining("Identity certificate not found") @@ -28,7 +28,7 @@ class NodeKeystoreCheckTest { @Test fun `node should throw exception if cert path doesn't chain to the trust root`() { - driver(startNodesInProcess = true) { + driver(startNodesInProcess = true, notarySpecs = emptyList()) { // Create keystores val keystorePassword = "password" val config = object : SSLConfiguration { diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt index 78e80c4913..67365bf422 100644 --- a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt @@ -4,27 +4,27 @@ import co.paralleluniverse.fibers.Suspendable import com.jcraft.jsch.ChannelExec import com.jcraft.jsch.JSch import com.jcraft.jsch.JSchException -import net.corda.core.flows.* +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.internal.config.User +import net.corda.testing.ALICE_NAME import net.corda.testing.driver.driver +import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.util.io.Streams import org.junit.Test -import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.testing.ALICE_NAME import java.net.ConnectException +import java.util.regex.Pattern import kotlin.test.assertTrue import kotlin.test.fail -import org.assertj.core.api.Assertions.assertThat -import org.junit.Ignore -import java.util.regex.Pattern class SSHServerTest { - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test() fun `ssh server does not start be default`() { val user = User("u", "p", setOf()) @@ -46,7 +46,6 @@ class SSHServerTest { } } - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `ssh server starts when configured`() { val user = User("u", "p", setOf()) @@ -66,8 +65,6 @@ class SSHServerTest { } } - - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `ssh server verify credentials`() { val user = User("u", "p", setOf()) @@ -91,7 +88,6 @@ class SSHServerTest { } } - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `ssh respects permissions`() { val user = User("u", "p", setOf(startFlow())) @@ -122,7 +118,6 @@ class SSHServerTest { } } - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `ssh runs flows`() { val user = User("u", "p", setOf(startFlow())) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 3da63fedde..7a7406cb76 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -20,15 +20,17 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.testing.* +import net.corda.testing.DUMMY_BANK_A_NAME +import net.corda.testing.DUMMY_NOTARY_NAME +import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.TestIdentity import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.withoutTestSerialization import net.corda.testing.services.MockAttachmentStorage -import net.corda.testing.internal.rigorousMock import org.junit.Assert.assertEquals -import org.junit.Ignore import org.junit.Rule import org.junit.Test import java.net.URLClassLoader @@ -100,7 +102,6 @@ class AttachmentLoadingTests { assertEquals(expected, actual) } - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `test that attachments retrieved over the network are not used for code`() = withoutTestSerialization { driver { @@ -113,7 +114,6 @@ class AttachmentLoadingTests { Unit } - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `tests that if the attachment is loaded on both sides already that a flow can run`() = withoutTestSerialization { driver { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 92723d52d8..32a09d06a9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -20,7 +20,6 @@ import net.corda.testing.driver.driver import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec import org.assertj.core.api.Assertions.assertThat -import org.junit.Ignore import org.junit.Test import rx.Observable import java.util.* @@ -74,7 +73,6 @@ class DistributedServiceTests { // TODO Use a dummy distributed service rather than a Raft Notary Service as this test is only about Artemis' ability // to handle distributed services - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `requests are distributed evenly amongst the nodes`() = setup { // Issue 100 pounds, then pay ourselves 50x2 pounds @@ -103,7 +101,6 @@ class DistributedServiceTests { } // TODO This should be in RaftNotaryServiceTests - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `cluster survives if a notary is killed`() = setup { // Issue 100 pounds, then pay ourselves 10x5 pounds diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index 67b6670629..4f70f23f5d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -20,7 +20,6 @@ import net.corda.testing.dummyCommand import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec import net.corda.testing.node.startFlow -import org.junit.Ignore import org.junit.Test import java.util.* import kotlin.test.assertEquals @@ -29,7 +28,6 @@ import kotlin.test.assertFailsWith class RaftNotaryServiceTests { private val notaryName = CordaX500Name("RAFT Notary Service", "London", "GB") - @Ignore("Test has undeterministic capacity to hang, ignore till fixed") @Test fun `detect double spend`() { driver( From 7311111c9cf1b14da8ace1ee2c5e9ca1fa0d8229 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 12 Jan 2018 20:25:49 +0000 Subject: [PATCH 04/16] Re-enable notary idempotence test (#2356) * Re-enable notary idempotence test --- .../transactions/NotaryServiceTests.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 77c5cf797b..a162c77a54 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -19,17 +19,17 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters -import net.corda.testing.singleIdentity import net.corda.testing.node.startFlow +import net.corda.testing.singleIdentity import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Test import java.time.Instant import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertTrue class NotaryServiceTests { private lateinit var mockNet: MockNetwork @@ -100,9 +100,8 @@ class NotaryServiceTests { assertThat(ex.error).isInstanceOf(NotaryError.TimeWindowInvalid::class.java) } - @Ignore("Only applies to deterministic signature schemes (e.g. EdDSA) and when deterministic metadata is attached (no timestamps or nonces)") @Test - fun `should sign identical transaction multiple times (signing is idempotent)`() { + fun `should sign identical transaction multiple times (notarisation is idempotent)`() { val stx = run { val inputState = issueState(aliceServices, alice) val tx = TransactionBuilder(notary) @@ -118,7 +117,16 @@ class NotaryServiceTests { mockNet.runNetwork() - assertEquals(f1.resultFuture.getOrThrow(), f2.resultFuture.getOrThrow()) + // Note that the notary will only return identical signatures when using deterministic signature + // schemes (e.g. EdDSA) and when deterministic metadata is attached (no timestamps or nonces). + // We only really care that both signatures are over the same transaction and by the same notary. + val sig1 = f1.resultFuture.getOrThrow().single() + assertEquals(sig1.by, notary.owningKey) + assertTrue(sig1.isValid(stx.id)) + + val sig2 = f2.resultFuture.getOrThrow().single() + assertEquals(sig2.by, notary.owningKey) + assertTrue(sig2.isValid(stx.id)) } @Test From fb1d3087dec8242c6935734e632d91534836c65e Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Sun, 17 Dec 2017 14:53:00 +0000 Subject: [PATCH 05/16] Raft notaries can share a single key pair for the service identity (in contrast to a shared composite public key, and individual signing key pairs). This allows adjusting the cluster size on the fly. --- .../nodeapi/internal/DevIdentityGenerator.kt | 60 +++++++---- .../node/services/BFTNotaryServiceTests.kt | 2 +- .../node/services/DistributedServiceTests.kt | 101 +++++++++++------- .../net/corda/node/internal/AbstractNode.kt | 8 +- .../net/corda/notarydemo/BFTNotaryCordform.kt | 2 +- .../kotlin/net/corda/notarydemo/Notarise.kt | 4 +- .../corda/notarydemo/RaftNotaryCordform.kt | 2 +- .../net/corda/testing/node/NotarySpec.kt | 6 +- .../testing/node/internal/DriverDSLImpl.kt | 57 +++++++--- .../testing/node/internal/DummyClusterSpec.kt | 20 ++++ 10 files changed, 174 insertions(+), 88 deletions(-) create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DummyClusterSpec.kt diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index 9ccc286f4f..0965db3a42 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -12,6 +12,8 @@ import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.crypto.* import org.slf4j.LoggerFactory import java.nio.file.Path +import java.security.KeyPair +import java.security.PublicKey /** * Contains utility methods for generating identities for a node. @@ -42,33 +44,47 @@ object DevIdentityGenerator { return identity.party } - fun generateDistributedNotaryIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { + fun generateDistributedNotaryCompositeIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { require(dirs.isNotEmpty()) - log.trace { "Generating identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } + log.trace { "Generating composite identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } - val compositeKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) - + val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) keyPairs.zip(dirs) { keyPair, nodeDir -> - val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey -> - X509Utilities.createCertificate( - CertificateType.SERVICE_IDENTITY, - DEV_INTERMEDIATE_CA.certificate, - DEV_INTERMEDIATE_CA.keyPair, - notaryName.x500Principal, - publicKey) - } - val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" - val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass") - keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) - keystore.setKeyEntry( - "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", - keyPair.private, - "cordacadevkeypass".toCharArray(), - arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) - keystore.save(distServKeyStoreFile, "cordacadevpass") + generateCertificates(keyPair, notaryKey, notaryName, nodeDir) } + return Party(notaryName, notaryKey) + } - return Party(notaryName, compositeKey) + fun generateDistributedNotarySingularIdentity(dirs: List, notaryName: CordaX500Name): Party { + require(dirs.isNotEmpty()) + + log.trace { "Generating singular identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } + val keyPair = generateKeyPair() + val notaryKey = keyPair.public + dirs.forEach { dir -> + generateCertificates(keyPair, notaryKey, notaryName, dir) + } + return Party(notaryName, notaryKey) + } + + private fun generateCertificates(keyPair: KeyPair, notaryKey: PublicKey, notaryName: CordaX500Name, nodeDir: Path) { + val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, notaryKey).map { publicKey -> + X509Utilities.createCertificate( + CertificateType.SERVICE_IDENTITY, + DEV_INTERMEDIATE_CA.certificate, + DEV_INTERMEDIATE_CA.keyPair, + notaryName.x500Principal, + publicKey) + } + val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" + val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass") + keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) + keystore.setKeyEntry( + "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", + keyPair.private, + "cordacadevkeypass".toCharArray(), + arrayOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate)) + keystore.save(distServKeyStoreFile, "cordacadevpass") } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 0fa5ed8d36..bdaec3ec16 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -60,7 +60,7 @@ class BFTNotaryServiceTests { (Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists? val replicaIds = (0 until clusterSize) - notary = DevIdentityGenerator.generateDistributedNotaryIdentity( + notary = DevIdentityGenerator.generateDistributedNotaryCompositeIdentity( replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, CordaX500Name("BFT", "Zurich", "CH")) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 32a09d06a9..2d7e4b66a7 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -17,8 +17,8 @@ import net.corda.nodeapi.internal.config.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver -import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.DummyClusterSpec import org.assertj.core.api.Assertions.assertThat import org.junit.Test import rx.Observable @@ -31,18 +31,22 @@ class DistributedServiceTests { private lateinit var raftNotaryIdentity: Party private lateinit var notaryStateMachines: Observable> - private fun setup(testBlock: () -> Unit) { + private fun setup(compositeIdentity: Boolean = false, testBlock: () -> Unit) { val testUser = User("test", "test", permissions = setOf( startFlow(), startFlow(), invokeRpc(CordaRPCOps::nodeInfo), invokeRpc(CordaRPCOps::stateMachinesFeed)) ) - driver( extraCordappPackagesToScan = listOf("net.corda.finance.contracts"), - notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3)))) - { + notarySpecs = listOf( + NotarySpec( + DUMMY_NOTARY_NAME, + rpcUsers = listOf(testUser), + cluster = DummyClusterSpec(clusterSize = 3, compositeServiceIdentity = compositeIdentity)) + ) + ) { alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(testUser)).getOrThrow() raftNotaryIdentity = defaultNotaryIdentity notaryNodes = defaultNotaryHandle.nodeHandles.getOrThrow().map { it as NodeHandle.OutOfProcess } @@ -71,10 +75,60 @@ class DistributedServiceTests { } } + // TODO This should be in RaftNotaryServiceTests + @Test + fun `cluster survives if a notary is killed`() { + setup { + // Issue 100 pounds, then pay ourselves 10x5 pounds + issueCash(100.POUNDS) + + for (i in 1..10) { + paySelf(5.POUNDS) + } + + // Now kill a notary node + with(notaryNodes[0].process) { + destroy() + waitFor() + } + + // Pay ourselves another 20x5 pounds + for (i in 1..20) { + paySelf(5.POUNDS) + } + + val notarisationsPerNotary = HashMap() + notaryStateMachines.expectEvents(isStrict = false) { + replicate>(30) { + expect(match = { it.second is StateMachineUpdate.Added }) { (notary, update) -> + update as StateMachineUpdate.Added + notarisationsPerNotary.compute(notary) { _, number -> number?.plus(1) ?: 1 } + } + } + } + + println("Notarisation distribution: $notarisationsPerNotary") + require(notarisationsPerNotary.size == 3) + } + } + // TODO Use a dummy distributed service rather than a Raft Notary Service as this test is only about Artemis' ability // to handle distributed services @Test - fun `requests are distributed evenly amongst the nodes`() = setup { + fun `requests are distributed evenly amongst the nodes`() { + setup { + checkRequestsDistributedEvenly() + } + } + + @Test + fun `requests are distributed evenly amongst the nodes with a composite public key`() { + setup(true) { + checkRequestsDistributedEvenly() + } + } + + private fun checkRequestsDistributedEvenly() { // Issue 100 pounds, then pay ourselves 50x2 pounds issueCash(100.POUNDS) @@ -100,41 +154,6 @@ class DistributedServiceTests { require(notarisationsPerNotary.values.all { it > 10 }) } - // TODO This should be in RaftNotaryServiceTests - @Test - fun `cluster survives if a notary is killed`() = setup { - // Issue 100 pounds, then pay ourselves 10x5 pounds - issueCash(100.POUNDS) - - for (i in 1..10) { - paySelf(5.POUNDS) - } - - // Now kill a notary node - with(notaryNodes[0].process) { - destroy() - waitFor() - } - - // Pay ourselves another 20x5 pounds - for (i in 1..20) { - paySelf(5.POUNDS) - } - - val notarisationsPerNotary = HashMap() - notaryStateMachines.expectEvents(isStrict = false) { - replicate>(30) { - expect(match = { it.second is StateMachineUpdate.Added }) { (notary, update) -> - update as StateMachineUpdate.Added - notarisationsPerNotary.compute(notary) { _, number -> number?.plus(1) ?: 1 } - } - } - } - - println("Notarisation distribution: $notarisationsPerNotary") - require(notarisationsPerNotary.size == 3) - } - private fun issueCash(amount: Amount) { aliceProxy.startFlow(::CashIssueFlow, amount, OpaqueBytes.of(0), raftNotaryIdentity).returnValue.getOrThrow() } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 0f988ff0a1..3d50368c72 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -173,11 +173,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } private inline fun signNodeInfo(nodeInfo: NodeInfo, sign: (PublicKey, SerializedBytes) -> DigitalSignature): SignedNodeInfo { - // For now we assume the node has only one identity (excluding any composite ones) - val owningKey = nodeInfo.legalIdentities.single { it.owningKey !is CompositeKey }.owningKey + // For now we exclude any composite identities, see [SignedNodeInfo] + val owningKeys = nodeInfo.legalIdentities.map { it.owningKey }.filter { it !is CompositeKey } val serialised = nodeInfo.serialize() - val signature = sign(owningKey, serialised) - return SignedNodeInfo(serialised, listOf(signature)) + val signatures = owningKeys.map { sign(it, serialised) } + return SignedNodeInfo(serialised, signatures) } open fun generateAndSaveNodeInfo(): NodeInfo { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 1b2967c953..8e4028956f 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -62,7 +62,7 @@ class BFTNotaryCordform : CordformDefinition() { } override fun setup(context: CordformContext) { - DevIdentityGenerator.generateDistributedNotaryIdentity( + DevIdentityGenerator.generateDistributedNotaryCompositeIdentity( notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, minCorrectReplicas(clusterSize) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt index 6a59809cf6..a86a6be0ea 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Notarise.kt @@ -1,6 +1,7 @@ package net.corda.notarydemo import net.corda.client.rpc.CordaRPCClient +import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.toStringShort import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.CordaRPCOps @@ -38,7 +39,8 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) { /** Makes calls to the node rpc to start transaction notarisation. */ fun notarise(count: Int) { - println("Notary: \"${notary.name}\", with composite key: ${notary.owningKey.toStringShort()}") + val keyType = if (notary.owningKey is CompositeKey) "composite" else "public" + println("Notary: \"${notary.name}\", with $keyType key: ${notary.owningKey.toStringShort()}") val transactions = buildTransactions(count) println("Notarised ${transactions.size} transactions:") transactions.zip(notariseTransactions(transactions)).forEach { (tx, signersFuture) -> diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index abceabbe77..f999ad68e6 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -58,7 +58,7 @@ class RaftNotaryCordform : CordformDefinition() { } override fun setup(context: CordformContext) { - DevIdentityGenerator.generateDistributedNotaryIdentity( + DevIdentityGenerator.generateDistributedNotarySingularIdentity( notaryNames.map { context.baseDirectory(it.toString()) }, clusterName ) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt index b6533ebd98..128ed9070b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt @@ -14,10 +14,12 @@ data class NotarySpec( ) @DoNotImplement -sealed class ClusterSpec { +abstract class ClusterSpec { abstract val clusterSize: Int - data class Raft(override val clusterSize: Int) : ClusterSpec() { + data class Raft( + override val clusterSize: Int + ) : ClusterSpec() { init { require(clusterSize > 0) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index e7b65d2572..1e4f710449 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -267,7 +267,7 @@ class DriverDSLImpl( if (cordform.notary == null) continue val name = CordaX500Name.parse(cordform.name) val notaryConfig = ConfigFactory.parseMap(cordform.notary).parseAs() - // We need to first group the nodes that form part of a cluser. We assume for simplicity that nodes of the + // We need to first group the nodes that form part of a cluster. We assume for simplicity that nodes of the // same cluster type and validating flag are part of the same cluster. if (notaryConfig.raft != null) { val key = if (notaryConfig.validating) VALIDATING_RAFT else NON_VALIDATING_RAFT @@ -282,10 +282,17 @@ class DriverDSLImpl( } clusterNodes.asMap().forEach { type, nodeNames -> - val identity = DevIdentityGenerator.generateDistributedNotaryIdentity( - dirs = nodeNames.map { baseDirectory(it) }, - notaryName = type.clusterName - ) + val identity = if (type == ClusterType.NON_VALIDATING_RAFT || type == ClusterType.VALIDATING_RAFT) { + DevIdentityGenerator.generateDistributedNotarySingularIdentity( + dirs = nodeNames.map { baseDirectory(it) }, + notaryName = type.clusterName + ) + } else { + DevIdentityGenerator.generateDistributedNotaryCompositeIdentity( + dirs = nodeNames.map { baseDirectory(it) }, + notaryName = type.clusterName + ) + } notaryInfos += NotaryInfo(identity, type.validating) } @@ -382,13 +389,30 @@ class DriverDSLImpl( private fun startNotaryIdentityGeneration(): CordaFuture> { return executorService.fork { notarySpecs.map { spec -> - val identity = if (spec.cluster == null) { - DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(spec.name), spec.name) - } else { - DevIdentityGenerator.generateDistributedNotaryIdentity( - dirs = generateNodeNames(spec).map { baseDirectory(it) }, - notaryName = spec.name - ) + val identity = when (spec.cluster) { + null -> { + DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(spec.name), spec.name) + } + is ClusterSpec.Raft -> { + DevIdentityGenerator.generateDistributedNotarySingularIdentity( + dirs = generateNodeNames(spec).map { baseDirectory(it) }, + notaryName = spec.name + ) + } + is DummyClusterSpec -> { + if (spec.cluster.compositeServiceIdentity) { + DevIdentityGenerator.generateDistributedNotarySingularIdentity( + dirs = generateNodeNames(spec).map { baseDirectory(it) }, + notaryName = spec.name + ) + } else { + DevIdentityGenerator.generateDistributedNotaryCompositeIdentity( + dirs = generateNodeNames(spec).map { baseDirectory(it) }, + notaryName = spec.name + ) + } + } + else -> throw UnsupportedOperationException("Cluster spec ${spec.cluster} not supported by Driver") } NotaryInfo(identity, spec.validating) } @@ -433,9 +457,12 @@ class DriverDSLImpl( private fun startNotaries(localNetworkMap: LocalNetworkMap?): List>> { return notarySpecs.map { - when { - it.cluster == null -> startSingleNotary(it, localNetworkMap) - it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it, localNetworkMap) + when (it.cluster) { + null -> startSingleNotary(it, localNetworkMap) + is ClusterSpec.Raft, + // DummyCluster is used for testing the notary communication path, and it does not matter + // which underlying consensus algorithm is used, so we just stick to Raft + is DummyClusterSpec -> startRaftNotaryCluster(it, localNetworkMap) else -> throw IllegalArgumentException("BFT-SMaRt not supported") } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DummyClusterSpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DummyClusterSpec.kt new file mode 100644 index 0000000000..13a5d3df2a --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DummyClusterSpec.kt @@ -0,0 +1,20 @@ +package net.corda.testing.node.internal + +import net.corda.testing.node.ClusterSpec + +/** + * Only used for testing the notary communication path. Can be configured to act as a Raft (singular identity), + * or a BFT (composite key identity) notary service. + */ +data class DummyClusterSpec( + override val clusterSize: Int, + /** + * If *true*, the cluster will use a shared composite public key for the service identity, with individual + * private keys. If *false*, the same "singular" key pair will be shared by all replicas. + */ + val compositeServiceIdentity: Boolean = false +) : ClusterSpec() { + init { + require(clusterSize > 0) + } +} \ No newline at end of file From 2082168cf70a5d265e1237528f4e50be9a64ce99 Mon Sep 17 00:00:00 2001 From: igor nitto Date: Mon, 15 Jan 2018 09:57:44 +0000 Subject: [PATCH 06/16] Close Hikari CP around external auth database on shutdown (RPCSecurityManagerImpl.kt) (#2359) --- .../internal/security/RPCSecurityManagerImpl.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt index 2237cc1547..3b1f28dc38 100644 --- a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt @@ -25,6 +25,7 @@ import org.apache.shiro.realm.AuthorizingRealm import org.apache.shiro.realm.jdbc.JdbcRealm import org.apache.shiro.subject.PrincipalCollection import org.apache.shiro.subject.SimplePrincipalCollection +import java.io.Closeable import javax.security.auth.login.FailedLoginException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit @@ -43,10 +44,6 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager { manager = buildImpl(config) } - override fun close() { - manager.destroy() - } - @Throws(FailedLoginException::class) override fun authenticate(principal: String, password: Password): AuthorizingSubject { password.use { @@ -67,6 +64,10 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager { subjectId = SimplePrincipalCollection(principal, id.value), manager = manager) + override fun close() { + manager.realms?.filterIsInstance()?.forEach { it.close() } + manager.destroy() + } companion object { @@ -240,7 +241,7 @@ private class InMemoryRealm(users: List, authorizationInfoByUser[principals.primaryPrincipal as String] } -private class NodeJdbcRealm(config: SecurityConfiguration.AuthService.DataSource) : JdbcRealm() { +private class NodeJdbcRealm(config: SecurityConfiguration.AuthService.DataSource) : JdbcRealm(), Closeable { init { credentialsMatcher = buildCredentialMatcher(config.passwordEncryption) @@ -248,6 +249,10 @@ private class NodeJdbcRealm(config: SecurityConfiguration.AuthService.DataSource dataSource = HikariDataSource(HikariConfig(config.connection!!)) permissionResolver = RPCPermissionResolver } + + override fun close() { + (dataSource as? Closeable)?.close() + } } private typealias ShiroCache = org.apache.shiro.cache.Cache From 91779276fc405835f1f5bf74642dd8c567161748 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Fri, 12 Jan 2018 15:22:56 +0000 Subject: [PATCH 07/16] Use single thread per netty eventgroup during testing --- .../src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt index 9811800de0..34b5bf7784 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisTcpTransport.kt @@ -53,7 +53,8 @@ class ArtemisTcpTransport { // It does not use AMQP messages for its own messages e.g. topology and heartbeats. // TODO further investigate how to ensure we use a well defined wire level protocol for Node to Node communications. TransportConstants.PROTOCOLS_PROP_NAME to "CORE,AMQP", - TransportConstants.USE_GLOBAL_WORKER_POOL_PROP_NAME to (nodeSerializationEnv != null) + TransportConstants.USE_GLOBAL_WORKER_POOL_PROP_NAME to (nodeSerializationEnv != null), + TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1) ) if (config != null && enableSSL) { From df195b20bdf8955e87997929c81d223af0c7a498 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 15 Jan 2018 13:48:55 +0000 Subject: [PATCH 08/16] ENT-1383 Memory weight based transaction cache (#2355) * ENT-1383 Make the transaction cache in DBTransactionStorage memory-weight based (rather than count based) so large transactions can no longer use an undue amount of memory. * Code review: formatting and legibility * Fix stupid type cast error * More formatting --- .../net/corda/node/internal/AbstractNode.kt | 4 +- .../node/services/config/NodeConfiguration.kt | 16 ++++- .../persistence/DBTransactionStorage.kt | 61 ++++++++++++++----- .../node/utilities/AppendOnlyPersistentMap.kt | 59 +++++++++++++++--- .../node/utilities/NonInvalidatingCache.kt | 18 +++++- .../node/messaging/TwoPartyTradeFlowTests.kt | 4 +- .../persistence/DBTransactionStorageTests.kt | 3 +- 7 files changed, 133 insertions(+), 32 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3d50368c72..410bab983d 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -214,7 +214,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService) val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair) identityService.loadIdentities(info.legalIdentitiesAndCerts) - val transactionStorage = makeTransactionStorage(database) + val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes) val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache) val notaryService = makeNotaryService(nodeServices, database) val smm = makeStateMachineManager(database) @@ -559,7 +559,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return tokenizableServices } - protected open fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage = DBTransactionStorage() + protected open fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage = DBTransactionStorage(transactionCacheSizeBytes) private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, smm: StateMachineManager, schemaService: SchemaService, flowLogicRefFactory: FlowLogicRefFactory) { VaultSoftLockManager.install(services.vaultService, smm) ScheduledActivityObserver.install(services.vaultService, schedulerService, flowLogicRefFactory) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index f549aa8bf9..8347c28d21 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -14,6 +14,8 @@ import java.net.URL import java.nio.file.Path import java.util.* +val Int.MB: Long get() = this * 1024L * 1024L + interface NodeConfiguration : NodeSSLConfiguration { // myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this. // TODO: Remove this so we don't accidentally use this identity in the code? @@ -43,6 +45,17 @@ interface NodeConfiguration : NodeSSLConfiguration { val sshd: SSHDConfiguration? val database: DatabaseConfig val useAMQPBridges: Boolean get() = true + val transactionCacheSizeBytes: Long get() = defaultTransactionCacheSize + + companion object { + // default to at least 8MB and a bit extra for larger heap sizes + val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory() + + // add 5% of any heapsize over 300MB to the default transaction cache size + private fun getAdditionalCacheMemory(): Long { + return Math.max((Runtime.getRuntime().maxMemory() - 300.MB) / 20, 0) + } + } } data class DevModeOptions(val disableCheckpointChecker: Boolean = false) @@ -118,7 +131,8 @@ data class NodeConfigurationImpl( override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(), override val sshd: SSHDConfiguration? = null, override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode), - override val useAMQPBridges: Boolean = true + override val useAMQPBridges: Boolean = true, + override val transactionCacheSizeBytes: Long = NodeConfiguration.defaultTransactionCacheSize ) : NodeConfiguration { override val exportJMXto: String get() = "http" diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index f8c887d073..893579af03 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -1,10 +1,12 @@ package net.corda.node.services.persistence - import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.TransactionSignature +import net.corda.core.internal.ThreadBox import net.corda.core.messaging.DataFeed import net.corda.core.serialization.* +import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.SignedTransaction import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.utilities.* @@ -13,9 +15,15 @@ import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction import rx.Observable import rx.subjects.PublishSubject +import java.util.* import javax.persistence.* -class DBTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() { +// cache value type to just store the immutable bits of a signed transaction plus conversion helpers +typealias TxCacheValue = Pair, List> +fun TxCacheValue.toSignedTx() = SignedTransaction(this.first, this.second) +fun SignedTransaction.toTxCacheValue() = TxCacheValue(this.txBits, this.sigs) + +class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, SingletonSerializeAsToken() { @Entity @Table(name = "${NODE_DATABASE_PREFIX}transactions") @@ -30,40 +38,63 @@ class DBTransactionStorage : WritableTransactionStorage, SingletonSerializeAsTok ) private companion object { - fun createTransactionsMap(): AppendOnlyPersistentMap { - return AppendOnlyPersistentMap( + fun createTransactionsMap(maxSizeInBytes: Long) + : AppendOnlyPersistentMapBase { + return WeightBasedAppendOnlyPersistentMap( toPersistentEntityKey = { it.toString() }, fromPersistentEntity = { Pair(SecureHash.parse(it.txId), - it.transaction.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) + it.transaction.deserialize(context = SerializationDefaults.STORAGE_CONTEXT) + .toTxCacheValue()) }, - toPersistentEntity = { key: SecureHash, value: SignedTransaction -> + toPersistentEntity = { key: SecureHash, value: TxCacheValue -> DBTransaction().apply { txId = key.toString() - transaction = value.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes + transaction = value.toSignedTx(). + serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes } }, - persistentEntityClass = DBTransaction::class.java + persistentEntityClass = DBTransaction::class.java, + maxWeight = maxSizeInBytes, + weighingFunc = { hash, tx -> hash.size + weighTx(tx) } ) } + + // Rough estimate for the average of a public key and the transaction metadata - hard to get exact figures here, + // as public keys can vary in size a lot, and if someone else is holding a reference to the key, it won't add + // to the memory pressure at all here. + private const val transactionSignatureOverheadEstimate = 1024 + + private fun weighTx(tx: Optional): Int { + if (!tx.isPresent) { + return 0 + } + val actTx = tx.get() + return actTx.second.sumBy { it.size + transactionSignatureOverheadEstimate } + actTx.first.size + } } - private val txStorage = createTransactionsMap() + private val txStorage = ThreadBox(createTransactionsMap(cacheSizeBytes)) override fun addTransaction(transaction: SignedTransaction): Boolean = - txStorage.addWithDuplicatesAllowed(transaction.id, transaction).apply { - updatesPublisher.bufferUntilDatabaseCommit().onNext(transaction) + txStorage.locked { + addWithDuplicatesAllowed(transaction.id, transaction.toTxCacheValue()).apply { + updatesPublisher.bufferUntilDatabaseCommit().onNext(transaction) + } } - override fun getTransaction(id: SecureHash): SignedTransaction? = txStorage[id] + override fun getTransaction(id: SecureHash): SignedTransaction? = txStorage.content[id]?.toSignedTx() private val updatesPublisher = PublishSubject.create().toSerialized() override val updates: Observable = updatesPublisher.wrapWithDatabaseTransaction() - override fun track(): DataFeed, SignedTransaction> = - DataFeed(txStorage.allPersisted().map { it.second }.toList(), updatesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + override fun track(): DataFeed, SignedTransaction> { + return txStorage.locked { + DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updatesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + } + } @VisibleForTesting val transactions: Iterable - get() = txStorage.allPersisted().map { it.second }.toList() + get() = txStorage.content.allPersisted().map { it.second.toSignedTx() }.toList() } diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index a21da310ee..4912871b46 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -1,5 +1,7 @@ package net.corda.node.utilities +import com.google.common.cache.LoadingCache +import com.google.common.cache.Weigher import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.currentDBSession import java.util.* @@ -10,23 +12,18 @@ import java.util.* * behaviour is unpredictable! There is a best-effort check for double inserts, but this should *not* be relied on, so * ONLY USE THIS IF YOUR TABLE IS APPEND-ONLY */ -class AppendOnlyPersistentMap( +abstract class AppendOnlyPersistentMapBase( val toPersistentEntityKey: (K) -> EK, val fromPersistentEntity: (E) -> Pair, val toPersistentEntity: (key: K, value: V) -> E, - val persistentEntityClass: Class, - cacheBound: Long = 1024 -) { //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size + val persistentEntityClass: Class +) { private companion object { private val log = contextLogger() } - private val cache = NonInvalidatingCache>( - bound = cacheBound, - concurrencyLevel = 8, - loadFunction = { key -> Optional.ofNullable(loadValue(key)) } - ) + abstract protected val cache: LoadingCache> /** * Returns the value associated with the key, first loading that value from the storage if necessary. @@ -116,7 +113,7 @@ class AppendOnlyPersistentMap( } } - private fun loadValue(key: K): V? { + protected fun loadValue(key: K): V? { val result = currentDBSession().find(persistentEntityClass, toPersistentEntityKey(key)) return result?.let(fromPersistentEntity)?.second } @@ -135,3 +132,45 @@ class AppendOnlyPersistentMap( cache.invalidateAll() } } + +class AppendOnlyPersistentMap( + toPersistentEntityKey: (K) -> EK, + fromPersistentEntity: (E) -> Pair, + toPersistentEntity: (key: K, value: V) -> E, + persistentEntityClass: Class, + cacheBound: Long = 1024 +) : AppendOnlyPersistentMapBase( + toPersistentEntityKey, + fromPersistentEntity, + toPersistentEntity, + persistentEntityClass) { + //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size + override val cache = NonInvalidatingCache>( + bound = cacheBound, + concurrencyLevel = 8, + loadFunction = { key -> Optional.ofNullable(loadValue(key)) }) +} + +class WeightBasedAppendOnlyPersistentMap( + toPersistentEntityKey: (K) -> EK, + fromPersistentEntity: (E) -> Pair, + toPersistentEntity: (key: K, value: V) -> E, + persistentEntityClass: Class, + maxWeight: Long, + weighingFunc: (K, Optional) -> Int +) : AppendOnlyPersistentMapBase( + toPersistentEntityKey, + fromPersistentEntity, + toPersistentEntity, + persistentEntityClass) { + override val cache = NonInvalidatingWeightBasedCache>( + maxWeight = maxWeight, + concurrencyLevel = 8, + weigher = object : Weigher> { + override fun weigh(key: K, value: Optional): Int { + return weighingFunc(key, value) + } + }, + loadFunction = { key -> Optional.ofNullable(loadValue(key)) } + ) +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt index 07dd16b936..0a199a0e31 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt @@ -3,6 +3,7 @@ package net.corda.node.utilities import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache +import com.google.common.cache.Weigher import com.google.common.util.concurrent.ListenableFuture @@ -21,11 +22,26 @@ class NonInvalidatingCache private constructor( } // TODO look into overriding loadAll() if we ever use it - private class NonInvalidatingCacheLoader(val loadFunction: (K) -> V) : CacheLoader() { + class NonInvalidatingCacheLoader(val loadFunction: (K) -> V) : CacheLoader() { override fun reload(key: K, oldValue: V): ListenableFuture { throw IllegalStateException("Non invalidating cache refreshed") } override fun load(key: K) = loadFunction(key) } +} + +class NonInvalidatingWeightBasedCache private constructor( + val cache: LoadingCache +) : LoadingCache by cache { + constructor (maxWeight: Long, concurrencyLevel: Int, weigher: Weigher, loadFunction: (K) -> V) : + this(buildCache(maxWeight, concurrencyLevel, weigher, loadFunction)) + + + private companion object { + private fun buildCache(maxWeight: Long, concurrencyLevel: Int, weigher: Weigher, loadFunction: (K) -> V): LoadingCache { + val builder = CacheBuilder.newBuilder().maximumWeight(maxWeight).weigher(weigher).concurrencyLevel(concurrencyLevel) + return builder.build(NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction)) + } + } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 0af608e531..f61f34f0ef 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -314,8 +314,8 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = { args -> object : MockNetwork.MockNode(args) { // That constructs a recording tx storage - override fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage { - return RecordingTransactionStorage(database, super.makeTransactionStorage(database)) + override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes)) } } }) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 3f559113da..255ce7aae9 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -10,6 +10,7 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.internal.configureDatabase +import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.* @@ -173,7 +174,7 @@ class DBTransactionStorageTests { private fun newTransactionStorage() { database.transaction { - transactionStorage = DBTransactionStorage() + transactionStorage = DBTransactionStorage(NodeConfiguration.defaultTransactionCacheSize) } } From 5e7d2f00ae85f01b2488c562958bc48ac0736c8a Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 15 Jan 2018 15:19:32 +0000 Subject: [PATCH 09/16] ENT-1389 Modify the HibernateObserver to persist states by schema (and only create a session per schema, not one per state per schema) (#2366) --- .../node/services/schema/HibernateObserver.kt | 41 ++++++++----- .../persistence/HibernateConfigurationTest.kt | 58 +++++++++++-------- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 17fab1f4ac..eb58d0871e 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -10,11 +10,18 @@ import net.corda.core.schemas.PersistentStateRef import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.services.api.SchemaService -import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.DatabaseTransactionManager +import net.corda.nodeapi.internal.persistence.HibernateConfiguration import org.hibernate.FlushMode import rx.Observable + +/** + * Small data class bundling together a ContractState and a StateRef (as opposed to a TransactionState and StateRef + * in StateAndRef) + */ +data class ContractStateAndRef(val state: ContractState, val ref: StateRef) + /** * A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate. */ @@ -30,27 +37,33 @@ class HibernateObserver private constructor(private val config: HibernateConfigu } private fun persist(produced: Set>) { - produced.forEach { persistState(it) } - } - - private fun persistState(stateAndRef: StateAndRef) { - val state = stateAndRef.state.data - log.debug { "Asked to persist state ${stateAndRef.ref}" } - schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) } + val stateBySchema: MutableMap> = mutableMapOf() + // map all states by their referenced schemas + produced.forEach { + val contractStateAndRef = ContractStateAndRef(it.state.data, it.ref) + log.debug { "Asked to persist state ${it.ref}" } + schemaService.selectSchemas(contractStateAndRef.state).forEach { + stateBySchema.getOrPut(it) { mutableListOf() }.add(contractStateAndRef) + } + } + // then persist all states for each schema + stateBySchema.forEach { persistStatesWithSchema(it.value, it.key) } } @VisibleForTesting - internal fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { + internal fun persistStatesWithSchema(statesAndRefs: List, schema: MappedSchema) { val sessionFactory = config.sessionFactoryForSchemas(setOf(schema)) val session = sessionFactory.withOptions(). connection(DatabaseTransactionManager.current().connection). flushMode(FlushMode.MANUAL). openSession() - session.use { - val mappedObject = schemaService.generateMappedObject(state, schema) - mappedObject.stateRef = PersistentStateRef(stateRef) - it.persist(mappedObject) - it.flush() + session.use { thisSession -> + statesAndRefs.forEach { + val mappedObject = schemaService.generateMappedObject(it.state, schema) + mappedObject.stateRef = PersistentStateRef(it.ref) + thisSession.persist(mappedObject) + } + thisSession.flush() } } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 6b56a5d291..4b6c3ab0bd 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -28,10 +28,11 @@ import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 import net.corda.finance.utils.sumCash +import net.corda.node.internal.configureDatabase +import net.corda.node.services.schema.ContractStateAndRef import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.vault.VaultSchemaV1 -import net.corda.node.internal.configureDatabase import net.corda.node.services.api.IdentityServiceInternal import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -503,11 +504,12 @@ class HibernateConfigurationTest { fun `count CashStates in V2`() { database.transaction { // persist cash states explicitly with V2 schema - cashStates.forEach { + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV2) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV2) } // structure query @@ -525,11 +527,12 @@ class HibernateConfigurationTest { database.transaction { vaultFiller.fillWithSomeTestLinearStates(5) // persist cash states explicitly with V2 schema - cashStates.forEach { + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV2) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV2) } // structure query @@ -620,11 +623,12 @@ class HibernateConfigurationTest { fun `select fungible states by owner party`() { database.transaction { // persist original cash states explicitly with V3 schema - cashStates.forEach { + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) } // structure query @@ -643,19 +647,20 @@ class HibernateConfigurationTest { fun `query fungible states by owner party`() { database.transaction { // persist original cash states explicitly with V3 schema - cashStates.forEach { + val stateAndRefs: MutableList = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }.toMutableList() vaultFiller.fillWithSomeTestCash(100.DOLLARS, issuerServices, 2, issuer.ref(1), ALICE, Random(0L)) val cashStates = vaultFiller.fillWithSomeTestCash(100.DOLLARS, services, 2, identity.ref(0)).states // persist additional cash states explicitly with V3 schema - cashStates.forEach { + stateAndRefs.addAll(cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }) + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) } val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, CommonSchemaV1, SampleCashSchemaV3) val criteriaBuilder = sessionFactory.criteriaBuilder @@ -694,12 +699,13 @@ class HibernateConfigurationTest { @Test fun `select fungible states by participants`() { database.transaction { - // persist cash states explicitly with V2 schema - cashStates.forEach { + // persist cash states explicitly with V3 schema + val stateAndRefs = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) + ContractStateAndRef(dummyFungibleState, it.ref) } + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) } // structure query @@ -720,25 +726,27 @@ class HibernateConfigurationTest { val firstCashState = database.transaction { // persist original cash states explicitly with V3 schema - cashStates.forEach { + val stateAndRefs: MutableList = cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }.toMutableList() + val moreCash = vaultFiller.fillWithSomeTestCash(100.DOLLARS, services, 2, identity.ref(0), identity, Random(0L)).states // persist additional cash states explicitly with V3 schema - moreCash.forEach { + stateAndRefs.addAll(moreCash.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }) val cashStates = vaultFiller.fillWithSomeTestCash(100.DOLLARS, issuerServices, 2, issuer.ref(1), ALICE, Random(0L)).states // persist additional cash states explicitly with V3 schema - cashStates.forEach { + stateAndRefs.addAll(cashStates.map { val cashState = it.state.data val dummyFungibleState = DummyFungibleContract.State(cashState.amount, cashState.owner) - hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) - } + ContractStateAndRef(dummyFungibleState, it.ref) + }) + hibernatePersister.persistStatesWithSchema(stateAndRefs, SampleCashSchemaV3) cashStates.first() } From 094e96d3032d7ae6167adf60416634b5b8fabab2 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 9 Jan 2018 17:46:57 +0100 Subject: [PATCH 10/16] Move the CommercialPaperTest examples to a location where it'll be run by gradle/ci. Make sure the tests pass, even the example tests that are intended to fail, so these files are kept up to date as the code changes. --- .../tutorial/testdsl/CommercialPaperTest.java | 7 +++- .../docs/tutorial/testdsl/TutorialTestDSL.kt | 7 +++- docs/source/tutorial-test-dsl.rst | 40 +++++++++---------- 3 files changed, 30 insertions(+), 24 deletions(-) rename docs/source/example-code/src/{main => test}/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java (97%) rename docs/source/example-code/src/{main => test}/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt (96%) diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java similarity index 97% rename from docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java rename to docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java index 0339b73754..087d632e83 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java +++ b/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java @@ -2,6 +2,7 @@ package net.corda.docs.java.tutorial.testdsl; import kotlin.Unit; import net.corda.core.contracts.PartyAndReference; +import net.corda.core.contracts.TransactionVerificationException; import net.corda.core.identity.CordaX500Name; import net.corda.finance.contracts.ICommercialPaperState; import net.corda.finance.contracts.JavaCommercialPaper; @@ -43,7 +44,8 @@ public class CommercialPaperTest { // DOCEND 1 // DOCSTART 2 - @Test + // This example test will fail with this exception. + @Test(expected = IllegalStateException.class) public void simpleCP() { ICommercialPaperState inState = getPaper(); ledger(ledgerServices, l -> { @@ -58,7 +60,8 @@ public class CommercialPaperTest { // DOCEND 2 // DOCSTART 3 - @Test + // This example test will fail with this exception. + @Test(expected = TransactionVerificationException.ContractRejection.class) public void simpleCPMove() { ICommercialPaperState inState = getPaper(); ledger(ledgerServices, l -> { diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt similarity index 96% rename from docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt rename to docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt index 1fc5f4c832..340cd1d9d9 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -2,6 +2,7 @@ package net.corda.docs.tutorial.testdsl import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.contracts.TransactionVerificationException import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.days @@ -53,7 +54,8 @@ class CommercialPaperTest { // DOCEND 1 // DOCSTART 2 - @Test + // This example test will fail with this exception. + @Test(expected = IllegalStateException::class) fun simpleCP() { val inState = getPaper() ledgerServices.ledger(DUMMY_NOTARY) { @@ -67,7 +69,8 @@ class CommercialPaperTest { // DOCEND 2 // DOCSTART 3 - @Test + // This example test will fail with this exception. + @Test(expected = TransactionVerificationException.ContractRejection::class) fun simpleCPMove() { val inState = getPaper() ledgerServices.ledger(DUMMY_NOTARY) { diff --git a/docs/source/tutorial-test-dsl.rst b/docs/source/tutorial-test-dsl.rst index 6771f077cb..91d788cfd6 100644 --- a/docs/source/tutorial-test-dsl.rst +++ b/docs/source/tutorial-test-dsl.rst @@ -55,13 +55,13 @@ We will start with defining helper function that returns a ``CommercialPaper`` s .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 1 :end-before: DOCEND 1 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 1 :end-before: DOCEND 1 @@ -122,13 +122,13 @@ last line of ``transaction``: .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 2 :end-before: DOCEND 2 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 2 :end-before: DOCEND 2 @@ -138,13 +138,13 @@ Let's take a look at a transaction that fails. .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 3 :end-before: DOCEND 3 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 3 :end-before: DOCEND 3 @@ -167,13 +167,13 @@ However we can specify that this is an intended behaviour by changing ``verifies .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 4 :end-before: DOCEND 4 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 4 :end-before: DOCEND 4 @@ -183,13 +183,13 @@ We can continue to build the transaction until it ``verifies``: .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 5 :end-before: DOCEND 5 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 5 :end-before: DOCEND 5 @@ -206,13 +206,13 @@ What should we do if we wanted to test what happens when the wrong party signs t .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 6 :end-before: DOCEND 6 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 6 :end-before: DOCEND 6 @@ -227,13 +227,13 @@ ledger with a single transaction: .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 7 :end-before: DOCEND 7 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 7 :end-before: DOCEND 7 @@ -246,13 +246,13 @@ Now that we know how to define a single transaction, let's look at how to define .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 8 :end-before: DOCEND 8 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 8 :end-before: DOCEND 8 @@ -273,13 +273,13 @@ To do so let's create a simple example that uses the same input twice: .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 9 :end-before: DOCEND 9 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 9 :end-before: DOCEND 9 @@ -290,13 +290,13 @@ verification (``fails()`` at the end). As in previous examples we can use ``twea .. container:: codeset - .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt + .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt :language: kotlin :start-after: DOCSTART 10 :end-before: DOCEND 10 :dedent: 4 - .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java + .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java :language: java :start-after: DOCSTART 10 :end-before: DOCEND 10 From 75e74e67a1b589cbfa443fb8f05e625233bef78b Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 15 Jan 2018 19:11:00 +0000 Subject: [PATCH 11/16] CORDA-599 RPCSecurityManager is no longer lateinit (#2347) --- .../net/corda/core/internal/InternalUtils.kt | 8 +- .../corda/core/internal/InternalUtilsTest.kt | 13 + .../main/kotlin/net/corda/lazyhub/LazyHub.kt | 109 +++ .../kotlin/net/corda/lazyhub/LazyHubImpl.kt | 200 ++++++ .../kotlin/net/corda/lazyhub/LazyHubModel.kt | 130 ++++ .../net/corda/node/internal/AbstractNode.kt | 34 +- .../kotlin/net/corda/node/internal/Node.kt | 37 +- .../kotlin/net/corda/lazyhub/LazyHubTests.kt | 640 ++++++++++++++++++ .../kotlin/net/corda/testing/node/MockNode.kt | 20 +- 9 files changed, 1150 insertions(+), 41 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/lazyhub/LazyHub.kt create mode 100644 node/src/main/kotlin/net/corda/lazyhub/LazyHubImpl.kt create mode 100644 node/src/main/kotlin/net/corda/lazyhub/LazyHubModel.kt create mode 100644 node/src/test/kotlin/net/corda/lazyhub/LazyHubTests.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 70724cb884..b5b8bc6f72 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -242,10 +242,14 @@ private fun IntProgression.toSpliterator(): Spliterator.OfInt { } fun IntProgression.stream(parallel: Boolean = false): IntStream = StreamSupport.intStream(toSpliterator(), parallel) - +inline fun Stream.toTypedArray() = toTypedArray(T::class.java) // When toArray has filled in the array, the component type is no longer T? but T (that may itself be nullable): -inline fun Stream.toTypedArray(): Array = uncheckedCast(toArray { size -> arrayOfNulls(size) }) +fun Stream.toTypedArray(componentType: Class): Array = toArray { size -> + uncheckedCast>(java.lang.reflect.Array.newInstance(componentType, size)) +} +fun Stream.filterNotNull(): Stream = uncheckedCast(filter(Objects::nonNull)) +fun Stream>.toMap(): Map = collect>(::LinkedHashMap, { m, (k, v) -> m.put(k, v) }, { m, t -> m.putAll(t) }) fun Class.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */ diff --git a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt index 0a2fb69f26..12818f9a81 100644 --- a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt @@ -3,6 +3,7 @@ package net.corda.core.internal import org.assertj.core.api.Assertions import org.junit.Assert.assertArrayEquals import org.junit.Test +import java.io.Serializable import java.util.stream.IntStream import java.util.stream.Stream import kotlin.test.assertEquals @@ -87,5 +88,17 @@ class InternalUtilsTest { val b: Array = Stream.of("one", "two", null).toTypedArray() assertEquals(Array::class.java, b.javaClass) assertArrayEquals(arrayOf("one", "two", null), b) + val c: Array = Stream.of("x", "y").toTypedArray(CharSequence::class.java) + assertEquals(Array::class.java, c.javaClass) + assertArrayEquals(arrayOf("x", "y"), c) + val d: Array = Stream.of("x", "y", null).toTypedArray(uncheckedCast(CharSequence::class.java)) + assertEquals(Array::class.java, d.javaClass) + assertArrayEquals(arrayOf("x", "y", null), d) + } + + @Test + fun `Stream of Pairs toMap works`() { + val m: Map, Serializable> = Stream.of, Serializable>>("x" to "y", 0 to 1, "x" to '2').toMap() + assertEquals>(mapOf("x" to '2', 0 to 1), m) } } diff --git a/node/src/main/kotlin/net/corda/lazyhub/LazyHub.kt b/node/src/main/kotlin/net/corda/lazyhub/LazyHub.kt new file mode 100644 index 0000000000..512fa1c74e --- /dev/null +++ b/node/src/main/kotlin/net/corda/lazyhub/LazyHub.kt @@ -0,0 +1,109 @@ +package net.corda.lazyhub + +import net.corda.core.serialization.CordaSerializable +import kotlin.reflect.KClass +import kotlin.reflect.KFunction + +/** Supertype of all exceptions thrown directly by [LazyHub]. */ +@CordaSerializable +abstract class LazyHubException(message: String) : RuntimeException(message) + +/** The type can't be instantiated because it is abstract, i.e. it's an interface or abstract class. */ +class AbstractTypeException(message: String) : LazyHubException(message) + +/** + * The class can't be instantiated because it has no public constructor. + * This is so that you can easily hide a constructor from LazyHub by making it non-public. + */ +class NoPublicConstructorsException(message: String) : LazyHubException(message) + +/** + * Nullable factory return types are not supported, as LazyHub has no concept of a provider that MAY supply an object. + * If you want an optional result, use logic to decide whether to add the factory to the lazyHub. + */ +class NullableReturnTypeException(message: String) : LazyHubException(message) + +/** The parameter can't be satisfied and doesn't have a default and isn't nullable. */ +abstract class UnsatisfiableParamException(message: String) : LazyHubException(message) + +/** No provider has been registered for the wanted type. */ +class NoSuchProviderException(message: String) : UnsatisfiableParamException(message) + +/** + * No provider has been registered for the component type of the wanted array. + * Note that LazyHub does not create empty arrays, make the array param type nullable to accept no elements. + * This allows you to express zero-or-more (nullable) or one-or-more via the parameter type. + */ +class UnsatisfiableArrayException(message: String) : UnsatisfiableParamException(message) + +/** More than one provider has been registered for the type but at most one object is wanted. */ +class TooManyProvidersException(message: String) : UnsatisfiableParamException(message) + +/** + * More than one public constructor is satisfiable and there is no clear winner. + * The winner is the constructor with the most params for which LazyHub actually supplies an arg. + */ +class NoUniqueGreediestSatisfiableConstructorException(message: String) : LazyHubException(message) + +/** The object being created depends on itself, i.e. it's already being instantiated/factoried. */ +class CircularDependencyException(message: String) : LazyHubException(message) + +/** Depend on this as a param (and add the [MutableLazyHub], which is a [LazyHubFactory], to itself) if you want to make child containers. */ +interface LazyHubFactory { + fun child(): MutableLazyHub +} + +/** + * Read-only interface to the lazyHub. + * Where possible, always obtain your object via a constructor/method param instead of directly from the [LazyHub]. + * This results in the greatest automatic benefits to the codebase e.g. separation of concerns and ease of testing. + * A notable exception to this rule is `getAll(Unit::class)` to (idempotently) run all side-effects. + */ +interface LazyHub : LazyHubFactory { + operator fun get(clazz: KClass) = get(clazz.java) + operator fun get(clazz: Class) = getOrNull(clazz) ?: throw NoSuchProviderException(clazz.toString()) + fun getAll(clazz: KClass) = getAll(clazz.java) + fun getAll(clazz: Class): List + fun getOrNull(clazz: KClass) = getOrNull(clazz.java) + fun getOrNull(clazz: Class): T? +} + +/** Fully-featured interface to the lazyHub. */ +interface MutableLazyHub : LazyHub { + /** Register the given object against its class and all supertypes. */ + fun obj(obj: Any) + + /** Like plain old [MutableLazyHub.obj] but removes all [service] providers first. */ + fun obj(service: KClass, obj: T) + + /** + * Register the given class as a provider for itself and all supertypes. + * The class is instantiated at most once, using the greediest public constructor satisfiable at the time. + */ + fun impl(impl: KClass<*>) + + /** + * Same as [MutableLazyHub.impl] if you don't have a static reference to the class. + * Note that Kotlin features such as nullable params and default args will not be available. + */ + fun impl(impl: Class<*>) + + /** Like plain old [MutableLazyHub.impl] but removes all [service] providers first. */ + fun impl(service: KClass, impl: KClass) + + /** Like the [KClass] variant if you don't have a static reference fo the class. */ + fun impl(service: KClass, impl: Class) + + /** + * Register the given function as a provider for its **declared** return type and all supertypes. + * The function is invoked at most once. Unlike constructors, the function may have any visibility. + * By convention the function should have side-effects iff its return type is [Unit]. + */ + fun factory(factory: KFunction<*>) + + /** Register a factory that provides the given type from the given hub. */ + fun factory(lh: LazyHub, type: KClass<*>) + + /** Like plain old [MutableLazyHub.factory] but removes all [service] providers first. */ + fun factory(service: KClass, factory: KFunction) +} diff --git a/node/src/main/kotlin/net/corda/lazyhub/LazyHubImpl.kt b/node/src/main/kotlin/net/corda/lazyhub/LazyHubImpl.kt new file mode 100644 index 0000000000..5edbb4027b --- /dev/null +++ b/node/src/main/kotlin/net/corda/lazyhub/LazyHubImpl.kt @@ -0,0 +1,200 @@ +package net.corda.lazyhub + +import net.corda.core.internal.filterNotNull +import net.corda.core.internal.toTypedArray +import net.corda.core.internal.uncheckedCast +import net.corda.lazyhub.JConcrete.Companion.validate +import net.corda.lazyhub.KConcrete.Companion.validate +import net.corda.lazyhub.KConstructor.Companion.validate +import java.util.* +import java.util.concurrent.Callable +import java.util.stream.Stream +import kotlin.reflect.KClass +import kotlin.reflect.KFunction + +/** + * Create a new [MutableLazyHub] with no parent. + * + * Basic usage: + * * Add classes/factories/objects to the LazyHub using [MutableLazyHub.impl], [MutableLazyHub.factory] and [MutableLazyHub.obj] + * * Then ask it for a type using [LazyHub.get] and it will create (and cache) the object graph for you + * * You can use [LazyHub.getAll] to get all objects of a type, e.g. by convention pass in [Unit] to run side-effects + * + * How it works: + * * [LazyHub.get] finds the unique registered class/factory/object for the given type (or fails) + * * If it's an object, that object is returned + * * If it's a factory, it is executed with args obtained recursively from the same LazyHub + * * If it's a class, it is instantiated using a public constructor in the same way as a factory + * * Of the public constructors that can be satisfied, the one that consumes the most args is chosen + * + * Advanced usage: + * * Use an array parameter to get one-or-more args of the component type, make it nullable for zero-or-more + * * If a LazyHub can't satisfy a type (or array param) and has a parent, it asks the parent + * * Typically the root LazyHub in the hierarchy will manage all singletons of the process + */ +fun lazyHub(): MutableLazyHub = LazyHubImpl(null) + +private class SimpleProvider(override val obj: T) : Provider { + override val type get() = obj.javaClass +} + +private class LazyProvider(private val busyProviders: BusyProviders, private val underlying: Any?, override val type: Class, val chooseInvocation: () -> Callable) : Provider { + override val obj by lazy { busyProviders.runFactory(this) } + override fun toString() = underlying.toString() +} + +private class Invocation(val constructor: PublicConstructor, val argSuppliers: List>) : Callable { + fun providerCount() = argSuppliers.stream().filter { (_, supplier) -> supplier.provider != null }.count() // Allow repeated providers. + override fun call() = constructor(argSuppliers) + override fun toString() = constructor.toString() +} + +private class BusyProviders { + private val busyProviders = mutableMapOf, Callable<*>>() + fun runFactory(provider: LazyProvider): T { + if (busyProviders.contains(provider)) throw CircularDependencyException("Provider '$provider' is already busy: ${busyProviders.values}") + val invocation = provider.chooseInvocation() + busyProviders.put(provider, invocation) + try { + return invocation.call() + } finally { + busyProviders.remove(provider) + } + } +} + +private val autotypes: Map, Class<*>> = mutableMapOf, Class<*>>().apply { + Arrays::class.java.declaredMethods.filter { it.name == "hashCode" }.map { it.parameterTypes[0].componentType }.filter { it.isPrimitive }.forEach { + val boxed = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(it, 1), 0).javaClass + put(it, boxed) + put(boxed, it) + } +} + +private infix fun Class<*>.isSatisfiedBy(clazz: Class<*>): Boolean { + return isAssignableFrom(clazz) || autotypes[this] == clazz +} + +private class LazyHubImpl(private val parent: LazyHubImpl?, private val busyProviders: BusyProviders = parent?.busyProviders ?: BusyProviders()) : MutableLazyHub { + private val providers = mutableMapOf, MutableList>>() + private fun add(provider: Provider<*>, type: Class<*> = provider.type, registered: MutableSet> = mutableSetOf()) { + if (!registered.add(type)) return + providers[type]?.add(provider) ?: providers.put(type, mutableListOf(provider)) + Stream.concat(Arrays.stream(type.interfaces), Stream.of(type.superclass, autotypes[type]).filterNotNull()).forEach { + add(provider, it, registered) + } + } + + /** The non-empty list of providers, or null. */ + private fun findProviders(clazz: Class): List>? = uncheckedCast(providers[clazz]) ?: parent?.findProviders(clazz) + + private fun dropAll(serviceClass: Class<*>) { + val removed = mutableSetOf>() + providers.iterator().run { + while (hasNext()) { + val entry = next() + if (serviceClass isSatisfiedBy entry.key) { + removed.addAll(entry.value) + remove() + } + } + } + providers.values.iterator().run { + while (hasNext()) { + val providers = next() + providers.removeAll(removed) + if (providers.isEmpty()) remove() + } + } + } + + override fun getOrNull(clazz: Class) = findProviders(clazz)?.run { (singleOrNull() ?: throw TooManyProvidersException(clazz.toString())).obj } + override fun getAll(clazz: Class) = findProviders(clazz)?.map { it.obj } ?: emptyList() + override fun child(): MutableLazyHub = LazyHubImpl(this) + override fun obj(obj: Any) = add(SimpleProvider(obj)) + override fun obj(service: KClass, obj: T) { + dropAll(service.java) + obj(obj) + } + + override fun factory(service: KClass, factory: KFunction) = factory.validate().let { + dropAll(service.java) + addFactory(it) + } + + override fun impl(service: KClass, impl: KClass) = impl.validate().let { + dropAll(service.java) + addConcrete(it) + } + + override fun impl(service: KClass, impl: Class) = impl.validate().let { + dropAll(service.java) + addConcrete(it) + } + + override fun factory(factory: KFunction<*>) = addFactory(factory.validate()) + private fun addFactory(factory: KConstructor) { + val type = factory.kFunction.returnType.toJavaType().let { if (it == Void.TYPE) Unit::class.java else it as Class<*> } + add(LazyProvider(busyProviders, factory, uncheckedCast(type)) { factory.toInvocation() }) + } + + override fun factory(lh: LazyHub, type: KClass<*>) = addFactory(lh, type) + private fun addFactory(lh: LazyHub, type: KClass) { + add(LazyProvider(busyProviders, lh, type.java) { Callable { lh[type] } }) + } + + override fun impl(impl: KClass<*>) = implGeneric(impl) + private fun implGeneric(type: KClass) = addConcrete(type.validate()) + override fun impl(impl: Class<*>) = implGeneric(impl) + private fun implGeneric(type: Class) = addConcrete(type.validate()) + private fun

> addConcrete(concrete: Concrete) { + add(LazyProvider(busyProviders, concrete, concrete.clazz) { + var fail: UnsatisfiableParamException? = null + val satisfiable = concrete.publicConstructors.mapNotNull { constructor -> + try { + constructor.toInvocation() + } catch (e: UnsatisfiableParamException) { + fail?.addSuppressed(e) ?: run { fail = e } + null + } + } + if (satisfiable.isEmpty()) throw fail!! + val greediest = mutableListOf(satisfiable[0]) + var providerCount = greediest[0].providerCount() + satisfiable.stream().skip(1).forEach next@ { + val pc = it.providerCount() + if (pc < providerCount) return@next + if (pc > providerCount) { + greediest.clear() + providerCount = pc + } + greediest += it + } + greediest.singleOrNull() ?: throw NoUniqueGreediestSatisfiableConstructorException(greediest.toString()) + }) + } + + private fun arrayProvider(arrayType: Class<*>, componentType: Class): LazyProvider>? { + val providers = findProviders(componentType) ?: return null + return LazyProvider(busyProviders, null, uncheckedCast(arrayType)) { + Callable { providers.stream().map { it.obj }.toTypedArray(componentType) } + } + } + + private fun

PublicConstructor.toInvocation() = Invocation(this, params.mapNotNull { param -> + if (param.type.isArray) { + val provider = arrayProvider(param.type, param.type.componentType) + when (provider) { + null -> param.supplierWhenUnsatisfiable()?.let { param to it } + else -> param to ArgSupplier(provider) + } + } else { + val providers = findProviders(param.type) + when (providers?.size) { + null -> param.supplierWhenUnsatisfiable()?.let { param to it } + 1 -> param to ArgSupplier(providers[0]) + else -> throw TooManyProvidersException(param.toString()) + } + } + }) +} diff --git a/node/src/main/kotlin/net/corda/lazyhub/LazyHubModel.kt b/node/src/main/kotlin/net/corda/lazyhub/LazyHubModel.kt new file mode 100644 index 0000000000..7c3217f7d7 --- /dev/null +++ b/node/src/main/kotlin/net/corda/lazyhub/LazyHubModel.kt @@ -0,0 +1,130 @@ +package net.corda.lazyhub + +import net.corda.core.internal.toMap +import net.corda.core.internal.toTypedArray +import net.corda.core.internal.uncheckedCast +import java.lang.reflect.* +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.KVisibility +import kotlin.reflect.jvm.internal.ReflectProperties +import kotlin.reflect.jvm.isAccessible + +private val javaTypeDelegateField = Class.forName("kotlin.reflect.jvm.internal.KTypeImpl").getDeclaredField("javaType\$delegate").apply { isAccessible = true } +internal fun kotlin.reflect.KType.toJavaType() = (javaTypeDelegateField.get(this) as ReflectProperties.Val<*>)() +internal interface Provider { + /** Most specific known type i.e. directly registered implementation class, or declared return type of factory method. */ + val type: Class + /** May be lazily computed. */ + val obj: T +} + +/** Like [Provider] but capable of supplying null. */ +internal class ArgSupplier(val provider: Provider<*>?) { + companion object { + val nullSupplier = ArgSupplier(null) + } + + operator fun invoke() = provider?.obj +} + +/** Common interface to Kotlin/Java params. */ +internal interface Param { + val type: Class<*> + /** The supplier, or null to supply nothing so the Kotlin default is used. */ + fun supplierWhenUnsatisfiable(): ArgSupplier? = throw (if (type.isArray) ::UnsatisfiableArrayException else ::NoSuchProviderException)(toString()) +} + +internal class KParam(val kParam: KParameter) : Param { + override val type = run { + var jType = kParam.type.toJavaType() + loop@ while (true) { + jType = when (jType) { + is ParameterizedType -> jType.rawType + is TypeVariable<*> -> jType.bounds.first() // Potentially surprising but most consistent behaviour, see unit tests. + else -> break@loop + } + } + jType as Class<*> + } + + override fun supplierWhenUnsatisfiable() = when { + kParam.isOptional -> null // Use default value, even if param is also nullable. + kParam.type.isMarkedNullable -> ArgSupplier.nullSupplier + else -> super.supplierWhenUnsatisfiable() + } + + override fun toString() = kParam.toString() +} + +internal class JParam(private val param: Parameter, private val index: Int, override val type: Class<*>) : Param { + override fun toString() = "parameter #$index ${param.name} of ${param.declaringExecutable}" +} + +internal interface PublicConstructor { + val params: List

+ operator fun invoke(argSuppliers: List>): T +} + +internal class KConstructor(val kFunction: KFunction) : PublicConstructor { + companion object { + fun KFunction.validate() = run { + if (returnType.isMarkedNullable) throw NullableReturnTypeException(toString()) + isAccessible = true + KConstructor(this) + } + } + + override val params = kFunction.parameters.map(::KParam) + override fun invoke(argSuppliers: List>): T { + return kFunction.callBy(argSuppliers.stream().map { (param, supplier) -> param.kParam to supplier() }.toMap()) + } + + override fun toString() = kFunction.toString() +} + +internal class JConstructor(private val constructor: Constructor) : PublicConstructor { + // Much cheaper to get the types up-front than via the Parameter API: + override val params = constructor.parameters.zip(constructor.parameterTypes).mapIndexed { i, (p, t) -> JParam(p, i, t) } + + override fun invoke(argSuppliers: List>): T { + return constructor.newInstance(*argSuppliers.stream().map { (_, supplier) -> supplier() }.toTypedArray()) + } + + override fun toString() = constructor.toString() +} + +internal interface Concrete> { + val clazz: Class + val publicConstructors: List +} + +internal class KConcrete private constructor(private val kClass: KClass) : Concrete> { + companion object { + fun KClass.validate() = run { + if (isAbstract) throw AbstractTypeException(toString()) + KConcrete(this).apply { + if (publicConstructors.isEmpty()) throw NoPublicConstructorsException(toString()) + } + } + } + + override val clazz get() = kClass.java + override val publicConstructors = kClass.constructors.filter { it.visibility == KVisibility.PUBLIC }.map(::KConstructor) + override fun toString() = kClass.toString() +} + +internal class JConcrete private constructor(override val clazz: Class) : Concrete> { + companion object { + fun Class.validate() = run { + if (Modifier.isAbstract(modifiers)) throw AbstractTypeException(toString()) + JConcrete(this).apply { + if (publicConstructors.isEmpty()) throw NoPublicConstructorsException(toString()) + } + } + } + + override val publicConstructors = uncheckedCast>, Array>>(clazz.constructors).map(::JConstructor) + override fun toString() = clazz.toString() +} diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 410bab983d..d17ca7cae0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -28,12 +28,14 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.getOrThrow +import net.corda.lazyhub.LazyHub +import net.corda.lazyhub.MutableLazyHub +import net.corda.lazyhub.lazyHub import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderInternal -import net.corda.node.internal.security.RPCSecurityManager import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler @@ -56,7 +58,6 @@ import net.corda.node.services.transactions.* import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager -import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo @@ -144,9 +145,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val runOnStop = ArrayList<() -> Any?>() private val _nodeReadyFuture = openFuture() protected var networkMapClient: NetworkMapClient? = null - - lateinit var securityManager: RPCSecurityManager get - /** Completes once the node has successfully registered with the network map service * or has loaded network map data from local database */ val nodeReadyFuture: CordaFuture get() = _nodeReadyFuture @@ -200,22 +198,31 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } + protected open fun configure(lh: MutableLazyHub) { + // TODO: Migrate classes and factories from start method. + } + open fun start(): StartedNode { check(started == null) { "Node has already been started" } log.info("Node starting up ...") initCertificate() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) + val lh = lazyHub() + configure(lh) val identityService = makeIdentityService(identity.certificate) + lh.obj(identityService) networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) } retrieveNetworkParameters(identityService.trustRoot) // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> + lh.obj(database) val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService) val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair) + lh.obj(info) identityService.loadIdentities(info.legalIdentitiesAndCerts) val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes) - val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache) + val nodeServices = makeServices(lh, keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache) val notaryService = makeNotaryService(nodeServices, database) val smm = makeStateMachineManager(database) val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader) @@ -238,13 +245,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService, flowLogicRefFactory) val rpcOps = makeRPCOps(flowStarter, database, smm) - startMessagingService(rpcOps) + lh.obj(rpcOps) + lh.getAll(Unit::class) // Run side-effects. installCoreFlows() val cordaServices = installCordaServices(flowStarter) tokenizableServices = nodeServices + cordaServices + schedulerService registerCordappFlows(smm) _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } - startShell(rpcOps) Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) } val networkMapUpdater = NetworkMapUpdater(services.networkMapCache, @@ -280,10 +287,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, */ protected abstract fun getRxIoScheduler(): Scheduler - open fun startShell(rpcOps: CordaRPCOps) { - InteractiveShell.startShell(configuration, rpcOps, securityManager, _services.identityService, _services.database) - } - private fun initNodeInfo(networkMapCache: NetworkMapCacheBaseInternal, identity: PartyAndCertificate, identityKeyPair: KeyPair): Pair, NodeInfo> { @@ -534,7 +537,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, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList { + private fun makeServices(lh: LazyHub, keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList { checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) @@ -550,7 +553,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, database, info, networkMapCache) - network = makeMessagingService(database, info) + network = lh[MessagingService::class] // TODO: Retire the lateinit var. val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.keyManagementService, services.identityService, platformClock, services.auditService, services.monitoringService, services.networkMapCache, services.schemaService, @@ -715,9 +718,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, _started = null } - protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService - protected abstract fun startMessagingService(rpcOps: RPCOps) - private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 16dae0ee2e..0ac13ee6d8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -5,6 +5,7 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.uncheckedCast +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub @@ -14,8 +15,10 @@ import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger +import net.corda.lazyhub.MutableLazyHub import net.corda.node.VersionInfo import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.security.RPCSecurityManager import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.services.api.SchemaService @@ -24,6 +27,7 @@ import net.corda.node.services.config.SecurityConfiguration import net.corda.node.services.config.VerifierType import net.corda.node.services.messaging.* import net.corda.node.services.transactions.InMemoryTransactionVerifierService +import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.DemoClock @@ -133,16 +137,27 @@ open class Node(configuration: NodeConfiguration, private var messageBroker: ArtemisMessagingServer? = null private var shutdownHook: ShutdownHook? = null - - override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService { + override fun configure(lh: MutableLazyHub) { + super.configure(lh) // Construct security manager reading users data either from the 'security' config section // if present or from rpcUsers list if the former is missing from config. - val securityManagerConfig = configuration.security?.authService ?: - SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers) + lh.obj(configuration.security?.authService ?: SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers)) + lh.impl(RPCSecurityManagerImpl::class) + configuration.messagingServerAddress?.also { + lh.obj(MessagingServerAddress(it)) + } ?: run { + lh.factory(this::makeLocalMessageBroker) + } + lh.factory(this::makeMessagingService) + // Side-effects: + lh.factory(this::startMessagingService) + lh.factory(this::startShell) + } - securityManager = RPCSecurityManagerImpl(securityManagerConfig) + class MessagingServerAddress(val address: NetworkHostAndPort) - val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker() + private fun makeMessagingService(database: CordaPersistence, info: NodeInfo, messagingServerAddress: MessagingServerAddress): MessagingService { + val serverAddress = messagingServerAddress.address val advertisedAddress = info.addresses.single() printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) @@ -162,10 +177,10 @@ open class Node(configuration: NodeConfiguration, networkParameters.maxMessageSize) } - private fun makeLocalMessageBroker(): NetworkHostAndPort { + private fun makeLocalMessageBroker(securityManager: RPCSecurityManager): MessagingServerAddress { with(configuration) { messageBroker = ArtemisMessagingServer(this, p2pAddress.port, rpcAddress?.port, services.networkMapCache, securityManager, networkParameters.maxMessageSize) - return NetworkHostAndPort("localhost", p2pAddress.port) + return MessagingServerAddress(NetworkHostAndPort("localhost", p2pAddress.port)) } } @@ -213,7 +228,7 @@ open class Node(configuration: NodeConfiguration, } } - override fun startMessagingService(rpcOps: RPCOps) { + private fun startMessagingService(rpcOps: RPCOps, securityManager: RPCSecurityManager) { // Start up the embedded MQ server messageBroker?.apply { runOnStop += this::stop @@ -234,6 +249,10 @@ open class Node(configuration: NodeConfiguration, } } + private fun startShell(rpcOps: CordaRPCOps, securityManager: RPCSecurityManager, identityService: IdentityService, database: CordaPersistence) { + InteractiveShell.startShell(configuration, rpcOps, securityManager, identityService, database) + } + /** * If the node is persisting to an embedded H2 database, then expose this via TCP with a DB URL of the form: * jdbc:h2:tcp://:/node diff --git a/node/src/test/kotlin/net/corda/lazyhub/LazyHubTests.kt b/node/src/test/kotlin/net/corda/lazyhub/LazyHubTests.kt new file mode 100644 index 0000000000..5d462585bc --- /dev/null +++ b/node/src/test/kotlin/net/corda/lazyhub/LazyHubTests.kt @@ -0,0 +1,640 @@ +package net.corda.lazyhub + +import net.corda.core.internal.uncheckedCast +import org.assertj.core.api.Assertions.catchThrowable +import org.hamcrest.CoreMatchers.* +import org.junit.Assert.* +import org.junit.Ignore +import org.junit.Test +import java.io.Closeable +import java.io.IOException +import java.io.Serializable +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.javaConstructor +import kotlin.reflect.jvm.javaMethod +import kotlin.test.assertEquals +import kotlin.test.fail + +open class LazyHubTests { + private val lh = lazyHub() + + class Config(val info: String) + interface A + interface B { + val a: A + } + + class AImpl(val config: Config) : A + class BImpl(override val a: A) : B + class Spectator { + init { + fail("Should not be instantiated.") + } + } + + @Test + fun `basic functionality`() { + val config = Config("woo") + lh.obj(config) + lh.impl(AImpl::class) + lh.impl(BImpl::class) + lh.impl(Spectator::class) + val b = lh[B::class] + // An impl is instantiated at most once per LazyHub: + assertSame(b.a, lh[A::class]) + assertSame(b, lh[B::class]) + // More specific type to expose config without casting: + val a = lh[AImpl::class] + assertSame(b.a, a) + assertSame(config, a.config) + } + + private fun createA(config: Config): A = AImpl(config) // Declared return type is significant. + internal open fun createB(): B = fail("Should not be called.") + @Test + fun `factory works`() { + lh.obj(Config("x")) + lh.factory(this::createA) // Observe private is OK. + assertSame(AImpl::class.java, lh[A::class].javaClass) + // The factory declares A not AImpl as its return type, and lh doesn't try to be clever: + catchThrowable { lh[AImpl::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(AImpl::class.toString(), message) + } + } + + @Ignore + class Subclass : LazyHubTests() { // Should not run as tests. + @Suppress("unused") + private fun createA(@Suppress("UNUSED_PARAMETER") config: Config): A = fail("Should not be called.") + + override fun createB() = BImpl(AImpl(Config("Subclass"))) // More specific return type is OK. + } + + @Suppress("MemberVisibilityCanPrivate") + internal fun addCreateATo(lh: MutableLazyHub) { + lh.factory(this::createA) + } + + @Suppress("MemberVisibilityCanPrivate") + internal fun addCreateBTo(lh: MutableLazyHub) { + lh.factory(this::createB) + } + + @Test + fun `private factory is not virtual`() { + val baseMethod = this::createA.javaMethod!! + // Check the Subclass version would override if baseMethod wasn't private: + Subclass::class.java.getDeclaredMethod(baseMethod.name, *baseMethod.parameterTypes) + lh.obj(Config("x")) + Subclass().addCreateATo(lh) + lh[A::class] // Should not blow up. + } + + @Test + fun `non-private factory is virtual`() { + Subclass().addCreateBTo(lh) + assertEquals("Subclass", (lh[B::class].a as AImpl).config.info) // Check overridden function was called. + // The signature that was added declares B not BImpl as its return type: + catchThrowable { lh[BImpl::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(BImpl::class.toString(), message) + } + } + + private fun returnsYay() = "yay" + class TakesString(@Suppress("UNUSED_PARAMETER") text: String) + + @Test + fun `too many providers`() { + lh.obj("woo") + lh.factory(this::returnsYay) + lh.impl(TakesString::class) + catchThrowable { lh[TakesString::class] }.run { + assertSame(TooManyProvidersException::class.java, javaClass) + assertEquals(TakesString::class.constructors.single().parameters[0].toString(), message) + assertThat(message, containsString(" #0 ")) + assertThat(message, endsWith(TakesString::class.qualifiedName)) + } + } + + class TakesStringOrInt(val text: String) { + @Suppress("unused") + constructor(number: Int) : this(number.toString()) + } + + @Test + fun `too many providers with alternate constructor`() { + lh.obj("woo") + lh.factory(this::returnsYay) + lh.impl(TakesStringOrInt::class) + val constructors = TakesStringOrInt::class.constructors.toList() + catchThrowable { lh[TakesStringOrInt::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(constructors[0].parameters[0].toString(), message) + assertThat(message, containsString(" #0 ")) + assertThat(message, endsWith(TakesStringOrInt::class.qualifiedName)) + suppressed.single().run { + assertSame(TooManyProvidersException::class.java, javaClass) + assertEquals(constructors[1].parameters[0].toString(), message) + assertThat(message, containsString(" #0 ")) + assertThat(message, endsWith(TakesStringOrInt::class.qualifiedName)) + } + } + lh.obj(123) + assertEquals("123", lh[TakesStringOrInt::class].text) + } + + @Test + fun genericClass() { + class G(val arg: T) + lh.obj("arg") + lh.impl(G::class) + assertEquals("arg", lh[G::class].arg) // Can't inspect type arg T as no such thing exists. + } + + private fun ntv(a: Y) = a.toString() + @Test + fun `nested type variable`() { + // First check it's actually legal to pass any old Closeable into the function: + val arg = Closeable {} + assertEquals(arg.toString(), ntv(arg)) + // Good, now check LazyHub can do it: + val ntv: Function1 = this::ntv + lh.factory(uncheckedCast>(ntv)) + lh.obj(arg) + assertEquals(arg.toString(), lh[String::class]) + } + + class PTWMB(val arg: Y) where Y : Closeable, Y : Serializable + private class CloseableAndSerializable : Closeable, Serializable { + override fun close() {} + } + + @Test + fun `parameter type with multiple bounds in java`() { + // At compile time we must pass something Closeable and Serializable into the constructor: + CloseableAndSerializable().let { assertSame(it, PTWMB(it).arg) } + // But at runtime only Closeable is needed (and Serializable is not enough) due to the leftmost bound erasure rule: + lh.impl(PTWMB::class.java) + lh.obj(object : Serializable {}) + catchThrowable { lh[PTWMB::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertThat(message, containsString(" #0 ")) + assertThat(message, endsWith(PTWMB::class.constructors.single().javaConstructor.toString())) + } + val arg = Closeable {} + lh.obj(arg) + assertSame(arg, lh[PTWMB::class].arg) + } + + @Test + fun `parameter type with multiple bounds in kotlin`() { + lh.impl(PTWMB::class) + lh.obj(object : Serializable {}) + catchThrowable { lh[PTWMB::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(PTWMB::class.constructors.single().parameters[0].toString(), message) + assertThat(message, containsString(" #0 ")) + assertThat(message, containsString(PTWMB::class.qualifiedName)) + } + val arg = Closeable {} + lh.obj(arg) + assertSame(arg, lh[PTWMB::class].arg) + } + + private fun ptwmb(arg: Y) where Y : Closeable, Y : Serializable = arg.toString() + @Test + fun `factory parameter type with multiple bounds`() { + val ptwmb: Function1 = this::ptwmb + val kFunction = uncheckedCast>(ptwmb) + lh.factory(kFunction) + lh.obj(object : Serializable {}) + catchThrowable { lh[String::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(kFunction.parameters[0].toString(), message) + assertThat(message, containsString(" #0 ")) + assertThat(message, endsWith(ptwmb.toString())) + } + val arg = Closeable {} + lh.obj(arg) + assertEquals(arg.toString(), lh[String::class]) + } + + private fun upt(a: Y) = a.toString() + @Test + fun `unbounded parameter type`() { + val upt: Function1 = this::upt + val kFunction: KFunction = uncheckedCast(upt) + lh.factory(kFunction) + // The only provider for Any is the factory, which is busy: + catchThrowable { lh[String::class] }.run { + assertSame(CircularDependencyException::class.java, javaClass) + assertThat(message, containsString("'$upt'")) + assertThat(message, endsWith(listOf(upt).toString())) + } + lh.obj(Any()) + // This time the factory isn't attempted: + catchThrowable { lh[String::class] }.run { + assertSame(TooManyProvidersException::class.java, javaClass) + assertEquals(kFunction.parameters[0].toString(), message) + assertThat(message, containsString(" #0 ")) + assertThat(message, endsWith(upt.toString())) + } + } + + open class NoPublicConstructor protected constructor() + + @Test + fun `no public constructor`() { + catchThrowable { lh.impl(NoPublicConstructor::class) }.run { + assertSame(NoPublicConstructorsException::class.java, javaClass) + assertEquals(NoPublicConstructor::class.toString(), message) + } + catchThrowable { lh.impl(NoPublicConstructor::class.java) }.run { + assertSame(NoPublicConstructorsException::class.java, javaClass) + assertEquals(NoPublicConstructor::class.toString(), message) + } + } + + private fun primitiveInt() = 1 + class IntConsumer(@Suppress("UNUSED_PARAMETER") i: Int) + class IntegerConsumer(@Suppress("UNUSED_PARAMETER") i: Int?) + + @Test + fun `boxed satisfies primitive`() { + lh.obj(1) + lh.impl(IntConsumer::class) + lh[IntConsumer::class] + } + + @Test + fun `primitive satisfies boxed`() { + lh.factory(this::primitiveInt) + lh.impl(IntegerConsumer::class.java) + lh[IntegerConsumer::class] + } + + // The primary constructor takes two distinct providers: + class TakesTwoThings(@Suppress("UNUSED_PARAMETER") first: String, @Suppress("UNUSED_PARAMETER") second: Int) { + // This constructor takes one repeated provider but we count it both times so greediness is 2: + @Suppress("unused") + constructor(first: Int, second: Int) : this(first.toString(), second) + + // This constructor would be greediest but is not satisfiable: + @Suppress("unused") + constructor(first: Int, second: String, @Suppress("UNUSED_PARAMETER") third: Config) : this(second, first) + } + + @Test + fun `equally greedy constructors kotlin`() { + lh.obj("str") + lh.obj(123) + lh.impl(TakesTwoThings::class) + catchThrowable { lh[TakesTwoThings::class] }.run { + assertSame(NoUniqueGreediestSatisfiableConstructorException::class.java, javaClass) + val expected = TakesTwoThings::class.constructors.filter { it.parameters.size == 2 } + assertEquals(2, expected.size) + assertThat(message, endsWith(expected.toString())) + } + } + + @Test + fun `equally greedy constructors java`() { + lh.obj("str") + lh.obj(123) + lh.impl(TakesTwoThings::class.java) + catchThrowable { lh[TakesTwoThings::class] }.run { + assertSame(NoUniqueGreediestSatisfiableConstructorException::class.java, javaClass) + val expected = TakesTwoThings::class.java.constructors.filter { it.parameters.size == 2 } + assertEquals(2, expected.size) + assertEquals(expected.toString(), message) + } + } + + private fun nrt(): String? = fail("Should not be invoked.") + @Test + fun `nullable return type is banned`() { + catchThrowable { lh.factory(this::nrt) }.run { + assertSame(NullableReturnTypeException::class.java, javaClass) + assertThat(message, endsWith(this@LazyHubTests::nrt.toString())) + } + } + + @Test + fun unsatisfiableArrayParam() { + class Impl(@Suppress("UNUSED_PARAMETER") v: Array) + lh.impl(Impl::class) + catchThrowable { lh[Impl::class] }.run { + assertSame(UnsatisfiableArrayException::class.java, javaClass) + assertEquals(Impl::class.constructors.single().parameters[0].toString(), message) + } + // Arrays are only special in real params, you should use getAll to get all the Strings: + catchThrowable { lh[Array::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(Array::class.java.toString(), message) + } + assertEquals(emptyList(), lh.getAll(String::class)) + } + + @Test + fun arrayParam1() { + class Impl(val v: Array) + lh.impl(Impl::class) + lh.obj("a") + assertArrayEquals(arrayOf("a"), lh[Impl::class].v) + } + + @Test + fun arrayParam2() { + class Impl(val v: Array) + lh.impl(Impl::class) + lh.obj("y") + lh.obj("x") + assertArrayEquals(arrayOf("y", "x"), lh[Impl::class].v) + } + + @Test + fun nullableArrayParam() { + class Impl(val v: Array?) + lh.impl(Impl::class) + assertEquals(null, lh[Impl::class].v) + } + + @Test + fun arraysAreNotCached() { + class B(val v: Array) + class A(val v: Array, val b: B) + class C(val v: Array) + class D(val v: Array) + lh.obj("x") + lh.obj("y") + lh.impl(A::class) + lh.impl(B::class) + val a = lh[A::class] + a.run { + assertArrayEquals(arrayOf("x", "y"), v) + assertArrayEquals(arrayOf("x", "y"), b.v) + assertNotSame(v, b.v) + } + assertSame(lh[B::class].v, a.b.v) // Because it's the same (cached) instance of B. + lh.impl(C::class) + lh[C::class].run { + assertArrayEquals(arrayOf("x", "y"), v) + assertNotSame(v, a.v) + assertNotSame(v, a.b.v) + } + lh.obj("z") + lh.impl(D::class) + lh[D::class].run { + assertArrayEquals(arrayOf("x", "y", "z"), v) + } + } + + class C1(@Suppress("UNUSED_PARAMETER") c2: C2) + class C2(@Suppress("UNUSED_PARAMETER") c3: String) + + private fun c3(@Suppress("UNUSED_PARAMETER") c2: C2): String { + fail("Should not be called.") + } + + @Test + fun `circularity error kotlin`() { + lh.impl(C1::class) + lh.impl(C2::class) + lh.factory(this::c3) + catchThrowable { lh[C1::class] }.run { + assertSame(CircularDependencyException::class.java, javaClass) + assertThat(message, containsString("'${C2::class}'")) + assertThat(message, endsWith(listOf(C1::class.constructors.single(), C2::class.constructors.single(), this@LazyHubTests::c3).toString())) + } + } + + @Test + fun `circularity error java`() { + lh.impl(C1::class.java) + lh.impl(C2::class.java) + lh.factory(this::c3) + catchThrowable { lh[C1::class] }.run { + assertSame(CircularDependencyException::class.java, javaClass) + assertThat(message, containsString("'${C2::class}'")) + assertThat(message, endsWith(listOf(C1::class.constructors.single().javaConstructor, C2::class.constructors.single().javaConstructor, this@LazyHubTests::c3).toString())) + } + } + + @Test + fun `ancestor hub providers are visible`() { + val c = Config("over here") + lh.obj(c) + lh.child().also { + it.impl(AImpl::class) + assertSame(c, it[AImpl::class].config) + } + lh.child().child().also { + it.impl(AImpl::class) + assertSame(c, it[AImpl::class].config) + } + } + + @Test + fun `descendant hub providers are not visible`() { + val child = lh.child() + child.obj(Config("over here")) + lh.impl(AImpl::class) + catchThrowable { lh[AImpl::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(AImpl::class.constructors.single().parameters.single().toString(), message) + } + // Fails even though we go via the child, as the cached AImpl in lh shouldn't have collaborators from descendant hubs: + catchThrowable { child[AImpl::class] }.run { + assertSame(NoSuchProviderException::class.java, javaClass) + assertEquals(AImpl::class.constructors.single().parameters.single().toString(), message) + } + } + + class AllConfigs(val configs: Array) + + @Test + fun `nearest ancestor with at least one provider wins`() { + lh.obj(Config("deep")) + lh.child().also { + it.child().also { + it.impl(AllConfigs::class) + assertEquals(listOf("deep"), it[AllConfigs::class].configs.map { it.info }) + } + it.obj(Config("shallow1")) + it.obj(Config("shallow2")) + it.child().also { + it.impl(AllConfigs::class) + assertEquals(listOf("shallow1", "shallow2"), it[AllConfigs::class].configs.map { it.info }) + } + it.child().also { + it.obj(Config("local")) + it.impl(AllConfigs::class) + assertEquals(listOf("local"), it[AllConfigs::class].configs.map { it.info }) + } + } + } + + @Test + fun `abstract type`() { + catchThrowable { lh.impl(Runnable::class) }.run { + assertSame(AbstractTypeException::class.java, javaClass) + assertEquals(Runnable::class.toString(), message) + } + catchThrowable { lh.impl(Runnable::class.java) }.run { + assertSame(AbstractTypeException::class.java, javaClass) + assertEquals(Runnable::class.java.toString(), message) + } + } + + private interface Service + open class GoodService : Service + abstract class BadService1 : Service + class BadService2 private constructor() : Service + + private fun badService3(): Service? = fail("Should not be called.") + @Test + fun `existing providers not removed if new type is bad`() { + lh.impl(GoodService::class) + catchThrowable { lh.impl(Service::class, BadService1::class) }.run { + assertSame(AbstractTypeException::class.java, javaClass) + assertEquals(BadService1::class.toString(), message) + } + catchThrowable { lh.impl(Service::class, BadService2::class) }.run { + assertSame(NoPublicConstructorsException::class.java, javaClass) + assertEquals(BadService2::class.toString(), message) + } + catchThrowable { lh.impl(Service::class, BadService2::class.java) }.run { + assertSame(NoPublicConstructorsException::class.java, javaClass) + assertEquals(BadService2::class.toString(), message) + } + // Type system won't let you pass in badService3, but I still want validation up-front: + catchThrowable { lh.factory(Service::class, uncheckedCast(this::badService3)) }.run { + assertSame(NullableReturnTypeException::class.java, javaClass) + assertEquals(this@LazyHubTests::badService3.toString(), message) + } + assertSame(GoodService::class.java, lh[Service::class].javaClass) + } + + class GoodService2 : GoodService() + + @Test + fun `service providers are removed completely`() { + lh.impl(GoodService::class) + assertSame(GoodService::class.java, lh[Service::class].javaClass) + lh.impl(GoodService::class, GoodService2::class) + // In particular, GoodService is no longer registered against Service (or Any): + assertSame(GoodService2::class.java, lh[Service::class].javaClass) + assertSame(GoodService2::class.java, lh[Any::class].javaClass) + } + + class JParamExample(@Suppress("UNUSED_PARAMETER") str: String, @Suppress("UNUSED_PARAMETER") num: Int) + + @Test + fun `JParam has useful toString`() { + val c = JParamExample::class.java.constructors.single() + // Parameter doesn't expose its index, here we deliberately pass in the wrong one to see what happens: + val text = JParam(c.parameters[0], 1, IOException::class.java).toString() + assertThat(text, containsString(" #1 ")) + assertThat(text, anyOf(containsString(" str "), containsString(" arg0 "))) + assertThat(text, endsWith(c.toString())) + } + + private val sideEffects = mutableListOf() + private fun sideEffect1() { + sideEffects.add(1) + } + + private fun sideEffect2() { + sideEffects.add(2) + } + + @Test + fun `side-effects are idempotent as a consequence of caching of results`() { + lh.factory(this::sideEffect1) + assertEquals(listOf(Unit), lh.getAll(Unit::class)) + assertEquals(listOf(1), sideEffects) + lh.factory(this::sideEffect2) + assertEquals(listOf(Unit, Unit), lh.getAll(Unit::class)) // Get both results. + assertEquals(listOf(1, 2), sideEffects) // sideEffect1 didn't run again. + } + + @Test + fun `getAll returns empty list when there is nothing to return`() { + // This is in contrast to the exception thrown by an array param, which would not be useful to replicate here: + assertEquals(emptyList(), lh.getAll(IOException::class)) + } + + // Two params needed to make primary constructor the winner when both are satisfiable. + // It's probably true that the secondary will always trigger a CircularDependencyException, but LazyHub isn't clever enough to tell. + class InvocationSwitcher(@Suppress("UNUSED_PARAMETER") s: String, @Suppress("UNUSED_PARAMETER") t: String) { + @Suppress("unused") + constructor(same: InvocationSwitcher) : this(same.toString(), same.toString()) + } + + @Test + fun `chosen constructor is not set in stone`() { + lh.impl(InvocationSwitcher::class) + assertSame(CircularDependencyException::class.java, catchThrowable { lh[InvocationSwitcher::class] }.javaClass) + lh.obj("alt") + lh[InvocationSwitcher::class] // Succeeds via other constructor. + } + + class GreedinessUnits(@Suppress("UNUSED_PARAMETER") v: Array, @Suppress("UNUSED_PARAMETER") z: Int) { + // Two greediness units even though it's one provider repeated: + @Suppress("unused") + constructor(z1: Int, z2: Int) : this(emptyArray(), z1 + z2) + } + + @Test + fun `array param counts as one greediness unit`() { + lh.obj("x") + lh.obj("y") + lh.obj(100) + lh.impl(GreedinessUnits::class) + assertSame(NoUniqueGreediestSatisfiableConstructorException::class.java, catchThrowable { lh[GreedinessUnits::class] }.javaClass) + } + + interface TriangleBase + interface TriangleSide : TriangleBase + class TriangleImpl : TriangleBase, TriangleSide + + @Test + fun `provider registered exactly once against each supertype`() { + lh.impl(TriangleImpl::class) + lh[TriangleBase::class] // Don't throw TooManyProvidersException. + } + + interface Service1 + interface Service2 + class ServiceImpl1 : Service1, Service2 + class ServiceImpl2 : Service2 + + @Test + fun `do not leak empty provider list`() { + lh.impl(ServiceImpl1::class) + lh.impl(Service2::class, ServiceImpl2::class) + assertSame(NoSuchProviderException::class.java, catchThrowable { lh[Service1::class] }.javaClass) + } + + class Global + class Session(val global: Global, val local: Int) + + @Test + fun `child can be used to create a scope`() { + lh.impl(Global::class) + lh.factory(lh.child().also { + it.obj(1) + it.impl(Session::class) + }, Session::class) + lh.factory(lh.child().also { + it.obj(2) + it.impl(Session::class) + }, Session::class) + val sessions = lh.getAll(Session::class) + val g = lh[Global::class] + sessions.forEach { assertSame(g, it.global) } + assertEquals(listOf(1, 2), sessions.map { it.local }) + } +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 0b8ae32540..7e8aa8225f 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -5,7 +5,6 @@ import com.google.common.jimfs.Jimfs import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.DoNotImplement -import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.Crypto import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name @@ -15,17 +14,15 @@ import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast -import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients -import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.NodeInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.seconds +import net.corda.lazyhub.MutableLazyHub import net.corda.node.VersionInfo import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode @@ -294,9 +291,14 @@ open class MockNetwork(private val cordappPackages: List, } } + override fun configure(lh: MutableLazyHub) { + super.configure(lh) + lh.factory(this::makeMessagingService) + } + // We only need to override the messaging service here, as currently everything that hits disk does so // through the java.nio API which we are already mocking via Jimfs. - override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService { + private fun makeMessagingService(database: CordaPersistence): MessagingService { require(id >= 0) { "Node ID must be zero or positive, was passed: " + id } return mockNet.messagingNetwork.createNodeWithID( !mockNet.threadPerNode, @@ -315,14 +317,6 @@ open class MockNetwork(private val cordappPackages: List, return E2ETestKeyManagementService(identityService, keyPairs) } - override fun startShell(rpcOps: CordaRPCOps) { - //No mock shell - } - - override fun startMessagingService(rpcOps: RPCOps) { - // Nothing to do - } - // This is not thread safe, but node construction is done on a single thread, so that should always be fine override fun generateKeyPair(): KeyPair { counter = counter.add(BigInteger.ONE) From 1367cd4adb24fe00702ab8985b7fcb328f81c55a Mon Sep 17 00:00:00 2001 From: Anthony Keenan <34482776+anthonykr3@users.noreply.github.com> Date: Mon, 15 Jan 2018 19:30:33 +0000 Subject: [PATCH 12/16] =?UTF-8?q?CORDA-912=20Stop=20exposing=20internal=20?= =?UTF-8?q?node=20user,=20create=20user=20in=20testing=20infrastructu?= =?UTF-8?q?=E2=80=A6=20(#2361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Stop exposing internal node user, create user in testing infrastructure instead * Update kdocs --- .../net/corda/client/jfx/NodeMonitorModelTest.kt | 4 ++-- .../net/corda/client/rpc/CordaRPCJavaClientTest.java | 2 +- .../kotlin/net/corda/client/rpc/CordaRPCClientTest.kt | 2 +- .../kotlin/net/corda/client/rpc/AbstractRPCTest.kt | 2 +- .../kotlin/net/corda/client/rpc/RPCPermissionsTests.kt | 5 +---- .../net/corda/core/flows/ContractUpgradeFlowTest.kt | 2 +- .../net/corda/docs/IntegrationTestingTutorial.kt | 2 +- .../main/kotlin/net/corda/docs/ClientRpcTutorial.kt | 2 +- .../kotlin/net/corda/node/BootTests.kt | 2 +- .../kotlin/net/corda/node/CordappScanningDriverTest.kt | 2 +- .../kotlin/net/corda/node/NodePerformanceTests.kt | 6 +++--- .../kotlin/net/corda/node/SSHServerTest.kt | 3 ++- .../net/corda/node/services/DistributedServiceTests.kt | 2 +- .../services/statemachine/LargeTransactionsTest.kt | 2 +- .../corda/services/messaging/MQSecurityAsRPCTest.kt | 2 +- .../net/corda/services/messaging/MQSecurityTest.kt | 2 +- .../net/corda/test/node/NodeStatePersistenceTests.kt | 2 +- .../net/corda/attachmentdemo/AttachmentDemoTest.kt | 2 +- .../src/main/kotlin/net/corda/attachmentdemo/Main.kt | 2 +- .../kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt | 2 +- .../main/kotlin/net/corda/bank/BankOfCordaCordform.kt | 2 +- .../kotlin/net/corda/irs/IRSDemoTest.kt | 2 +- .../net/corda/notarydemo/SingleNotaryCordform.kt | 6 ++++-- .../kotlin/net/corda/traderdemo/TraderDemoTest.kt | 2 +- .../src/test/kotlin/net/corda/traderdemo/Main.kt | 2 +- .../net/corda/testing/node/FlowStackSnapshotTest.kt | 1 - .../src/main/kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../main/kotlin/net/corda/testing/driver/DriverDSL.kt | 2 +- .../main/kotlin/net/corda/testing/node/NotarySpec.kt | 1 - .../src/main/kotlin/net/corda/testing/node/User.kt | 7 +++++++ .../net/corda/testing/node/internal/DriverDSLImpl.kt | 10 +++++----- .../net/corda/testing/node/internal/NodeBasedTest.kt | 5 +++-- .../net/corda/testing/node/internal/RPCDriver.kt | 5 +++-- .../testing/node/internal/demorun/CordformUtils.kt | 4 ++-- .../kotlin/net/corda/explorer/ExplorerSimulation.kt | 2 +- 35 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/User.kt diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index b70d60bd55..860ab8d4d2 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -2,6 +2,7 @@ package net.corda.client.jfx import net.corda.client.jfx.model.NodeMonitorModel import net.corda.client.jfx.model.ProgressTrackingEvent +import net.corda.core.context.Origin import net.corda.core.contracts.Amount import net.corda.core.contracts.ContractState import net.corda.core.crypto.isFulfilledBy @@ -10,7 +11,6 @@ import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.bufferUntilSubscribed -import net.corda.core.context.Origin import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.messaging.StateMachineUpdate @@ -28,9 +28,9 @@ 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.internal.config.User import net.corda.testing.* import net.corda.testing.driver.driver +import net.corda.testing.node.User import org.junit.Test import rx.Observable diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java index ac6272f579..a1c93caac5 100644 --- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -10,8 +10,8 @@ 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.internal.config.User; import net.corda.testing.CoreTestUtils; +import net.corda.testing.node.User; import net.corda.testing.node.internal.NodeBasedTest; import org.junit.After; import org.junit.Before; diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 51c17f0baf..e15b9dfc04 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -20,8 +20,8 @@ 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.internal.config.User import net.corda.testing.* +import net.corda.testing.node.User import net.corda.testing.node.internal.NodeBasedTest import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.assertj.core.api.Assertions.assertThat diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/AbstractRPCTest.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/AbstractRPCTest.kt index 11a9a027fa..4bcf3ec51f 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/AbstractRPCTest.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/AbstractRPCTest.kt @@ -5,8 +5,8 @@ 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.internal.config.User import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.node.User import net.corda.testing.node.internal.RPCDriverDSL import net.corda.testing.node.internal.rpcTestUser import net.corda.testing.node.internal.startInVmRpcClient diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt index fae5fbc6d5..21ebac1fbd 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt @@ -1,11 +1,8 @@ package net.corda.client.rpc -import net.corda.core.flows.FlowLogic -import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps -import net.corda.node.services.Permissions import net.corda.node.services.messaging.rpcContext -import net.corda.nodeapi.internal.config.User +import net.corda.testing.node.User import net.corda.testing.node.internal.RPCDriverDSL import net.corda.testing.node.internal.rpcDriver import org.junit.Test diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index eb3b5609a5..818c58ebf2 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -19,9 +19,9 @@ 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.internal.config.User import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME +import net.corda.testing.node.User import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContractV2 import net.corda.testing.node.internal.RPCDriverDSL diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index d398072edc..47f6b74ea1 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -13,9 +13,9 @@ 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.internal.config.User import net.corda.testing.* import net.corda.testing.driver.driver +import net.corda.testing.node.User import org.junit.Test import kotlin.test.assertEquals diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt index 685a10c4ad..5e61c128b1 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt @@ -17,8 +17,8 @@ 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.internal.config.User import net.corda.testing.ALICE_NAME +import net.corda.testing.node.User import net.corda.testing.driver.driver import org.graphstream.graph.Edge import org.graphstream.graph.Node diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 33124f11ef..6f1b74b446 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -8,8 +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.internal.config.User import net.corda.testing.ALICE_NAME +import net.corda.testing.node.User import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt index 422ae6e1ac..f420896945 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt @@ -8,9 +8,9 @@ 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.internal.config.User import net.corda.testing.* import net.corda.testing.driver.driver +import net.corda.testing.node.User import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 583b30b4fd..cf62847193 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -13,16 +13,16 @@ 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.internal.config.User import net.corda.testing.DUMMY_NOTARY_NAME +import net.corda.testing.node.User import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver -import net.corda.testing.node.internal.InternalDriverDSL import net.corda.testing.internal.performance.div +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.InternalDriverDSL import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector import net.corda.testing.node.internal.performance.startReporter import net.corda.testing.node.internal.performance.startTightLoopInjector -import net.corda.testing.node.NotarySpec import org.junit.Before import org.junit.Ignore import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt index 67365bf422..78ac121dac 100644 --- a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt @@ -12,11 +12,12 @@ import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.internal.config.User import net.corda.testing.ALICE_NAME +import net.corda.testing.node.User import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.util.io.Streams +import org.junit.Ignore import org.junit.Test import java.net.ConnectException import java.util.regex.Pattern diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 2d7e4b66a7..710b5ee97a 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -13,11 +13,11 @@ 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.internal.config.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User import net.corda.testing.node.internal.DummyClusterSpec import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt index cf8b4593b9..77f6e0c560 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -8,11 +8,11 @@ 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.internal.config.User import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import net.corda.testing.driver.driver +import net.corda.testing.node.User import org.junit.Test import kotlin.test.assertEquals diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt index eab85f937a..4609aab319 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsRPCTest.kt @@ -1,6 +1,6 @@ package net.corda.services.messaging -import net.corda.nodeapi.internal.config.User +import net.corda.testing.node.User import org.junit.Test /** diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 3381d1bcc9..d08866c2f2 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -24,9 +24,9 @@ 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.internal.config.SSLConfiguration -import net.corda.nodeapi.internal.config.User import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME +import net.corda.testing.node.User import net.corda.testing.chooseIdentity import net.corda.testing.internal.configureTestSSL import net.corda.testing.node.internal.NodeBasedTest diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index ee94671bca..bbd0d7c209 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -19,7 +19,7 @@ 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.internal.config.User +import net.corda.testing.node.User import net.corda.testing.chooseIdentity import net.corda.testing.driver.driver import org.junit.Assume.assumeFalse diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index f2bd4207d3..0384710236 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -4,9 +4,9 @@ import net.corda.core.messaging.CordaRPCOps 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.internal.config.User import net.corda.testing.DUMMY_BANK_A_NAME import net.corda.testing.DUMMY_BANK_B_NAME +import net.corda.testing.node.User import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver import org.junit.Test diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt index 9f8c834519..44108c93c8 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt @@ -1,9 +1,9 @@ package net.corda.attachmentdemo import net.corda.core.internal.div -import net.corda.nodeapi.internal.config.User import net.corda.testing.DUMMY_BANK_A_NAME import net.corda.testing.DUMMY_BANK_B_NAME +import net.corda.testing.node.User import net.corda.testing.driver.driver /** diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index 268195d935..27177358b2 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -11,9 +11,9 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.internal.config.User import net.corda.testing.* import net.corda.testing.driver.driver +import net.corda.testing.node.User import org.junit.Test class BankOfCordaRPCClientTest { diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt index b135d7baa1..9e7b1dae83 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt @@ -12,9 +12,9 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.config.NotaryConfig -import net.corda.nodeapi.internal.config.User import net.corda.testing.node.internal.demorun.* import net.corda.testing.BOC_NAME +import net.corda.testing.node.User import java.util.* import kotlin.system.exitProcess diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index 90df9427a3..f80342e918 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -21,11 +21,11 @@ import net.corda.finance.plugin.registerFinanceJSONMappers import net.corda.irs.contract.InterestRateSwap import net.corda.irs.web.IrsDemoWebApplication import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.internal.config.User import net.corda.test.spring.springDriver import net.corda.testing.* import net.corda.testing.http.HttpApi import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index 4910a57d3e..8386ccece9 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -4,9 +4,11 @@ import net.corda.cordform.CordformContext import net.corda.cordform.CordformDefinition import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.config.NotaryConfig -import net.corda.nodeapi.internal.config.User +import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME +import net.corda.testing.DUMMY_NOTARY_NAME +import net.corda.testing.node.User import net.corda.testing.node.internal.demorun.* -import net.corda.testing.* import java.nio.file.Paths fun main(args: Array) = SingleNotaryCordform().deployNodes() diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index 51bc7c99da..0cd187f96d 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -8,10 +8,10 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.internal.config.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import net.corda.testing.node.User import net.corda.testing.node.internal.poll import net.corda.traderdemo.flow.BuyerFlow import net.corda.traderdemo.flow.CommercialPaperIssueFlow diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt index c3314ff155..6b4f7fab0e 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt @@ -4,10 +4,10 @@ import net.corda.core.internal.div import net.corda.finance.flows.CashIssueFlow import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.internal.config.User import net.corda.testing.BOC_NAME import net.corda.testing.DUMMY_BANK_A_NAME import net.corda.testing.DUMMY_BANK_B_NAME +import net.corda.testing.node.User import net.corda.testing.driver.driver import net.corda.traderdemo.flow.CommercialPaperIssueFlow import net.corda.traderdemo.flow.SellerFlow diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt index 4212902a73..17fdbc814a 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt @@ -9,7 +9,6 @@ import net.corda.core.internal.read import net.corda.core.messaging.startFlow import net.corda.core.serialization.CordaSerializable import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.internal.config.User import net.corda.testing.driver.driver import org.junit.Ignore import org.junit.Test diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 0824108dae..7866a7cb91 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -14,8 +14,8 @@ import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType -import net.corda.nodeapi.internal.config.User import net.corda.testing.DUMMY_NOTARY_NAME +import net.corda.testing.node.User import net.corda.testing.node.NotarySpec import net.corda.testing.node.internal.DriverDSLImpl import net.corda.testing.node.internal.genericDriver diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt index a4908d5f53..2cf3be566e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -7,7 +7,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.concurrent.map import net.corda.node.internal.Node import net.corda.node.services.config.VerifierType -import net.corda.nodeapi.internal.config.User +import net.corda.testing.node.User import net.corda.testing.node.NotarySpec import java.nio.file.Path diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt index 128ed9070b..348de42338 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt @@ -3,7 +3,6 @@ package net.corda.testing.node import net.corda.core.DoNotImplement import net.corda.core.identity.CordaX500Name import net.corda.node.services.config.VerifierType -import net.corda.nodeapi.internal.config.User data class NotarySpec( val name: CordaX500Name, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/User.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/User.kt new file mode 100644 index 0000000000..a60ea9fc26 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/User.kt @@ -0,0 +1,7 @@ +package net.corda.testing.node + +/** Object encapsulating a node rpc user and their associated permissions for use when testing */ +data class User( + val username: String, + val password: String, + val permissions: Set) \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 1e4f710449..24d08bc896 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -31,7 +31,6 @@ import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.addShutdownHook -import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.crypto.X509Utilities @@ -49,6 +48,7 @@ import net.corda.testing.driver.* import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT import net.corda.testing.setGlobalSerialization @@ -74,6 +74,7 @@ import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread +import net.corda.nodeapi.internal.config.User as InternalUser class DriverDSLImpl( val portAllocation: PortAllocation, @@ -127,8 +128,7 @@ class DriverDSLImpl( val jarPattern = jarNamePattern.toRegex() val jarFileUrl = urls.first { jarPattern.matches(it.path) } Paths.get(jarFileUrl.toURI()).toString() - } - catch(e: Exception) { + } catch (e: Exception) { log.warn("Unable to locate JAR `$jarNamePattern` on classpath: ${e.message}", e) throw e } @@ -686,7 +686,7 @@ class DriverDSLImpl( companion object { internal val log = contextLogger() - private val defaultRpcUserList = listOf(User("default", "default", setOf("ALL")).toConfig().root().unwrapped()) + private val defaultRpcUserList = listOf(InternalUser("default", "default", setOf("ALL")).toConfig().root().unwrapped()) private val names = arrayOf(ALICE_NAME, BOB_NAME, DUMMY_BANK_A_NAME) /** * A sub-set of permissions that grant most of the essential operations used in the unit/integration tests as well as @@ -749,7 +749,7 @@ class DriverDSLImpl( "name" to config.corda.myLegalName, "visualvm.display.name" to "corda-${config.corda.myLegalName}", "java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process - "log4j2.debug" to if(debugPort != null) "true" else "false" + "log4j2.debug" to if (debugPort != null) "true" else "false" ) if (cordappPackages.isNotEmpty()) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 4c5772b308..e4ee8091c0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -12,9 +12,10 @@ import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* -import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.config.toConfig import net.corda.testing.SerializationEnvironmentRule import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.testing.node.User import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.getFreeLocalPorts import net.corda.testing.internal.testThreadFactory @@ -93,7 +94,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi "myLegalName" to legalName.toString(), "p2pAddress" to p2pAddress, "rpcAddress" to localPort[1].toString(), - "rpcUsers" to rpcUsers.map { it.toMap() } + "rpcUsers" to rpcUsers.map { it.toConfig().root().unwrapped() } ) + configOverrides ) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index a5dff915ca..5370276fb2 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -22,12 +22,12 @@ import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.RPCApi -import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.testing.MAX_MESSAGE_SIZE import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User 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 @@ -52,6 +52,7 @@ import java.lang.reflect.Method import java.nio.file.Path import java.nio.file.Paths import java.util.* +import net.corda.nodeapi.internal.config.User as InternalUser inline fun RPCDriverDSL.startInVmRpcClient( username: String = rpcTestUser.username, @@ -433,7 +434,7 @@ data class RPCDriverDSL( minLargeMessageSize = MAX_MESSAGE_SIZE isUseGlobalPools = false } - val rpcSecurityManager = RPCSecurityManagerImpl.fromUserList(users = listOf(rpcUser), id = AuthServiceId("TEST_SECURITY_MANAGER")) + val rpcSecurityManager = RPCSecurityManagerImpl.fromUserList(users = listOf(InternalUser(rpcUser.username, rpcUser.password, rpcUser.permissions)) , id = AuthServiceId("TEST_SECURITY_MANAGER")) val rpcServer = RPCServer( ops, rpcUser.username, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformUtils.kt index 25ab1bed6b..5807bd893b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformUtils.kt @@ -6,8 +6,8 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.node.services.config.NotaryConfig -import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.toConfig +import net.corda.testing.node.User fun CordformDefinition.node(configure: CordformNode.() -> Unit) { addNode { cordformNode -> cordformNode.configure() } @@ -16,7 +16,7 @@ fun CordformDefinition.node(configure: CordformNode.() -> Unit) { fun CordformNode.name(name: CordaX500Name) = name(name.toString()) fun CordformNode.rpcUsers(vararg users: User) { - rpcUsers = users.map { it.toMap() } + rpcUsers = users.map { it.toConfig().root().unwrapped() } } fun CordformNode.notary(notaryConfig: NotaryConfig) { diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index c3f0c0c173..faebad502d 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -21,9 +21,9 @@ import net.corda.finance.flows.* import net.corda.finance.flows.CashExitFlow.ExitRequest import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.nodeapi.internal.config.User import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME +import net.corda.testing.node.User import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation From 55b7035a447e76f8a6eb0f59d3cd1d03b664edfc Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Tue, 16 Jan 2018 09:58:23 +0000 Subject: [PATCH 13/16] Don't propagate internal notary service errors to the client (#2368) --- core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt | 8 ++++++-- .../kotlin/net/corda/core/node/services/NotaryService.kt | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 0fccf0cb4c..52a802ed0a 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -158,7 +158,7 @@ class NotaryFlow { */ data class TransactionParts(val id: SecureHash, val inputs: List, val timestamp: TimeWindow?, val notary: Party?) -class NotaryException(val error: NotaryError) : FlowException("Error response from Notary - $error") +class NotaryException(val error: NotaryError) : FlowException("Unable to notarise: $error") @CordaSerializable sealed class NotaryError { @@ -166,7 +166,7 @@ sealed class NotaryError { override fun toString() = "One or more input states for transaction $txId have been used in another transaction" } - /** Thrown if the time specified in the [TimeWindow] command is outside the allowed tolerance. */ + /** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */ object TimeWindowInvalid : NotaryError() data class TransactionInvalid(val cause: Throwable) : NotaryError() { @@ -174,4 +174,8 @@ sealed class NotaryError { } object WrongNotary : NotaryError() + + data class General(val cause: String): NotaryError() { + override fun toString() = cause + } } diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index 8c6a160618..5fff5d54ce 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -78,6 +78,9 @@ abstract class TrustedAuthorityNotaryService : NotaryService() { log.warn("Notary conflicts for $txId: $conflicts") throw notaryException(txId, e) } + } catch (e: Exception) { + log.error("Internal error", e) + throw NotaryException(NotaryError.General("Service unavailable, please try again later")) } } From 7bc2f2a8e6c62dd7d35857bd5d7eb24be542d9ae Mon Sep 17 00:00:00 2001 From: Przemyslaw Bak Date: Tue, 16 Jan 2018 10:02:53 +0000 Subject: [PATCH 14/16] Set maxMessageSize and maxTransactionSize at the real, useful values --- network-management/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network-management/README.md b/network-management/README.md index 4f278c83df..3753d44410 100644 --- a/network-management/README.md +++ b/network-management/README.md @@ -174,8 +174,8 @@ networkMapConfig { validating: false }] minimumPlatformVersion = 1 - maxMessageSize = 100 - maxTransactionSize = 100 + maxMessageSize = 10485760 + maxTransactionSize = 40000 Save the parameters to `network-parameters.conf` From 66b47f0926b112f5d3d1b590477b745ede6f2895 Mon Sep 17 00:00:00 2001 From: Przemyslaw Bak Date: Tue, 16 Jan 2018 10:44:54 +0000 Subject: [PATCH 15/16] Highlight some text. --- network-management/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network-management/README.md b/network-management/README.md index 3753d44410..a3a6da45ff 100644 --- a/network-management/README.md +++ b/network-management/README.md @@ -149,8 +149,8 @@ networkMapConfig { they attempt to register. The trust store password is `trustpass`. ### 2. Start Doorman service for notary registration - Start the network management server with the doorman service for initial bootstrapping. Network map service should be disabled at this point. - Comment out network map config in the config file and start the server by running : + Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMapConfig`) should be **disabled** at this point. + **Comment out** network map config in the config file and start the server by running : ``` java -jar doorman-.jar ``` From 42b4b80545b57ca7d60e3ea34eb07164ccfc9076 Mon Sep 17 00:00:00 2001 From: josecoll Date: Tue, 16 Jan 2018 13:24:53 +0000 Subject: [PATCH 16/16] Fixed imports --- .../kotlin/net/corda/node/BootTests.kt | 3 +++ .../kotlin/net/corda/node/NodePerformanceTests.kt | 7 ++++--- .../kotlin/net/corda/node/SSHServerTest.kt | 7 +------ .../corda/node/services/AttachmentLoadingTests.kt | 7 +------ .../corda/services/messaging/MQSecurityTest.kt | 4 +--- .../services/persistence/DBTransactionStorage.kt | 15 ++++++++++----- .../flows/NoSelectionIntegrationTest.kt | 2 +- .../flows/TwoPartyTradeFlowTest.kt | 4 ++-- .../corda/attachmentdemo/AttachmentDemoTest.kt | 1 + .../corda/testing/node/internal/NodeBasedTest.kt | 7 +++---- .../com/r3/corda/jmeter/StartLocalPerfCorDapp.kt | 2 +- 11 files changed, 28 insertions(+), 31 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 5a04f40721..cf0adc7f28 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -9,6 +9,9 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup import net.corda.node.services.Permissions.Companion.startFlow import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME +import net.corda.testing.DUMMY_BANK_A_NAME +import net.corda.testing.DUMMY_NOTARY_NAME import net.corda.testing.node.User import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.driver.driver diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 0045ca386a..8332c95acc 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -13,19 +13,20 @@ 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.testing.ALICE_NAME +import net.corda.testing.DUMMY_BANK_A_NAME import net.corda.testing.DUMMY_NOTARY_NAME -import net.corda.testing.node.User +import net.corda.testing.TestIdentity import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.performance.div -import net.corda.testing.node.NotarySpec -import net.corda.testing.node.internal.InternalDriverDSL import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaNames import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User import net.corda.testing.node.internal.InternalDriverDSL import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector import net.corda.testing.node.internal.performance.startReporter diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt index f053a4e98e..3608e42630 100644 --- a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt @@ -12,20 +12,15 @@ import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.testing.ALICE_NAME -import net.corda.testing.node.User import net.corda.testing.ALICE_NAME import net.corda.testing.driver.driver -import org.assertj.core.api.Assertions.assertThat import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName +import net.corda.testing.node.User import org.assertj.core.api.Assertions.assertThat import org.bouncycastle.util.io.Streams -import org.junit.Ignore import org.junit.ClassRule -import org.junit.Ignore import org.junit.Test import java.net.ConnectException import java.util.regex.Pattern diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 90b8081714..a9d190355a 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -20,19 +20,14 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.testing.DUMMY_BANK_A_NAME -import net.corda.testing.DUMMY_NOTARY_NAME -import net.corda.testing.SerializationEnvironmentRule -import net.corda.testing.TestIdentity +import net.corda.testing.* import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver -import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.* import net.corda.testing.services.MockAttachmentStorage import org.junit.Assert.assertEquals import org.junit.ClassRule -import org.junit.Ignore import org.junit.Rule import org.junit.Test import java.net.URLClassLoader diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index cf6c72ab39..d1a69c9be5 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -26,13 +26,11 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREF import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.testing.ALICE_NAME import net.corda.testing.BOB_NAME -import net.corda.testing.node.User import net.corda.testing.chooseIdentity -import net.corda.nodeapi.internal.config.User -import net.corda.testing.* import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.configureTestSSL import net.corda.testing.internal.toDatabaseSchemaName +import net.corda.testing.node.User import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.startFlow import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index b3c2633229..d715cc70d5 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -1,15 +1,20 @@ package net.corda.node.services.persistence -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.bufferUntilSubscribed + +import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature import net.corda.core.internal.ThreadBox +import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.bufferUntilSubscribed +import net.corda.core.internal.concurrent.doneFuture import net.corda.core.messaging.DataFeed import net.corda.core.serialization.* +import net.corda.core.toFuture import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.SignedTransaction import net.corda.node.services.api.WritableTransactionStorage -import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.AppendOnlyPersistentMapBase +import net.corda.node.utilities.WeightBasedAppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction @@ -90,7 +95,7 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S override fun track(): DataFeed, SignedTransaction> { return txStorage.locked { - DataFeed(allPersisted().map { it.second }.toList(), updatesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updatesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) } } @@ -100,7 +105,7 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S if (existingTransaction == null) { updatesPublisher.filter { it.id == id }.toFuture() } else { - doneFuture(existingTransaction) + doneFuture(existingTransaction.toSignedTx()) } } } diff --git a/perftestcordapp/src/integrationTest/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/NoSelectionIntegrationTest.kt b/perftestcordapp/src/integrationTest/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/NoSelectionIntegrationTest.kt index bf0cbd457f..c58ea5fb46 100644 --- a/perftestcordapp/src/integrationTest/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/NoSelectionIntegrationTest.kt +++ b/perftestcordapp/src/integrationTest/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/NoSelectionIntegrationTest.kt @@ -5,9 +5,9 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.node.services.Permissions -import net.corda.nodeapi.internal.config.User import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver +import net.corda.testing.node.User import org.junit.Ignore import org.junit.Test diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index b41e869971..a70fd21d16 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -328,8 +328,8 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = { args -> object : MockNetwork.MockNode(args) { // That constructs a recording tx storage - override fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage { - return RecordingTransactionStorage(database, super.makeTransactionStorage(database)) + override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes)) } } }) diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index 26da2996a0..c8238d8b75 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -6,6 +6,7 @@ import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow import net.corda.testing.DUMMY_BANK_A_NAME import net.corda.testing.DUMMY_BANK_B_NAME +import net.corda.testing.DUMMY_NOTARY_NAME import net.corda.testing.node.User import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 2cd144de53..430fa81d58 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -13,15 +13,14 @@ import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* import net.corda.nodeapi.internal.config.toConfig -import net.corda.nodeapi.internal.config.User -import net.corda.testing.internal.IntegrationTest -import net.corda.testing.SerializationEnvironmentRule import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.testing.node.User +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.getFreeLocalPorts +import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO +import net.corda.testing.node.User import org.apache.logging.log4j.Level import org.junit.After import org.junit.Before diff --git a/tools/jmeter/src/test/kotlin/com/r3/corda/jmeter/StartLocalPerfCorDapp.kt b/tools/jmeter/src/test/kotlin/com/r3/corda/jmeter/StartLocalPerfCorDapp.kt index 7f23b83893..15939f8731 100644 --- a/tools/jmeter/src/test/kotlin/com/r3/corda/jmeter/StartLocalPerfCorDapp.kt +++ b/tools/jmeter/src/test/kotlin/com/r3/corda/jmeter/StartLocalPerfCorDapp.kt @@ -2,9 +2,9 @@ package com.r3.corda.jmeter import net.corda.core.utilities.getOrThrow import net.corda.node.services.Permissions -import net.corda.nodeapi.internal.config.User import net.corda.testing.DUMMY_NOTARY_NAME import net.corda.testing.node.NotarySpec +import net.corda.testing.node.User import org.slf4j.LoggerFactory import java.io.BufferedReader import java.io.InputStreamReader