mirror of
https://github.com/corda/corda.git
synced 2025-06-01 07:00:54 +00:00
Merge remote-tracking branch 'remotes/open/master' into merges/may-29-16-48
# Conflicts: # node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt # node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt # node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt # node/src/main/kotlin/net/corda/node/internal/Node.kt # node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt # node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt # node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt # node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt # node/src/test/kotlin/net/corda/node/internal/NodeTest.kt # node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt # node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt # node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt # node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt # node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt # node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt # node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt # node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt # node/src/test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt # node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt # node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt # samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt # testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
This commit is contained in:
commit
de0c69a888
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -98,6 +98,8 @@
|
|||||||
<module name="experimental-behave_main" target="1.8" />
|
<module name="experimental-behave_main" target="1.8" />
|
||||||
<module name="experimental-behave_smokeTest" target="1.8" />
|
<module name="experimental-behave_smokeTest" target="1.8" />
|
||||||
<module name="experimental-behave_test" target="1.8" />
|
<module name="experimental-behave_test" target="1.8" />
|
||||||
|
<module name="experimental-blobinspector_main" target="1.8" />
|
||||||
|
<module name="experimental-blobinspector_test" target="1.8" />
|
||||||
<module name="experimental-kryo-hook_main" target="1.8" />
|
<module name="experimental-kryo-hook_main" target="1.8" />
|
||||||
<module name="experimental-kryo-hook_test" target="1.8" />
|
<module name="experimental-kryo-hook_test" target="1.8" />
|
||||||
<module name="experimental_main" target="1.8" />
|
<module name="experimental_main" target="1.8" />
|
||||||
|
@ -31,7 +31,7 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
import org.apache.activemq.artemis.api.core.ActiveMQException
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
@ -202,8 +202,11 @@ class NodeMonitorModel {
|
|||||||
val nodeInfo = _connection.proxy.nodeInfo()
|
val nodeInfo = _connection.proxy.nodeInfo()
|
||||||
require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty())
|
require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty())
|
||||||
_connection
|
_connection
|
||||||
} catch(secEx: ActiveMQSecurityException) {
|
} catch(secEx: ActiveMQException) {
|
||||||
// Happens when incorrect credentials provided - no point to retry connecting.
|
// Happens when:
|
||||||
|
// * incorrect credentials provided;
|
||||||
|
// * incorrect endpoint specified;
|
||||||
|
// - no point to retry connecting.
|
||||||
throw secEx
|
throw secEx
|
||||||
}
|
}
|
||||||
catch(th: Throwable) {
|
catch(th: Throwable) {
|
||||||
|
@ -23,7 +23,22 @@
|
|||||||
|
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||||
<PatternLayout pattern="%highlight{%level{length=1} %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %equals{%X}{{}}{}%n}{INFO=white,WARN=red,FATAL=bright red}" />
|
<PatternLayout>
|
||||||
|
<ScriptPatternSelector defaultPattern="%highlight{%level{length=1} %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}">
|
||||||
|
<Script name="MDCSelector" language="javascript"><![CDATA[
|
||||||
|
result = null;
|
||||||
|
if (!logEvent.getContextData().size() == 0) {
|
||||||
|
result = "WithMDC";
|
||||||
|
} else {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
result;
|
||||||
|
]]>
|
||||||
|
</Script>
|
||||||
|
<PatternMatch key="WithMDC" pattern="%highlight{%level{length=1} %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %X%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||||
|
</ScriptPatternSelector>
|
||||||
|
</PatternLayout>
|
||||||
|
<ThresholdFilter level="trace"/>
|
||||||
</Console>
|
</Console>
|
||||||
|
|
||||||
<!-- Required for printBasicInfo -->
|
<!-- Required for printBasicInfo -->
|
||||||
|
@ -252,26 +252,6 @@ The network must then be manually run before retrieving the future's value:
|
|||||||
Accessing ``StartedMockNode`` internals
|
Accessing ``StartedMockNode`` internals
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Creating a node database transaction
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Whenever you query a node's database (e.g. to extract information from the node's vault), you must wrap the query in
|
|
||||||
a database transaction, as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
nodeA.database.transaction {
|
|
||||||
// Perform query here.
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
|
||||||
|
|
||||||
node.getDatabase().transaction(tx -> {
|
|
||||||
// Perform query here.
|
|
||||||
}
|
|
||||||
|
|
||||||
Querying a node's vault
|
Querying a node's vault
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -281,15 +261,11 @@ Recorded states can be retrieved from the vault of a ``StartedMockNode`` using:
|
|||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
nodeA.database.transaction {
|
val myStates = nodeA.services.vaultService.queryBy<MyStateType>().states
|
||||||
val myStates = nodeA.services.vaultService.queryBy<MyStateType>().states
|
|
||||||
}
|
|
||||||
|
|
||||||
.. sourcecode:: java
|
.. sourcecode:: java
|
||||||
|
|
||||||
node.getDatabase().transaction(tx -> {
|
List<MyStateType> myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates();
|
||||||
List<MyStateType> myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes.
|
This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes.
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ Unreleased
|
|||||||
==========
|
==========
|
||||||
* Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code.
|
* Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code.
|
||||||
|
|
||||||
|
* ``ServiceHub`` and ``CordaRPCOps`` can now safely be used from multiple threads without incurring in database transaction problems.
|
||||||
|
|
||||||
* Doorman and NetworkMap url's can now be configured individually rather than being assumed to be
|
* Doorman and NetworkMap url's can now be configured individually rather than being assumed to be
|
||||||
the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file`
|
the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file`
|
||||||
and :doc:`permissioning` for details.
|
and :doc:`permissioning` for details.
|
||||||
|
@ -6,7 +6,7 @@ Software requirements
|
|||||||
Corda uses industry-standard tools:
|
Corda uses industry-standard tools:
|
||||||
|
|
||||||
* **Oracle JDK 8 JVM** - minimum supported version **8u131**
|
* **Oracle JDK 8 JVM** - minimum supported version **8u131**
|
||||||
* **IntelliJ IDEA** - supported versions **2017.1**, **2017.2** and **2017.3**
|
* **IntelliJ IDEA** - supported versions **2017.x** and **2018.x**
|
||||||
* **Git**
|
* **Git**
|
||||||
|
|
||||||
We also use Gradle and Kotlin, but you do not need to install them. A standalone Gradle wrapper is provided, and it
|
We also use Gradle and Kotlin, but you do not need to install them. A standalone Gradle wrapper is provided, and it
|
||||||
@ -80,7 +80,7 @@ Download a sample project
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
1. Open a command prompt
|
1. Open a command prompt
|
||||||
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
|
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
|
||||||
3. Move into the cordapp-example folder by running ``cd cordapp-example``
|
3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example``
|
||||||
|
|
||||||
Run from the command prompt
|
Run from the command prompt
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -92,16 +92,22 @@ Run from the command prompt
|
|||||||
Run from IntelliJ
|
Run from IntelliJ
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
1. Open IntelliJ Community Edition
|
1. Open IntelliJ Community Edition
|
||||||
2. On the splash screen, click "Open" (do NOT click "Import Project") and select the cordapp-example folder
|
2. On the splash screen, click ``Open`` (do **not** click ``Import Project``) and select the ``cordapp-example`` folder
|
||||||
|
|
||||||
.. warning:: If you click "Import Project" instead of "Open", the project's run configurations will be erased!
|
.. warning:: If you click ``Import Project`` instead of ``Open``, the project's run configurations will be erased!
|
||||||
|
|
||||||
3. Once the project is open, click "File > Project Structure". Under "Project SDK:", set the project SDK by clicking "New...", clicking "JDK", and navigating to C:\\Program Files\\Java\\jdk1.8.0_XXX (where "XXX" is the latest minor version number). Click "OK".
|
3. Once the project is open, click ``File``, then ``Project Structure``. Under ``Project SDK:``, set the project SDK by
|
||||||
4. Click "View > Tool Windows > Event Log", and click "Import Gradle project", then "OK". Wait, and click "OK" again when the "Gradle Project Data To Import" window appears
|
clicking ``New...``, clicking ``JDK``, and navigating to ``C:\\Program Files\\Java\\jdk1.8.0_XXX`` (where ``XXX`` is
|
||||||
5. Wait for indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing is complete)
|
the latest minor version number). Click "OK"
|
||||||
6. At the top-right of the screen, to the left of the green "play" arrow, you should see a dropdown. In that dropdown, select "Run Example Cordapp - Kotlin" and click the green "play" arrow.
|
4. Again under ``File`` then ``Project Structure``, select ``Modules``. Click ``+``, then ``Import Module``, then select
|
||||||
7. Wait until the run windows displays the message "Webserver started up in XX.X sec"
|
the ``cordapp-example`` folder and click ``Open``. Choose to ``Import module from external model``, select
|
||||||
8. Test the CorDapp is running correctly by visiting the front end at http://localhost:10007/web/example/
|
``Gradle``, click ``Next`` then ``Finish`` (leaving the defaults) and ``OK``
|
||||||
|
5. Wait for the indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing
|
||||||
|
is complete)
|
||||||
|
6. At the top-right of the screen, to the left of the green ``play`` arrow, you should see a dropdown. In that
|
||||||
|
dropdown, select ``Run Example Cordapp - Kotlin`` and click the green ``play`` arrow.
|
||||||
|
7. Wait until the run windows displays the message ``Webserver started up in XX.X sec``
|
||||||
|
8. Test the CorDapp is running correctly by visiting the front end at `http://localhost:10007/web/example/
|
||||||
|
|
||||||
.. _mac-label:
|
.. _mac-label:
|
||||||
|
|
||||||
@ -128,7 +134,7 @@ Download a sample project
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
1. Open a terminal
|
1. Open a terminal
|
||||||
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
|
2. Clone the CorDapp example repo by running ``git clone https://github.com/corda/cordapp-example``
|
||||||
3. Move into the cordapp-example folder by running ``cd cordapp-example``
|
3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example``
|
||||||
|
|
||||||
Run from the terminal
|
Run from the terminal
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -140,13 +146,22 @@ Run from the terminal
|
|||||||
Run from IntelliJ
|
Run from IntelliJ
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
1. Open IntelliJ Community Edition
|
1. Open IntelliJ Community Edition
|
||||||
2. On the splash screen, click "Open" (do NOT click "Import Project") and select the cordapp-example folder
|
2. On the splash screen, click ``Open`` (do **not** click ``Import Project``) and select the ``cordapp-example`` folder
|
||||||
3. Once the project is open, click "File > Project Structure". Under "Project SDK:", set the project SDK by clicking "New...", clicking "JDK", and navigating to /Library/Java/JavaVirtualMachines/jdk1.8.0_XXX (where "XXX" is the latest minor version number). Click "OK".
|
|
||||||
4. Click "View > Tool Windows > Event Log", and click "Import Gradle project", then "OK". Wait, and click "OK" again when the "Gradle Project Data To Import" window appears
|
.. warning:: If you click ``Import Project`` instead of ``Open``, the project's run configurations will be erased!
|
||||||
5. Wait for indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing is complete)
|
|
||||||
6. At the top-right of the screen, to the left of the green "play" arrow, you should see a dropdown. In that dropdown, select "Run Example Cordapp - Kotlin" and click the green "play" arrow.
|
3. Once the project is open, click ``File``, then ``Project Structure``. Under ``Project SDK:``, set the project SDK by
|
||||||
7. Wait until the run windows displays the message "Webserver started up in XX.X sec"
|
clicking ``New...``, clicking ``JDK``, and navigating to ``C:\\Program Files\\Java\\jdk1.8.0_XXX`` (where ``XXX`` is
|
||||||
8. Test the CorDapp is running correctly by visiting the front end at http://localhost:10007/web/example/
|
the latest minor version number). Click "OK"
|
||||||
|
4. Again under ``File`` then ``Project Structure``, select ``Modules``. Click ``+``, then ``Import Module``, then select
|
||||||
|
the ``cordapp-example`` folder and click ``Open``. Choose to ``Import module from external model``, select
|
||||||
|
``Gradle``, click ``Next`` then ``Finish`` (leaving the defaults) and ``OK``
|
||||||
|
5. Wait for the indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing
|
||||||
|
is complete)
|
||||||
|
6. At the top-right of the screen, to the left of the green ``play`` arrow, you should see a dropdown. In that
|
||||||
|
dropdown, select ``Run Example Cordapp - Kotlin`` and click the green ``play`` arrow.
|
||||||
|
7. Wait until the run windows displays the message ``Webserver started up in XX.X sec``
|
||||||
|
8. Test the CorDapp is running correctly by visiting the front end at `http://localhost:10007/web/example/
|
||||||
|
|
||||||
Corda source code
|
Corda source code
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -54,7 +54,7 @@ enum class TransactionIsolationLevel {
|
|||||||
val jdbcValue: Int = java.sql.Connection::class.java.getField(jdbcString).get(null) as Int
|
val jdbcValue: Int = java.sql.Connection::class.java.getField(jdbcString).get(null) as Int
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _contextDatabase = ThreadLocal<CordaPersistence>()
|
private val _contextDatabase = InheritableThreadLocal<CordaPersistence>()
|
||||||
var contextDatabase: CordaPersistence
|
var contextDatabase: CordaPersistence
|
||||||
get() = _contextDatabase.get() ?: error("Was expecting to find CordaPersistence set on current thread: ${Strand.currentStrand()}")
|
get() = _contextDatabase.get() ?: error("Was expecting to find CordaPersistence set on current thread: ${Strand.currentStrand()}")
|
||||||
set(database) = _contextDatabase.set(database)
|
set(database) = _contextDatabase.set(database)
|
||||||
|
@ -16,7 +16,11 @@ import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
|
|||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.CompositeKey
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.TransactionSignature
|
||||||
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.flows.NotaryError
|
import net.corda.core.flows.NotaryError
|
||||||
import net.corda.core.flows.NotaryException
|
import net.corda.core.flows.NotaryException
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
@ -50,12 +54,26 @@ import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
|||||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||||
import net.corda.testing.node.internal.startFlow
|
import net.corda.testing.node.internal.startFlow
|
||||||
import org.hamcrest.Matchers.instanceOf
|
import org.hamcrest.Matchers.instanceOf
|
||||||
import org.junit.*
|
import org.junit.AfterClass
|
||||||
import org.junit.Assert.assertThat
|
import org.junit.Assert.assertThat
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.distinct
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.last
|
||||||
|
import kotlin.collections.listOf
|
||||||
|
import kotlin.collections.map
|
||||||
|
import kotlin.collections.mapIndexedNotNull
|
||||||
|
import kotlin.collections.plus
|
||||||
|
import kotlin.collections.single
|
||||||
|
import kotlin.collections.zip
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -127,9 +145,7 @@ class BFTNotaryServiceTests {
|
|||||||
val issueTx = signInitialTransaction(notary) {
|
val issueTx = signInitialTransaction(notary) {
|
||||||
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
|
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
|
||||||
}
|
}
|
||||||
database.transaction {
|
services.recordTransactions(issueTx)
|
||||||
services.recordTransactions(issueTx)
|
|
||||||
}
|
|
||||||
val spendTxs = (1..10).map {
|
val spendTxs = (1..10).map {
|
||||||
signInitialTransaction(notary) {
|
signInitialTransaction(notary) {
|
||||||
addInputState(issueTx.tx.outRef<ContractState>(0))
|
addInputState(issueTx.tx.outRef<ContractState>(0))
|
||||||
@ -171,9 +187,7 @@ class BFTNotaryServiceTests {
|
|||||||
val issueTx = signInitialTransaction(notary) {
|
val issueTx = signInitialTransaction(notary) {
|
||||||
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
|
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
|
||||||
}
|
}
|
||||||
database.transaction {
|
services.recordTransactions(issueTx)
|
||||||
services.recordTransactions(issueTx)
|
|
||||||
}
|
|
||||||
val spendTx = signInitialTransaction(notary) {
|
val spendTx = signInitialTransaction(notary) {
|
||||||
addInputState(issueTx.tx.outRef<ContractState>(0))
|
addInputState(issueTx.tx.outRef<ContractState>(0))
|
||||||
setTimeWindow(TimeWindow.fromOnly(Instant.MAX))
|
setTimeWindow(TimeWindow.fromOnly(Instant.MAX))
|
||||||
@ -188,7 +202,7 @@ class BFTNotaryServiceTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `notarise issue tx with time-window`() {
|
fun `notarise issue tx with time-window`() {
|
||||||
node.run {
|
node.run {
|
||||||
val issueTx = signInitialTransaction(notary) {
|
val issueTx = signInitialTransaction(notary) {
|
||||||
@ -209,9 +223,7 @@ class BFTNotaryServiceTests {
|
|||||||
val issueTx = signInitialTransaction(notary) {
|
val issueTx = signInitialTransaction(notary) {
|
||||||
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
|
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
|
||||||
}
|
}
|
||||||
database.transaction {
|
services.recordTransactions(issueTx)
|
||||||
services.recordTransactions(issueTx)
|
|
||||||
}
|
|
||||||
val spendTx = signInitialTransaction(notary) {
|
val spendTx = signInitialTransaction(notary) {
|
||||||
addInputState(issueTx.tx.outRef<ContractState>(0))
|
addInputState(issueTx.tx.outRef<ContractState>(0))
|
||||||
setTimeWindow(TimeWindow.untilOnly(Instant.now() + Duration.ofHours(1)))
|
setTimeWindow(TimeWindow.untilOnly(Instant.now() + Duration.ofHours(1)))
|
||||||
|
@ -21,10 +21,10 @@ import net.corda.core.internal.concurrent.map
|
|||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
|
||||||
import net.corda.testing.core.singleIdentity
|
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
import net.corda.testing.core.dummyCommand
|
import net.corda.testing.core.dummyCommand
|
||||||
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.driver.internal.InProcessImpl
|
import net.corda.testing.driver.internal.InProcessImpl
|
||||||
@ -89,22 +89,17 @@ class RaftNotaryServiceTests : IntegrationTest() {
|
|||||||
notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3)))
|
notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3)))
|
||||||
)) {
|
)) {
|
||||||
val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcessImpl) }.getOrThrow()
|
val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcessImpl) }.getOrThrow()
|
||||||
val issueTx = bankA.database.transaction {
|
val builder = DummyContract.generateInitial(Random().nextInt(), defaultNotaryIdentity, bankA.services.myInfo.singleIdentity().ref(0))
|
||||||
val builder = DummyContract.generateInitial(Random().nextInt(), defaultNotaryIdentity, bankA.services.myInfo.singleIdentity().ref(0))
|
.setTimeWindow(bankA.services.clock.instant(), 30.seconds)
|
||||||
.setTimeWindow(bankA.services.clock.instant(), 30.seconds)
|
val issueTx = bankA.services.signInitialTransaction(builder)
|
||||||
bankA.services.signInitialTransaction(builder)
|
|
||||||
}
|
|
||||||
bankA.startFlow(NotaryFlow.Client(issueTx)).getOrThrow()
|
bankA.startFlow(NotaryFlow.Client(issueTx)).getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun issueState(nodeHandle: InProcessImpl, notary: Party): StateAndRef<*> {
|
private fun issueState(nodeHandle: InProcessImpl, notary: Party): StateAndRef<*> {
|
||||||
return nodeHandle.database.transaction {
|
val builder = DummyContract.generateInitial(Random().nextInt(), notary, nodeHandle.services.myInfo.singleIdentity().ref(0))
|
||||||
|
val stx = nodeHandle.services.signInitialTransaction(builder)
|
||||||
val builder = DummyContract.generateInitial(Random().nextInt(), notary, nodeHandle.services.myInfo.singleIdentity().ref(0))
|
nodeHandle.services.recordTransactions(stx)
|
||||||
val stx = nodeHandle.services.signInitialTransaction(builder)
|
return StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
|
||||||
nodeHandle.services.recordTransactions(stx)
|
|
||||||
StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,13 @@ import net.corda.node.internal.Node
|
|||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.testing.internal.IntegrationTestSchemas
|
import net.corda.testing.internal.IntegrationTestSchemas
|
||||||
import net.corda.testing.internal.toDatabaseSchemaName
|
import net.corda.testing.internal.toDatabaseSchemaName
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.ALICE_NAME
|
||||||
|
import net.corda.testing.core.BOB_NAME
|
||||||
|
import net.corda.testing.core.CHARLIE_NAME
|
||||||
|
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||||
|
import net.corda.testing.core.TestIdentity
|
||||||
|
import net.corda.testing.core.getTestPartyAndCertificate
|
||||||
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.node.internal.NodeBasedTest
|
import net.corda.testing.node.internal.NodeBasedTest
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||||
@ -58,12 +64,10 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
|
|||||||
fun `unknown legal name`() {
|
fun `unknown legal name`() {
|
||||||
val alice = startNodesWithPort(listOf(ALICE))[0]
|
val alice = startNodesWithPort(listOf(ALICE))[0]
|
||||||
val netMapCache = alice.services.networkMapCache
|
val netMapCache = alice.services.networkMapCache
|
||||||
alice.database.transaction {
|
assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).isEmpty()
|
||||||
assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).isEmpty()
|
assertThat(netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME)).isNull()
|
||||||
assertThat(netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME)).isNull()
|
assertThat(netMapCache.getPeerByLegalName(DUMMY_NOTARY_NAME)).isNull()
|
||||||
assertThat(netMapCache.getPeerByLegalName(DUMMY_NOTARY_NAME)).isNull()
|
assertThat(netMapCache.getPeerCertificateByLegalName(DUMMY_NOTARY_NAME)).isNull()
|
||||||
assertThat(netMapCache.getPeerCertificateByLegalName(DUMMY_NOTARY_NAME)).isNull()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -71,48 +75,40 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
|
|||||||
val alice = startNodesWithPort(listOf(ALICE))[0]
|
val alice = startNodesWithPort(listOf(ALICE))[0]
|
||||||
val netMapCache = alice.services.networkMapCache
|
val netMapCache = alice.services.networkMapCache
|
||||||
|
|
||||||
val distServiceNodeInfos = alice.database.transaction {
|
val distributedIdentity = TestIdentity(DUMMY_NOTARY_NAME).identity
|
||||||
val distributedIdentity = TestIdentity(DUMMY_NOTARY_NAME).identity
|
val distServiceNodeInfos = (1..2).map {
|
||||||
(1..2).map {
|
val nodeInfo = NodeInfo(
|
||||||
val nodeInfo = NodeInfo(
|
addresses = listOf(NetworkHostAndPort("localhost", 1000 + it)),
|
||||||
addresses = listOf(NetworkHostAndPort("localhost", 1000 + it)),
|
legalIdentitiesAndCerts = listOf(TestIdentity.fresh("Org-$it").identity, distributedIdentity),
|
||||||
legalIdentitiesAndCerts = listOf(TestIdentity.fresh("Org-$it").identity, distributedIdentity),
|
platformVersion = 3,
|
||||||
platformVersion = 3,
|
serial = 1
|
||||||
serial = 1
|
)
|
||||||
)
|
netMapCache.addNode(nodeInfo)
|
||||||
netMapCache.addNode(nodeInfo)
|
nodeInfo
|
||||||
nodeInfo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alice.database.transaction {
|
assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).containsOnlyElementsOf(distServiceNodeInfos)
|
||||||
assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).containsOnlyElementsOf(distServiceNodeInfos)
|
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
||||||
assertThatExceptionOfType(IllegalArgumentException::class.java)
|
.isThrownBy { netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME) }
|
||||||
.isThrownBy { netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME) }
|
.withMessageContaining(DUMMY_NOTARY_NAME.toString())
|
||||||
.withMessageContaining(DUMMY_NOTARY_NAME.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get nodes by owning key and by name`() {
|
fun `get nodes by owning key and by name`() {
|
||||||
val alice = startNodesWithPort(listOf(ALICE))[0]
|
val alice = startNodesWithPort(listOf(ALICE))[0]
|
||||||
val netCache = alice.services.networkMapCache
|
val netCache = alice.services.networkMapCache
|
||||||
alice.database.transaction {
|
val res = netCache.getNodeByLegalIdentity(alice.info.singleIdentity())
|
||||||
val res = netCache.getNodeByLegalIdentity(alice.info.singleIdentity())
|
assertEquals(alice.info, res)
|
||||||
assertEquals(alice.info, res)
|
val res2 = netCache.getNodeByLegalName(DUMMY_REGULATOR.name)
|
||||||
val res2 = netCache.getNodeByLegalName(DUMMY_REGULATOR.name)
|
assertEquals(infos.singleOrNull { DUMMY_REGULATOR.name in it.legalIdentities.map { it.name } }, res2)
|
||||||
assertEquals(infos.singleOrNull { DUMMY_REGULATOR.name in it.legalIdentities.map { it.name } }, res2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get nodes by address`() {
|
fun `get nodes by address`() {
|
||||||
val alice = startNodesWithPort(listOf(ALICE))[0]
|
val alice = startNodesWithPort(listOf(ALICE))[0]
|
||||||
val netCache = alice.services.networkMapCache
|
val netCache = alice.services.networkMapCache
|
||||||
alice.database.transaction {
|
val res = netCache.getNodeByAddress(alice.info.addresses[0])
|
||||||
val res = netCache.getNodeByAddress(alice.info.addresses[0])
|
assertEquals(alice.info, res)
|
||||||
assertEquals(alice.info, res)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test has to be done as normal node not mock, because MockNodes don't have addresses.
|
// This test has to be done as normal node not mock, because MockNodes don't have addresses.
|
||||||
@ -122,9 +118,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() {
|
|||||||
val charliePartyCert = getTestPartyAndCertificate(CHARLIE_NAME, generateKeyPair().public)
|
val charliePartyCert = getTestPartyAndCertificate(CHARLIE_NAME, generateKeyPair().public)
|
||||||
val aliceCache = aliceNode.services.networkMapCache
|
val aliceCache = aliceNode.services.networkMapCache
|
||||||
aliceCache.addNode(aliceNode.info.copy(legalIdentitiesAndCerts = listOf(charliePartyCert)))
|
aliceCache.addNode(aliceNode.info.copy(legalIdentitiesAndCerts = listOf(charliePartyCert)))
|
||||||
val res = aliceNode.database.transaction {
|
val res = aliceCache.allNodes.filter { aliceNode.info.addresses[0] in it.addresses }
|
||||||
aliceCache.allNodes.filter { aliceNode.info.addresses[0] in it.addresses }
|
|
||||||
}
|
|
||||||
assertEquals(2, res.size)
|
assertEquals(2, res.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import net.corda.core.flows.InitiatingFlow
|
|||||||
import net.corda.core.flows.NotaryChangeFlow
|
import net.corda.core.flows.NotaryChangeFlow
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.flows.NotaryFlow
|
||||||
import net.corda.core.flows.StartableByService
|
import net.corda.core.flows.StartableByService
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
@ -51,7 +52,6 @@ import net.corda.core.node.NetworkParameters
|
|||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.StatesToRecord
|
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
@ -61,7 +61,6 @@ import net.corda.core.serialization.SerializationWhitelist
|
|||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
@ -177,6 +176,8 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
||||||
@ -250,9 +251,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
@Volatile private var _started: StartedNode<AbstractNode>? = null
|
@Volatile private var _started: StartedNode<AbstractNode>? = null
|
||||||
|
|
||||||
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
||||||
open fun makeRPCOps(flowStarter: FlowStarter, database: CordaPersistence, smm: StateMachineManager): CordaRPCOps {
|
open fun makeRPCOps(flowStarter: FlowStarter, smm: StateMachineManager): CordaRPCOps {
|
||||||
|
|
||||||
val ops: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter, { shutdownExecutor.submit { stop() } })
|
val ops: CordaRPCOps = CordaRPCOpsImpl(services, smm, flowStarter, { shutdownExecutor.submit { stop() } })
|
||||||
val proxies = mutableListOf<(CordaRPCOps) -> CordaRPCOps>()
|
val proxies = mutableListOf<(CordaRPCOps) -> CordaRPCOps>()
|
||||||
// Mind that order is relevant here.
|
// Mind that order is relevant here.
|
||||||
proxies += ::AuthenticatedRpcOpsProxy
|
proxies += ::AuthenticatedRpcOpsProxy
|
||||||
@ -279,7 +280,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
initCertificate()
|
initCertificate()
|
||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)).use {
|
// Wrapped in an atomic reference just to allow setting it before the closure below gets invoked.
|
||||||
|
val identityServiceRef = AtomicReference<IdentityService>()
|
||||||
|
val database = initialiseDatabasePersistence(schemaService, { name -> identityServiceRef.get().wellKnownPartyFromX500Name(name) }, { party -> identityServiceRef.get().wellKnownPartyFromAnonymous(party) })
|
||||||
|
val identityService = makeIdentityService(identity.certificate, database)
|
||||||
|
identityServiceRef.set(identityService)
|
||||||
|
return database.use {
|
||||||
it.transaction {
|
it.transaction {
|
||||||
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks
|
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks
|
||||||
// like a design smell.
|
// like a design smell.
|
||||||
@ -302,8 +308,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
initialiseJVMAgents()
|
initialiseJVMAgents()
|
||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null)
|
||||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
val identityService = makeIdentityService(identity.certificate)
|
|
||||||
|
|
||||||
|
// Wrapped in an atomic reference just to allow setting it before the closure below gets invoked.
|
||||||
|
val identityServiceRef = AtomicReference<IdentityService>()
|
||||||
|
|
||||||
|
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||||
|
val database = initialiseDatabasePersistence(
|
||||||
|
schemaService,
|
||||||
|
{ name -> identityServiceRef.get().wellKnownPartyFromX500Name(name) },
|
||||||
|
{ party -> identityServiceRef.get().wellKnownPartyFromAnonymous(party) })
|
||||||
|
val identityService = makeIdentityService(identity.certificate, database).also(identityServiceRef::set)
|
||||||
networkMapClient = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, identityService.trustRoot) }
|
networkMapClient = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, identityService.trustRoot) }
|
||||||
val networkParameteresReader = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory)
|
val networkParameteresReader = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory)
|
||||||
val networkParameters = networkParameteresReader.networkParameters
|
val networkParameters = networkParameteresReader.networkParameters
|
||||||
@ -311,15 +325,14 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
"Node's platform version is lower than network's required minimumPlatformVersion"
|
"Node's platform version is lower than network's required minimumPlatformVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
val (startedImpl, schedulerService) = database.transaction {
|
||||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService).transaction {
|
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService, database)
|
||||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService)
|
|
||||||
val (keyPairs, nodeInfo) = updateNodeInfo(networkMapCache, networkMapClient, identity, identityKeyPair)
|
val (keyPairs, nodeInfo) = updateNodeInfo(networkMapCache, networkMapClient, identity, identityKeyPair)
|
||||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||||
val metrics = MetricRegistry()
|
val metrics = MetricRegistry()
|
||||||
val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes)
|
val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes)
|
||||||
log.debug("Transaction storage created")
|
log.debug("Transaction storage created")
|
||||||
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound, database)
|
||||||
log.debug("Attachment service created")
|
log.debug("Attachment service created")
|
||||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations)
|
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations)
|
||||||
log.debug("Cordapp provider created")
|
log.debug("Cordapp provider created")
|
||||||
@ -371,7 +384,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
makeVaultObservers(schedulerService, database.hibernateConfig, schemaService, flowLogicRefFactory)
|
makeVaultObservers(schedulerService, database.hibernateConfig, schemaService, flowLogicRefFactory)
|
||||||
val rpcOps = makeRPCOps(flowStarter, database, smm)
|
val rpcOps = makeRPCOps(flowStarter, smm)
|
||||||
startMessagingService(rpcOps)
|
startMessagingService(rpcOps)
|
||||||
installCoreFlows()
|
installCoreFlows()
|
||||||
val cordaServices = installCordaServices(flowStarter)
|
val cordaServices = installCordaServices(flowStarter)
|
||||||
@ -735,7 +748,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
networkParameters: NetworkParameters): MutableList<Any> {
|
networkParameters: NetworkParameters): MutableList<Any> {
|
||||||
checkpointStorage = DBCheckpointStorage()
|
checkpointStorage = DBCheckpointStorage()
|
||||||
|
|
||||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
val keyManagementService = makeKeyManagementService(identityService, keyPairs, database)
|
||||||
_services = ServiceHubInternalImpl(
|
_services = ServiceHubInternalImpl(
|
||||||
identityService,
|
identityService,
|
||||||
keyManagementService,
|
keyManagementService,
|
||||||
@ -757,7 +770,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
services, cordappProvider, this)
|
services, cordappProvider, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage = DBTransactionStorage(transactionCacheSizeBytes)
|
protected open fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage = DBTransactionStorage(transactionCacheSizeBytes, database)
|
||||||
private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, schemaService: SchemaService, flowLogicRefFactory: FlowLogicRefFactory) {
|
private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, schemaService: SchemaService, flowLogicRefFactory: FlowLogicRefFactory) {
|
||||||
ScheduledActivityObserver.install(services.vaultService, schedulerService, flowLogicRefFactory)
|
ScheduledActivityObserver.install(services.vaultService, schedulerService, flowLogicRefFactory)
|
||||||
HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig, schemaService)
|
HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig, schemaService)
|
||||||
@ -798,7 +811,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
// Specific class so that MockNode can catch it.
|
// Specific class so that MockNode can catch it.
|
||||||
class DatabaseConfigurationException(msg: String) : CordaException(msg)
|
class DatabaseConfigurationException(msg: String) : CordaException(msg)
|
||||||
|
|
||||||
protected open fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService): CordaPersistence {
|
protected open fun initialiseDatabasePersistence(schemaService: SchemaService,
|
||||||
|
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
|
||||||
|
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?): CordaPersistence {
|
||||||
log.debug {
|
log.debug {
|
||||||
val driverClasses = DriverManager.getDrivers().asSequence().map { it.javaClass.name }
|
val driverClasses = DriverManager.getDrivers().asSequence().map { it.javaClass.name }
|
||||||
"Available JDBC drivers: $driverClasses"
|
"Available JDBC drivers: $driverClasses"
|
||||||
@ -806,7 +821,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
val props = configuration.dataSourceProperties
|
val props = configuration.dataSourceProperties
|
||||||
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
|
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
|
||||||
val database = configureDatabase(props, configuration.database, identityService, schemaService)
|
val database = configureDatabase(props, configuration.database, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService)
|
||||||
// Now log the vendor string as this will also cause a connection to be tested eagerly.
|
// Now log the vendor string as this will also cause a connection to be tested eagerly.
|
||||||
logVendorString(database, log)
|
logVendorString(database, log)
|
||||||
runOnStop += database::close
|
runOnStop += database::close
|
||||||
@ -832,8 +847,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>): KeyManagementService {
|
protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>, database: CordaPersistence): KeyManagementService {
|
||||||
return PersistentKeyManagementService(identityService, keyPairs)
|
return PersistentKeyManagementService(identityService, keyPairs, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
|
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
|
||||||
@ -864,10 +879,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService {
|
private fun makeIdentityService(identityCert: X509Certificate, database: CordaPersistence): PersistentIdentityService {
|
||||||
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
|
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||||
val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
|
val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
|
||||||
return PersistentIdentityService(trustRoot, listOf(identityCert, nodeCa))
|
return PersistentIdentityService(trustRoot, database, listOf(identityCert, nodeCa))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
|
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
|
||||||
@ -945,8 +960,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
|
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
|
||||||
protected open fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
protected open fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration, database: CordaPersistence): VaultServiceInternal {
|
||||||
return NodeVaultService(platformClock, keyManagementService, services, hibernateConfig)
|
return NodeVaultService(platformClock, keyManagementService, services, hibernateConfig, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load configured JVM agents */
|
/** Load configured JVM agents */
|
||||||
@ -983,10 +998,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
private val servicesForResolution: ServicesForResolution
|
private val servicesForResolution: ServicesForResolution
|
||||||
) : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution {
|
) : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution {
|
||||||
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
||||||
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
|
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database)
|
||||||
override val auditService = DummyAuditService()
|
override val auditService = DummyAuditService()
|
||||||
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
||||||
override val vaultService by lazy { makeVaultService(keyManagementService, servicesForResolution, database.hibernateConfig) }
|
override val vaultService by lazy { makeVaultService(keyManagementService, servicesForResolution, database.hibernateConfig, database) }
|
||||||
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
||||||
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
||||||
override val networkService: MessagingService get() = network
|
override val networkService: MessagingService get() = network
|
||||||
@ -1002,12 +1017,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
return flowFactories[initiatingFlowClass]
|
return flowFactories[initiatingFlowClass]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
|
||||||
database.transaction {
|
|
||||||
super.recordTransactions(statesToRecord, txs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun jdbcSession(): Connection = database.createSession()
|
override fun jdbcSession(): Connection = database.createSession()
|
||||||
|
|
||||||
// allows services to register handlers to be informed when the node stop method is called
|
// allows services to register handlers to be informed when the node stop method is called
|
||||||
@ -1077,15 +1086,16 @@ internal class NetworkMapCacheEmptyException : Exception()
|
|||||||
|
|
||||||
fun configureDatabase(hikariProperties: Properties,
|
fun configureDatabase(hikariProperties: Properties,
|
||||||
databaseConfig: DatabaseConfig,
|
databaseConfig: DatabaseConfig,
|
||||||
identityService: IdentityService,
|
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
|
||||||
|
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
|
||||||
schemaService: SchemaService = NodeSchemaService()): CordaPersistence {
|
schemaService: SchemaService = NodeSchemaService()): CordaPersistence {
|
||||||
// Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately
|
// Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately
|
||||||
// Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default
|
// Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default
|
||||||
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
||||||
// either Hibernate can be convinced to stop warning, use the descriptor by default, or something else.
|
// either Hibernate can be convinced to stop warning, use the descriptor by default, or something else.
|
||||||
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService))
|
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
|
||||||
val dataSource = DataSourceFactory.createDataSource(hikariProperties)
|
val dataSource = DataSourceFactory.createDataSource(hikariProperties)
|
||||||
val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(identityService))
|
val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
|
||||||
val jdbcUrl = hikariProperties.getProperty("dataSource.url", "")
|
val jdbcUrl = hikariProperties.getProperty("dataSource.url", "")
|
||||||
SchemaMigration(
|
SchemaMigration(
|
||||||
schemaService.schemaOptions.keys,
|
schemaService.schemaOptions.keys,
|
||||||
|
@ -27,12 +27,26 @@ import net.corda.core.internal.FlowStateMachine
|
|||||||
import net.corda.core.internal.RPC_UPLOADER
|
import net.corda.core.internal.RPC_UPLOADER
|
||||||
import net.corda.core.internal.STRUCTURAL_STEP_PREFIX
|
import net.corda.core.internal.STRUCTURAL_STEP_PREFIX
|
||||||
import net.corda.core.internal.sign
|
import net.corda.core.internal.sign
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import net.corda.core.messaging.DataFeed
|
||||||
|
import net.corda.core.messaging.FlowHandle
|
||||||
|
import net.corda.core.messaging.FlowHandleImpl
|
||||||
|
import net.corda.core.messaging.FlowProgressHandle
|
||||||
|
import net.corda.core.messaging.FlowProgressHandleImpl
|
||||||
|
import net.corda.core.messaging.ParametersUpdateInfo
|
||||||
|
import net.corda.core.messaging.RPCReturnsObservables
|
||||||
|
import net.corda.core.messaging.StateMachineInfo
|
||||||
|
import net.corda.core.messaging.StateMachineTransactionMapping
|
||||||
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.node.services.vault.*
|
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||||
|
import net.corda.core.node.services.vault.AttachmentSort
|
||||||
|
import net.corda.core.node.services.vault.PageSpecification
|
||||||
|
import net.corda.core.node.services.vault.QueryCriteria
|
||||||
|
import net.corda.core.node.services.vault.Sort
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
@ -42,7 +56,6 @@ import net.corda.node.services.messaging.context
|
|||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.nodeapi.exceptions.NonRpcFlowException
|
import net.corda.nodeapi.exceptions.NonRpcFlowException
|
||||||
import net.corda.nodeapi.exceptions.RejectedCommandException
|
import net.corda.nodeapi.exceptions.RejectedCommandException
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -55,7 +68,6 @@ import java.time.Instant
|
|||||||
internal class CordaRPCOpsImpl(
|
internal class CordaRPCOpsImpl(
|
||||||
private val services: ServiceHubInternal,
|
private val services: ServiceHubInternal,
|
||||||
private val smm: StateMachineManager,
|
private val smm: StateMachineManager,
|
||||||
private val database: CordaPersistence,
|
|
||||||
private val flowStarter: FlowStarter,
|
private val flowStarter: FlowStarter,
|
||||||
private val shutdownNode: () -> Unit
|
private val shutdownNode: () -> Unit
|
||||||
) : CordaRPCOps {
|
) : CordaRPCOps {
|
||||||
@ -78,18 +90,15 @@ internal class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> {
|
override fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> {
|
||||||
return database.transaction {
|
return services.networkMapCache.track()
|
||||||
services.networkMapCache.track()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria,
|
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria,
|
||||||
paging: PageSpecification,
|
paging: PageSpecification,
|
||||||
sorting: Sort,
|
sorting: Sort,
|
||||||
contractStateType: Class<out T>): Vault.Page<T> {
|
contractStateType: Class<out T>): Vault.Page<T> {
|
||||||
return database.transaction {
|
contractStateType.checkIsA<ContractState>()
|
||||||
services.vaultService._queryBy(criteria, paging, sorting, contractStateType)
|
return services.vaultService._queryBy(criteria, paging, sorting, contractStateType)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
@ -97,9 +106,8 @@ internal class CordaRPCOpsImpl(
|
|||||||
paging: PageSpecification,
|
paging: PageSpecification,
|
||||||
sorting: Sort,
|
sorting: Sort,
|
||||||
contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||||
return database.transaction {
|
contractStateType.checkIsA<ContractState>()
|
||||||
services.vaultService._trackBy(criteria, paging, sorting, contractStateType)
|
return services.vaultService._trackBy(criteria, paging, sorting, contractStateType)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("OverridingDeprecatedMember")
|
@Suppress("OverridingDeprecatedMember")
|
||||||
@ -111,9 +119,7 @@ internal class CordaRPCOpsImpl(
|
|||||||
|
|
||||||
@Suppress("OverridingDeprecatedMember")
|
@Suppress("OverridingDeprecatedMember")
|
||||||
override fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
override fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||||
return database.transaction {
|
return services.validatedTransactions.track()
|
||||||
services.validatedTransactions.track()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
|
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
|
||||||
@ -125,13 +131,11 @@ internal class CordaRPCOpsImpl(
|
|||||||
override fun killFlow(id: StateMachineRunId) = smm.killFlow(id)
|
override fun killFlow(id: StateMachineRunId) = smm.killFlow(id)
|
||||||
|
|
||||||
override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> {
|
override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> {
|
||||||
return database.transaction {
|
val (allStateMachines, changes) = smm.track()
|
||||||
val (allStateMachines, changes) = smm.track()
|
return DataFeed(
|
||||||
DataFeed(
|
allStateMachines.map { stateMachineInfoFromFlowLogic(it) },
|
||||||
allStateMachines.map { stateMachineInfoFromFlowLogic(it) },
|
changes.map { stateMachineUpdateFromStateMachineChange(it) }
|
||||||
changes.map { stateMachineUpdateFromStateMachineChange(it) }
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stateMachineRecordedTransactionMappingSnapshot(): List<StateMachineTransactionMapping> {
|
override fun stateMachineRecordedTransactionMappingSnapshot(): List<StateMachineTransactionMapping> {
|
||||||
@ -141,9 +145,7 @@ internal class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
|
override fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
|
||||||
return database.transaction {
|
return services.stateMachineRecordedTransactionMapping.track()
|
||||||
services.stateMachineRecordedTransactionMapping.track()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nodeInfo(): NodeInfo {
|
override fun nodeInfo(): NodeInfo {
|
||||||
@ -155,15 +157,11 @@ internal class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
|
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
|
||||||
return database.transaction {
|
services.vaultService.addNoteToTransaction(txnId, txnNote)
|
||||||
services.vaultService.addNoteToTransaction(txnId, txnNote)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String> {
|
override fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String> {
|
||||||
return database.transaction {
|
return services.vaultService.getTransactionNotes(txnId)
|
||||||
services.vaultService.getTransactionNotes(txnId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
|
override fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T> {
|
||||||
@ -191,38 +189,23 @@ internal class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun attachmentExists(id: SecureHash): Boolean {
|
override fun attachmentExists(id: SecureHash): Boolean {
|
||||||
// TODO: this operation should not require an explicit transaction
|
return services.attachments.openAttachment(id) != null
|
||||||
return database.transaction {
|
|
||||||
services.attachments.openAttachment(id) != null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openAttachment(id: SecureHash): InputStream {
|
override fun openAttachment(id: SecureHash): InputStream {
|
||||||
// TODO: this operation should not require an explicit transaction
|
return services.attachments.openAttachment(id)!!.open()
|
||||||
return database.transaction {
|
|
||||||
services.attachments.openAttachment(id)!!.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun uploadAttachment(jar: InputStream): SecureHash {
|
override fun uploadAttachment(jar: InputStream): SecureHash {
|
||||||
// TODO: this operation should not require an explicit transaction
|
return services.attachments.importAttachment(jar, RPC_UPLOADER, null)
|
||||||
return database.transaction {
|
|
||||||
services.attachments.importAttachment(jar, RPC_UPLOADER, null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader:String, filename:String): SecureHash {
|
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash {
|
||||||
// TODO: this operation should not require an explicit transaction
|
return services.attachments.importAttachment(jar, uploader, filename)
|
||||||
return database.transaction {
|
|
||||||
services.attachments.importAttachment(jar, uploader, filename)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
||||||
// TODO: this operation should not require an explicit transaction
|
return services.attachments.queryAttachments(query, sorting)
|
||||||
return database.transaction {
|
|
||||||
services.attachments.queryAttachments(query, sorting)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun currentNodeTime(): Instant = Instant.now(services.clock)
|
override fun currentNodeTime(): Instant = Instant.now(services.clock)
|
||||||
@ -230,43 +213,31 @@ internal class CordaRPCOpsImpl(
|
|||||||
override fun waitUntilNetworkReady(): CordaFuture<Void?> = services.networkMapCache.nodeReady
|
override fun waitUntilNetworkReady(): CordaFuture<Void?> = services.networkMapCache.nodeReady
|
||||||
|
|
||||||
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
||||||
return database.transaction {
|
return services.identityService.wellKnownPartyFromAnonymous(party)
|
||||||
services.identityService.wellKnownPartyFromAnonymous(party)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun partyFromKey(key: PublicKey): Party? {
|
override fun partyFromKey(key: PublicKey): Party? {
|
||||||
return database.transaction {
|
return services.identityService.partyFromKey(key)
|
||||||
services.identityService.partyFromKey(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? {
|
override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? {
|
||||||
return database.transaction {
|
return services.identityService.wellKnownPartyFromX500Name(x500Name)
|
||||||
services.identityService.wellKnownPartyFromX500Name(x500Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? = services.networkMapCache.getNotary(x500Name)
|
override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? = services.networkMapCache.getNotary(x500Name)
|
||||||
|
|
||||||
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
||||||
return database.transaction {
|
return services.identityService.partiesFromName(query, exactMatch)
|
||||||
services.identityService.partiesFromName(query, exactMatch)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? {
|
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? {
|
||||||
return database.transaction {
|
return services.networkMapCache.getNodeByLegalIdentity(party)
|
||||||
services.networkMapCache.getNodeByLegalIdentity(party)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registeredFlows(): List<String> = services.rpcFlows.map { it.name }.sorted()
|
override fun registeredFlows(): List<String> = services.rpcFlows.map { it.name }.sorted()
|
||||||
|
|
||||||
override fun clearNetworkMapCache() {
|
override fun clearNetworkMapCache() {
|
||||||
database.transaction {
|
services.networkMapCache.clearNetworkMapCache()
|
||||||
services.networkMapCache.clearNetworkMapCache()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
||||||
@ -325,14 +296,25 @@ internal class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun InvocationContext.toFlowInitiator(): FlowInitiator {
|
private fun InvocationContext.toFlowInitiator(): FlowInitiator {
|
||||||
|
|
||||||
val principal = origin.principal().name
|
val principal = origin.principal().name
|
||||||
return when (origin) {
|
return when (origin) {
|
||||||
is InvocationOrigin.RPC -> FlowInitiator.RPC(principal)
|
is InvocationOrigin.RPC -> FlowInitiator.RPC(principal)
|
||||||
is InvocationOrigin.Peer -> services.identityService.wellKnownPartyFromX500Name((origin as InvocationOrigin.Peer).party)?.let { FlowInitiator.Peer(it) } ?: throw IllegalStateException("Unknown peer with name ${(origin as InvocationOrigin.Peer).party}.")
|
is InvocationOrigin.Peer -> {
|
||||||
|
val wellKnownParty = services.identityService.wellKnownPartyFromX500Name((origin as InvocationOrigin.Peer).party)
|
||||||
|
wellKnownParty?.let { FlowInitiator.Peer(it) }
|
||||||
|
?: throw IllegalStateException("Unknown peer with name ${(origin as InvocationOrigin.Peer).party}.")
|
||||||
|
}
|
||||||
is InvocationOrigin.Service -> FlowInitiator.Service(principal)
|
is InvocationOrigin.Service -> FlowInitiator.Service(principal)
|
||||||
InvocationOrigin.Shell -> FlowInitiator.Shell
|
InvocationOrigin.Shell -> FlowInitiator.Shell
|
||||||
is InvocationOrigin.Scheduled -> FlowInitiator.Scheduled((origin as InvocationOrigin.Scheduled).scheduledState)
|
is InvocationOrigin.Scheduled -> FlowInitiator.Scheduled((origin as InvocationOrigin.Scheduled).scheduledState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC can be invoked from the shell where the type parameter of any [Class] parameter is lost, so we must
|
||||||
|
* explicitly check that the provided [Class] is the one we want.
|
||||||
|
*/
|
||||||
|
private inline fun <reified TARGET> Class<*>.checkIsA() {
|
||||||
|
require(TARGET::class.java.isAssignableFrom(this)) { "$name is not a ${TARGET::class.java.name}" }
|
||||||
|
}
|
||||||
}
|
}
|
@ -13,7 +13,9 @@ package net.corda.node.internal
|
|||||||
import com.codahale.metrics.JmxReporter
|
import com.codahale.metrics.JmxReporter
|
||||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.concurrent.thenMatch
|
import net.corda.core.internal.concurrent.thenMatch
|
||||||
@ -23,7 +25,6 @@ import net.corda.core.messaging.RPCOps
|
|||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.IdentityService
|
|
||||||
import net.corda.core.node.services.TransactionVerifierService
|
import net.corda.core.node.services.TransactionVerifierService
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
@ -343,7 +344,9 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
* This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details
|
* This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details
|
||||||
* on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url
|
* on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url
|
||||||
*/
|
*/
|
||||||
override fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService): CordaPersistence {
|
override fun initialiseDatabasePersistence(schemaService: SchemaService,
|
||||||
|
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
|
||||||
|
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?): CordaPersistence {
|
||||||
val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url")
|
val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url")
|
||||||
val h2Prefix = "jdbc:h2:file:"
|
val h2Prefix = "jdbc:h2:file:"
|
||||||
if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) {
|
if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) {
|
||||||
@ -363,7 +366,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
else if (databaseUrl != null) {
|
else if (databaseUrl != null) {
|
||||||
printBasicNodeInfo("Database connection url is", databaseUrl)
|
printBasicNodeInfo("Database connection url is", databaseUrl)
|
||||||
}
|
}
|
||||||
return super.initialiseDatabasePersistence(schemaService, identityService)
|
return super.initialiseDatabasePersistence(schemaService, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _startupComplete = openFuture<Unit>()
|
private val _startupComplete = openFuture<Unit>()
|
||||||
|
@ -34,6 +34,7 @@ import net.corda.node.services.network.NetworkMapUpdater
|
|||||||
import net.corda.node.services.statemachine.ExternalEvent
|
import net.corda.node.services.statemachine.ExternalEvent
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
import net.corda.nodeapi.internal.persistence.contextDatabase
|
||||||
|
|
||||||
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
|
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
|
||||||
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
|
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
|
||||||
@ -62,55 +63,58 @@ interface ServiceHubInternal : ServiceHub {
|
|||||||
fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>,
|
fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>,
|
||||||
validatedTransactions: WritableTransactionStorage,
|
validatedTransactions: WritableTransactionStorage,
|
||||||
stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage,
|
stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage,
|
||||||
vaultService: VaultServiceInternal) {
|
vaultService: VaultServiceInternal,
|
||||||
|
database: CordaPersistence) {
|
||||||
|
|
||||||
require(txs.any()) { "No transactions passed in for recording" }
|
database.transaction {
|
||||||
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }
|
require(txs.any()) { "No transactions passed in for recording" }
|
||||||
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
|
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }
|
||||||
if (stateMachineRunId != null) {
|
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
|
||||||
recordedTransactions.forEach {
|
if (stateMachineRunId != null) {
|
||||||
stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
|
recordedTransactions.forEach {
|
||||||
|
stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("Transactions recorded from outside of a state machine")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.warn("Transactions recorded from outside of a state machine")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statesToRecord != StatesToRecord.NONE) {
|
if (statesToRecord != StatesToRecord.NONE) {
|
||||||
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
|
// When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states
|
||||||
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
|
// that do not involve us and that we cannot sign for. This will break coin selection and thus a warning
|
||||||
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
|
// is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net).
|
||||||
//
|
//
|
||||||
// The reason for this is three-fold:
|
// The reason for this is three-fold:
|
||||||
//
|
//
|
||||||
// 1) We are putting in place the observer mode feature relatively quickly to meet specific customer
|
// 1) We are putting in place the observer mode feature relatively quickly to meet specific customer
|
||||||
// launch target dates.
|
// launch target dates.
|
||||||
//
|
//
|
||||||
// 2) The right design for vaults which mix observations and relevant states isn't entirely clear yet.
|
// 2) The right design for vaults which mix observations and relevant states isn't entirely clear yet.
|
||||||
//
|
//
|
||||||
// 3) If we get the design wrong it could create security problems and business confusions.
|
// 3) If we get the design wrong it could create security problems and business confusions.
|
||||||
//
|
//
|
||||||
// Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the
|
// Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the
|
||||||
// Bitcoin equivalent of observer nodes:
|
// Bitcoin equivalent of observer nodes:
|
||||||
//
|
//
|
||||||
// https://bitcoinj.github.io/working-with-the-wallet#watching-wallets
|
// https://bitcoinj.github.io/working-with-the-wallet#watching-wallets
|
||||||
//
|
//
|
||||||
// The ability to have a wallet containing both irrelevant and relevant states complicated everything quite
|
// The ability to have a wallet containing both irrelevant and relevant states complicated everything quite
|
||||||
// dramatically, even methods as basic as the getBalance() API which required additional modes to let you
|
// dramatically, even methods as basic as the getBalance() API which required additional modes to let you
|
||||||
// query "balance I can spend" vs "balance I am observing". In the end it might have been better to just
|
// query "balance I can spend" vs "balance I am observing". In the end it might have been better to just
|
||||||
// require the user to create an entirely separate wallet for observing with.
|
// require the user to create an entirely separate wallet for observing with.
|
||||||
//
|
//
|
||||||
// In Corda we don't support a single node having multiple vaults (at the time of writing), and it's not
|
// In Corda we don't support a single node having multiple vaults (at the time of writing), and it's not
|
||||||
// clear that's the right way to go: perhaps adding an "origin" column to the VAULT_STATES table is a better
|
// clear that's the right way to go: perhaps adding an "origin" column to the VAULT_STATES table is a better
|
||||||
// solution. Then you could select subsets of states depending on where the report came from.
|
// solution. Then you could select subsets of states depending on where the report came from.
|
||||||
//
|
//
|
||||||
// The risk of doing this is that apps/developers may use 'canned SQL queries' not written by us that forget
|
// The risk of doing this is that apps/developers may use 'canned SQL queries' not written by us that forget
|
||||||
// to add a WHERE clause for the origin column. Those queries will seem to work most of the time until
|
// to add a WHERE clause for the origin column. Those queries will seem to work most of the time until
|
||||||
// they're run on an observer node and mix in irrelevant data. In the worst case this may result in
|
// they're run on an observer node and mix in irrelevant data. In the worst case this may result in
|
||||||
// erroneous data being reported to the user, which could cause security problems.
|
// erroneous data being reported to the user, which could cause security problems.
|
||||||
//
|
//
|
||||||
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
|
// Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely
|
||||||
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
|
// to make writes to the ledger very often or at all, we choose to punt this issue for the time being.
|
||||||
vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction })
|
vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,7 +139,7 @@ interface ServiceHubInternal : ServiceHub {
|
|||||||
val networkMapUpdater: NetworkMapUpdater
|
val networkMapUpdater: NetworkMapUpdater
|
||||||
override val cordappProvider: CordappProviderInternal
|
override val cordappProvider: CordappProviderInternal
|
||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||||
recordTransactions(statesToRecord, txs, validatedTransactions, stateMachineRecordedTransactionMapping, vaultService)
|
recordTransactions(statesToRecord, txs, validatedTransactions, stateMachineRecordedTransactionMapping, vaultService, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
|
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
|
||||||
|
@ -26,6 +26,7 @@ import net.corda.node.utilities.AppendOnlyPersistentMap
|
|||||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
@ -48,6 +49,7 @@ import javax.persistence.Lob
|
|||||||
// TODO There is duplicated logic between this and InMemoryIdentityService
|
// TODO There is duplicated logic between this and InMemoryIdentityService
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class PersistentIdentityService(override val trustRoot: X509Certificate,
|
class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||||
|
private val database: CordaPersistence,
|
||||||
caCertificates: List<X509Certificate> = emptyList()) : SingletonSerializeAsToken(), IdentityServiceInternal {
|
caCertificates: List<X509Certificate> = emptyList()) : SingletonSerializeAsToken(), IdentityServiceInternal {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -120,72 +122,82 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
|||||||
|
|
||||||
/** Requires a database transaction. */
|
/** Requires a database transaction. */
|
||||||
fun loadIdentities(identities: Iterable<PartyAndCertificate> = emptySet(), confidentialIdentities: Iterable<PartyAndCertificate> = emptySet()) {
|
fun loadIdentities(identities: Iterable<PartyAndCertificate> = emptySet(), confidentialIdentities: Iterable<PartyAndCertificate> = emptySet()) {
|
||||||
identities.forEach {
|
database.transaction {
|
||||||
val key = mapToKey(it)
|
identities.forEach {
|
||||||
keyToParties.addWithDuplicatesAllowed(key, it, false)
|
val key = mapToKey(it)
|
||||||
principalToParties.addWithDuplicatesAllowed(it.name, key, false)
|
keyToParties.addWithDuplicatesAllowed(key, it, false)
|
||||||
|
principalToParties.addWithDuplicatesAllowed(it.name, key, false)
|
||||||
|
}
|
||||||
|
confidentialIdentities.forEach {
|
||||||
|
principalToParties.addWithDuplicatesAllowed(it.name, mapToKey(it), false)
|
||||||
|
}
|
||||||
|
log.debug("Identities loaded")
|
||||||
}
|
}
|
||||||
confidentialIdentities.forEach {
|
|
||||||
principalToParties.addWithDuplicatesAllowed(it.name, mapToKey(it), false)
|
|
||||||
}
|
|
||||||
log.debug("Identities loaded")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||||
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||||
// Validate the chain first, before we do anything clever with it
|
return database.transaction {
|
||||||
val identityCertChain = identity.certPath.x509Certificates
|
|
||||||
try {
|
// Validate the chain first, before we do anything clever with it
|
||||||
identity.verify(trustAnchor)
|
val identityCertChain = identity.certPath.x509Certificates
|
||||||
} catch (e: CertPathValidatorException) {
|
try {
|
||||||
log.warn(e.localizedMessage)
|
identity.verify(trustAnchor)
|
||||||
log.warn("Path = ")
|
} catch (e: CertPathValidatorException) {
|
||||||
identityCertChain.reversed().forEach {
|
log.warn(e.localizedMessage)
|
||||||
log.warn(it.subjectX500Principal.toString())
|
log.warn("Path = ")
|
||||||
|
identityCertChain.reversed().forEach {
|
||||||
|
log.warn(it.subjectX500Principal.toString())
|
||||||
|
}
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we record the first identity of the same name, first
|
// Ensure we record the first identity of the same name, first
|
||||||
val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false }
|
val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false }
|
||||||
if (wellKnownCert != identity.certificate) {
|
if (wellKnownCert != identity.certificate) {
|
||||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug { "Registering identity $identity" }
|
log.debug { "Registering identity $identity" }
|
||||||
val key = mapToKey(identity)
|
val key = mapToKey(identity)
|
||||||
keyToParties.addWithDuplicatesAllowed(key, identity, false)
|
keyToParties.addWithDuplicatesAllowed(key, identity, false)
|
||||||
// Always keep the first party we registered, as that's the well known identity
|
// Always keep the first party we registered, as that's the well known identity
|
||||||
principalToParties.addWithDuplicatesAllowed(identity.name, key, false)
|
principalToParties.addWithDuplicatesAllowed(identity.name, key, false)
|
||||||
val parentId = mapToKey(identityCertChain[1].publicKey)
|
val parentId = mapToKey(identityCertChain[1].publicKey)
|
||||||
return keyToParties[parentId]
|
keyToParties[parentId]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[mapToKey(owningKey)]
|
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = database.transaction { keyToParties[mapToKey(owningKey)] }
|
||||||
|
|
||||||
private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? {
|
private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? {
|
||||||
val partyId = principalToParties[name]
|
return database.transaction {
|
||||||
return if (partyId != null) {
|
val partyId = principalToParties[name]
|
||||||
keyToParties[partyId]
|
if (partyId != null) {
|
||||||
} else null
|
keyToParties[partyId]
|
||||||
|
} else null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We give the caller a copy of the data set to avoid any locking problems
|
// We give the caller a copy of the data set to avoid any locking problems
|
||||||
override fun getAllIdentities(): Iterable<PartyAndCertificate> = keyToParties.allPersisted().map { it.second }.asIterable()
|
override fun getAllIdentities(): Iterable<PartyAndCertificate> = database.transaction { keyToParties.allPersisted().map { it.second }.asIterable() }
|
||||||
|
|
||||||
override fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party
|
override fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party
|
||||||
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party
|
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party
|
||||||
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? {
|
||||||
// The original version of this would return the party as-is if it was a Party (rather than AnonymousParty),
|
return database.transaction {
|
||||||
// however that means that we don't verify that we know who owns the key. As such as now enforce turning the key
|
// The original version of this would return the party as-is if it was a Party (rather than AnonymousParty),
|
||||||
// into a party, and from there figure out the well known party.
|
// however that means that we don't verify that we know who owns the key. As such as now enforce turning the key
|
||||||
val candidate = partyFromKey(party.owningKey)
|
// into a party, and from there figure out the well known party.
|
||||||
// TODO: This should be done via the network map cache, which is the authoritative source of well known identities
|
val candidate = partyFromKey(party.owningKey)
|
||||||
return if (candidate != null) {
|
// TODO: This should be done via the network map cache, which is the authoritative source of well known identities
|
||||||
wellKnownPartyFromX500Name(candidate.name)
|
if (candidate != null) {
|
||||||
} else {
|
wellKnownPartyFromX500Name(candidate.name)
|
||||||
null
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,20 +207,23 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
|
||||||
val results = LinkedHashSet<Party>()
|
return database.transaction {
|
||||||
for ((x500name, partyId) in principalToParties.allPersisted()) {
|
val results = LinkedHashSet<Party>()
|
||||||
partiesFromName(query, exactMatch, x500name, results, keyToParties[partyId]!!.party)
|
for ((x500name, partyId) in principalToParties.allPersisted()) {
|
||||||
|
partiesFromName(query, exactMatch, x500name, results, keyToParties[partyId]!!.party)
|
||||||
|
}
|
||||||
|
results
|
||||||
}
|
}
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(UnknownAnonymousPartyException::class)
|
@Throws(UnknownAnonymousPartyException::class)
|
||||||
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
|
||||||
val anonymousIdentity = certificateFromKey(anonymousParty.owningKey) ?:
|
database.transaction {
|
||||||
throw UnknownAnonymousPartyException("Unknown $anonymousParty")
|
val anonymousIdentity = certificateFromKey(anonymousParty.owningKey) ?: throw UnknownAnonymousPartyException("Unknown $anonymousParty")
|
||||||
val issuingCert = anonymousIdentity.certPath.certificates[1]
|
val issuingCert = anonymousIdentity.certPath.certificates[1]
|
||||||
require(issuingCert.publicKey == party.owningKey) {
|
require(issuingCert.publicKey == party.owningKey) {
|
||||||
"Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}."
|
"Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import net.corda.core.node.services.KeyManagementService
|
|||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
@ -37,7 +38,8 @@ import javax.persistence.Lob
|
|||||||
* This class needs database transactions to be in-flight during method calls and init.
|
* This class needs database transactions to be in-flight during method calls and init.
|
||||||
*/
|
*/
|
||||||
class PersistentKeyManagementService(val identityService: IdentityService,
|
class PersistentKeyManagementService(val identityService: IdentityService,
|
||||||
initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
initialKeys: Set<KeyPair>,
|
||||||
|
private val database: CordaPersistence) : SingletonSerializeAsToken(), KeyManagementService {
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
|
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
|
||||||
@ -76,17 +78,23 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
|||||||
val keysMap = createKeyMap()
|
val keysMap = createKeyMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initialKeys.forEach({ it -> keysMap.addWithDuplicatesAllowed(it.public, it.private) })
|
// TODO this should be in a start function, not in an init block.
|
||||||
|
database.transaction {
|
||||||
|
initialKeys.forEach({ it -> keysMap.addWithDuplicatesAllowed(it.public, it.private) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val keys: Set<PublicKey> get() = keysMap.allPersisted().map { it.first }.toSet()
|
override val keys: Set<PublicKey> get() = database.transaction { keysMap.allPersisted().map { it.first }.toSet() }
|
||||||
|
|
||||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> =
|
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = database.transaction {
|
||||||
candidateKeys.filter { keysMap[it] != null }
|
candidateKeys.filter { keysMap[it] != null }
|
||||||
|
}
|
||||||
|
|
||||||
override fun freshKey(): PublicKey {
|
override fun freshKey(): PublicKey {
|
||||||
val keyPair = generateKeyPair()
|
val keyPair = generateKeyPair()
|
||||||
keysMap[keyPair.public] = keyPair.private
|
database.transaction {
|
||||||
|
keysMap[keyPair.public] = keyPair.private
|
||||||
|
}
|
||||||
return keyPair.public
|
return keyPair.public
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +105,10 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
|||||||
|
|
||||||
//It looks for the PublicKey in the (potentially) CompositeKey that is ours, and then returns the associated PrivateKey to use in signing
|
//It looks for the PublicKey in the (potentially) CompositeKey that is ours, and then returns the associated PrivateKey to use in signing
|
||||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||||
val pk = publicKey.keys.first { keysMap[it] != null } //TODO here for us to re-write this using an actual query if publicKey.keys.size > 1
|
return database.transaction {
|
||||||
return KeyPair(pk, keysMap[pk]!!)
|
val pk = publicKey.keys.first { keysMap[it] != null } //TODO here for us to re-write this using an actual query if publicKey.keys.size > 1
|
||||||
|
KeyPair(pk, keysMap[pk]!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
||||||
|
@ -163,9 +163,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
fun sendMessage(address: String, message: ClientMessage) = producer!!.send(address, message)
|
fun sendMessage(address: String, message: ClientMessage) = producer!!.send(address, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val messagesToRedeliver = database.transaction {
|
private val messagesToRedeliver = createMessageToRedeliver()
|
||||||
createMessageToRedeliver()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val scheduledMessageRedeliveries = ConcurrentHashMap<Long, ScheduledFuture<*>>()
|
private val scheduledMessageRedeliveries = ConcurrentHashMap<Long, ScheduledFuture<*>>()
|
||||||
|
|
||||||
|
@ -48,8 +48,9 @@ import kotlin.collections.HashSet
|
|||||||
|
|
||||||
class NetworkMapCacheImpl(
|
class NetworkMapCacheImpl(
|
||||||
networkMapCacheBase: NetworkMapCacheBaseInternal,
|
networkMapCacheBase: NetworkMapCacheBaseInternal,
|
||||||
private val identityService: IdentityService
|
private val identityService: IdentityService,
|
||||||
) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal {
|
private val database: CordaPersistence
|
||||||
|
) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal, SingletonSerializeAsToken() {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<NetworkMapCacheImpl>()
|
private val logger = loggerFor<NetworkMapCacheImpl>()
|
||||||
}
|
}
|
||||||
@ -72,9 +73,11 @@ class NetworkMapCacheImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
|
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
|
||||||
val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party)
|
return database.transaction {
|
||||||
return wellKnownParty?.let {
|
val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party)
|
||||||
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
|
wellKnownParty?.let {
|
||||||
|
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +196,7 @@ open class PersistentNetworkMapCache(
|
|||||||
|
|
||||||
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
||||||
synchronized(_changed) {
|
synchronized(_changed) {
|
||||||
val allInfos = database.transaction { getAllInfos(session) }.map { it.toNodeInfo() }
|
val allInfos = database.transaction { getAllInfos(session).map { it.toNodeInfo() } }
|
||||||
return DataFeed(allInfos, _changed.bufferUntilSubscribed().wrapWithDatabaseTransaction())
|
return DataFeed(allInfos, _changed.bufferUntilSubscribed().wrapWithDatabaseTransaction())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,22 +12,23 @@ package net.corda.node.services.persistence
|
|||||||
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.services.IdentityService
|
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import org.hibernate.type.descriptor.WrapperOptions
|
import org.hibernate.type.descriptor.WrapperOptions
|
||||||
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor
|
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor
|
||||||
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan
|
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan
|
||||||
import org.hibernate.type.descriptor.java.MutabilityPlan
|
import org.hibernate.type.descriptor.java.MutabilityPlan
|
||||||
|
|
||||||
class AbstractPartyDescriptor(private val identityService: IdentityService) : AbstractTypeDescriptor<AbstractParty>(AbstractParty::class.java) {
|
class AbstractPartyDescriptor(private val wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
|
||||||
|
private val wellKnownPartyFromAnonymous: (AbstractParty) -> Party?) : AbstractTypeDescriptor<AbstractParty>(AbstractParty::class.java) {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromString(dbData: String?): AbstractParty? {
|
override fun fromString(dbData: String?): AbstractParty? {
|
||||||
return if (dbData != null) {
|
return if (dbData != null) {
|
||||||
val party = identityService.wellKnownPartyFromX500Name(CordaX500Name.parse(dbData))
|
val party = wellKnownPartyFromX500Name(CordaX500Name.parse(dbData))
|
||||||
if (party == null) log.warn("Identity service unable to resolve X500name: $dbData")
|
if (party == null) log.warn("Identity service unable to resolve X500name: $dbData")
|
||||||
party
|
party
|
||||||
} else {
|
} else {
|
||||||
@ -39,7 +40,7 @@ class AbstractPartyDescriptor(private val identityService: IdentityService) : Ab
|
|||||||
|
|
||||||
override fun toString(party: AbstractParty?): String? {
|
override fun toString(party: AbstractParty?): String? {
|
||||||
return if (party != null) {
|
return if (party != null) {
|
||||||
val partyName = party.nameOrNull() ?: identityService.wellKnownPartyFromAnonymous(party)?.name
|
val partyName = party.nameOrNull() ?: wellKnownPartyFromAnonymous(party)?.name
|
||||||
if (partyName == null) log.warn("Identity service unable to resolve AbstractParty: $party")
|
if (partyName == null) log.warn("Identity service unable to resolve AbstractParty: $party")
|
||||||
partyName.toString()
|
partyName.toString()
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,7 +12,7 @@ package net.corda.node.services.persistence
|
|||||||
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import javax.persistence.AttributeConverter
|
import javax.persistence.AttributeConverter
|
||||||
import javax.persistence.Converter
|
import javax.persistence.Converter
|
||||||
@ -22,14 +22,15 @@ import javax.persistence.Converter
|
|||||||
* Completely anonymous parties are stored as null (to preserve privacy).
|
* Completely anonymous parties are stored as null (to preserve privacy).
|
||||||
*/
|
*/
|
||||||
@Converter(autoApply = true)
|
@Converter(autoApply = true)
|
||||||
class AbstractPartyToX500NameAsStringConverter(private val identityService: IdentityService) : AttributeConverter<AbstractParty, String> {
|
class AbstractPartyToX500NameAsStringConverter(private val wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
|
||||||
|
private val wellKnownPartyFromAnonymous: (AbstractParty) -> Party?) : AttributeConverter<AbstractParty, String> {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun convertToDatabaseColumn(party: AbstractParty?): String? {
|
override fun convertToDatabaseColumn(party: AbstractParty?): String? {
|
||||||
if (party != null) {
|
if (party != null) {
|
||||||
val partyName = identityService.wellKnownPartyFromAnonymous(party)?.toString()
|
val partyName = wellKnownPartyFromAnonymous(party)?.toString()
|
||||||
if (partyName != null) return partyName
|
if (partyName != null) return partyName
|
||||||
log.warn("Identity service unable to resolve AbstractParty: $party")
|
log.warn("Identity service unable to resolve AbstractParty: $party")
|
||||||
}
|
}
|
||||||
@ -38,7 +39,7 @@ class AbstractPartyToX500NameAsStringConverter(private val identityService: Iden
|
|||||||
|
|
||||||
override fun convertToEntityAttribute(dbData: String?): AbstractParty? {
|
override fun convertToEntityAttribute(dbData: String?): AbstractParty? {
|
||||||
if (dbData != null) {
|
if (dbData != null) {
|
||||||
val party = identityService.wellKnownPartyFromX500Name(CordaX500Name.parse(dbData))
|
val party = wellKnownPartyFromX500Name(CordaX500Name.parse(dbData))
|
||||||
if (party != null) return party
|
if (party != null) return party
|
||||||
}
|
}
|
||||||
return null // non resolvable anonymous parties are stored as nulls
|
return null // non resolvable anonymous parties are stored as nulls
|
||||||
|
@ -18,6 +18,7 @@ import net.corda.core.messaging.DataFeed
|
|||||||
import net.corda.core.messaging.StateMachineTransactionMapping
|
import net.corda.core.messaging.StateMachineTransactionMapping
|
||||||
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
|
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||||
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
@ -36,7 +37,7 @@ import javax.persistence.Id
|
|||||||
* RPC API to correlate transaction creation with flows.
|
* RPC API to correlate transaction creation with flows.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class DBTransactionMappingStorage : StateMachineRecordedTransactionMappingStorage {
|
class DBTransactionMappingStorage(private val database: CordaPersistence) : StateMachineRecordedTransactionMappingStorage {
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}transaction_mappings")
|
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}transaction_mappings")
|
||||||
@ -73,16 +74,20 @@ class DBTransactionMappingStorage : StateMachineRecordedTransactionMappingStorag
|
|||||||
private val concurrentBox = ConcurrentBox(InnerState())
|
private val concurrentBox = ConcurrentBox(InnerState())
|
||||||
|
|
||||||
override fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash) {
|
override fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash) {
|
||||||
concurrentBox.concurrent {
|
database.transaction {
|
||||||
stateMachineTransactionMap.addWithDuplicatesAllowed(transactionId, stateMachineRunId)
|
concurrentBox.concurrent {
|
||||||
updates.bufferUntilDatabaseCommit().onNext(StateMachineTransactionMapping(stateMachineRunId, transactionId))
|
stateMachineTransactionMap.addWithDuplicatesAllowed(transactionId, stateMachineRunId)
|
||||||
|
updates.bufferUntilDatabaseCommit().onNext(StateMachineTransactionMapping(stateMachineRunId, transactionId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun track(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
|
override fun track(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
|
||||||
return concurrentBox.exclusive {
|
return database.transaction {
|
||||||
DataFeed(stateMachineTransactionMap.allPersisted().map { StateMachineTransactionMapping(it.second, it.first) }.toList(),
|
concurrentBox.exclusive {
|
||||||
updates.bufferUntilSubscribed().wrapWithDatabaseTransaction())
|
DataFeed(stateMachineTransactionMap.allPersisted().map { StateMachineTransactionMapping(it.second, it.first) }.toList(),
|
||||||
|
updates.bufferUntilSubscribed().wrapWithDatabaseTransaction())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,18 @@ import net.corda.core.internal.VisibleForTesting
|
|||||||
import net.corda.core.internal.bufferUntilSubscribed
|
import net.corda.core.internal.bufferUntilSubscribed
|
||||||
import net.corda.core.internal.concurrent.doneFuture
|
import net.corda.core.internal.concurrent.doneFuture
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.core.serialization.deserialize
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.CoreTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.node.services.api.WritableTransactionStorage
|
import net.corda.node.services.api.WritableTransactionStorage
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMapBase
|
import net.corda.node.utilities.AppendOnlyPersistentMapBase
|
||||||
import net.corda.node.utilities.WeightBasedAppendOnlyPersistentMap
|
import net.corda.node.utilities.WeightBasedAppendOnlyPersistentMap
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||||
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
@ -33,14 +38,19 @@ import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import javax.persistence.*
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.Lob
|
||||||
|
import javax.persistence.Table
|
||||||
|
|
||||||
// cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
// cache value type to just store the immutable bits of a signed transaction plus conversion helpers
|
||||||
typealias TxCacheValue = Pair<SerializedBytes<CoreTransaction>, List<TransactionSignature>>
|
typealias TxCacheValue = Pair<SerializedBytes<CoreTransaction>, List<TransactionSignature>>
|
||||||
|
|
||||||
fun TxCacheValue.toSignedTx() = SignedTransaction(this.first, this.second)
|
fun TxCacheValue.toSignedTx() = SignedTransaction(this.first, this.second)
|
||||||
fun SignedTransaction.toTxCacheValue() = TxCacheValue(this.txBits, this.sigs)
|
fun SignedTransaction.toTxCacheValue() = TxCacheValue(this.txBits, this.sigs)
|
||||||
|
|
||||||
class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, SingletonSerializeAsToken() {
|
class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPersistence) : WritableTransactionStorage, SingletonSerializeAsToken() {
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "${NODE_DATABASE_PREFIX}transactions")
|
@Table(name = "${NODE_DATABASE_PREFIX}transactions")
|
||||||
@ -93,35 +103,41 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S
|
|||||||
|
|
||||||
private val txStorage = ConcurrentBox(createTransactionsMap(cacheSizeBytes))
|
private val txStorage = ConcurrentBox(createTransactionsMap(cacheSizeBytes))
|
||||||
|
|
||||||
override fun addTransaction(transaction: SignedTransaction): Boolean =
|
override fun addTransaction(transaction: SignedTransaction): Boolean = database.transaction {
|
||||||
txStorage.concurrent {
|
txStorage.concurrent {
|
||||||
addWithDuplicatesAllowed(transaction.id, transaction.toTxCacheValue()).apply {
|
addWithDuplicatesAllowed(transaction.id, transaction.toTxCacheValue()).apply {
|
||||||
updatesPublisher.bufferUntilDatabaseCommit().onNext(transaction)
|
updatesPublisher.bufferUntilDatabaseCommit().onNext(transaction)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTransaction(id: SecureHash): SignedTransaction? = txStorage.content[id]?.toSignedTx()
|
override fun getTransaction(id: SecureHash): SignedTransaction? = database.transaction { txStorage.content[id]?.toSignedTx() }
|
||||||
|
|
||||||
private val updatesPublisher = PublishSubject.create<SignedTransaction>().toSerialized()
|
private val updatesPublisher = PublishSubject.create<SignedTransaction>().toSerialized()
|
||||||
override val updates: Observable<SignedTransaction> = updatesPublisher.wrapWithDatabaseTransaction()
|
override val updates: Observable<SignedTransaction> = updatesPublisher.wrapWithDatabaseTransaction()
|
||||||
|
|
||||||
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||||
return txStorage.exclusive {
|
return database.transaction {
|
||||||
DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updates.bufferUntilSubscribed())
|
txStorage.exclusive {
|
||||||
|
DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updates.bufferUntilSubscribed())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun trackTransaction(id: SecureHash): CordaFuture<SignedTransaction> {
|
override fun trackTransaction(id: SecureHash): CordaFuture<SignedTransaction> {
|
||||||
return txStorage.exclusive {
|
return database.transaction {
|
||||||
val existingTransaction = get(id)
|
txStorage.exclusive {
|
||||||
if (existingTransaction == null) {
|
val existingTransaction = get(id)
|
||||||
updates.filter { it.id == id }.toFuture()
|
if (existingTransaction == null) {
|
||||||
} else {
|
updates.filter { it.id == id }.toFuture()
|
||||||
doneFuture(existingTransaction.toSignedTx())
|
} else {
|
||||||
|
doneFuture(existingTransaction.toSignedTx())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val transactions: Iterable<SignedTransaction> get() = txStorage.content.allPersisted().map { it.second.toSignedTx() }.toList()
|
val transactions: Iterable<SignedTransaction>
|
||||||
|
get() = database.transaction { txStorage.content.allPersisted().map { it.second.toSignedTx() }.toList() }
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,18 @@ import net.corda.core.node.services.AttachmentId
|
|||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||||
import net.corda.core.node.services.vault.AttachmentSort
|
import net.corda.core.node.services.vault.AttachmentSort
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.SerializationToken
|
||||||
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
|
import net.corda.core.serialization.SerializeAsTokenContext
|
||||||
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
||||||
import net.corda.node.utilities.NonInvalidatingCache
|
import net.corda.node.utilities.NonInvalidatingCache
|
||||||
import net.corda.node.utilities.NonInvalidatingWeightBasedCache
|
import net.corda.node.utilities.NonInvalidatingWeightBasedCache
|
||||||
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
|
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import net.corda.nodeapi.internal.withContractsInJar
|
import net.corda.nodeapi.internal.withContractsInJar
|
||||||
@ -49,7 +54,16 @@ import java.time.Instant
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.jar.JarInputStream
|
import java.util.jar.JarInputStream
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import javax.persistence.*
|
import javax.persistence.CollectionTable
|
||||||
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.ElementCollection
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.ForeignKey
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.Index
|
||||||
|
import javax.persistence.JoinColumn
|
||||||
|
import javax.persistence.Lob
|
||||||
|
import javax.persistence.Table
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores attachments using Hibernate to database.
|
* Stores attachments using Hibernate to database.
|
||||||
@ -58,7 +72,8 @@ import javax.persistence.*
|
|||||||
class NodeAttachmentService(
|
class NodeAttachmentService(
|
||||||
metrics: MetricRegistry,
|
metrics: MetricRegistry,
|
||||||
attachmentContentCacheSize: Long = NodeConfiguration.defaultAttachmentContentCacheSize,
|
attachmentContentCacheSize: Long = NodeConfiguration.defaultAttachmentContentCacheSize,
|
||||||
attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound
|
attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
|
||||||
|
private val database: CordaPersistence
|
||||||
) : AttachmentStorage, SingletonSerializeAsToken(
|
) : AttachmentStorage, SingletonSerializeAsToken(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -117,13 +132,15 @@ class NodeAttachmentService(
|
|||||||
|
|
||||||
private val attachmentCount = metrics.counter("Attachments")
|
private val attachmentCount = metrics.counter("Attachments")
|
||||||
|
|
||||||
init {
|
fun start() {
|
||||||
val session = currentDBSession()
|
database.transaction {
|
||||||
val criteriaBuilder = session.criteriaBuilder
|
val session = currentDBSession()
|
||||||
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
|
val criteriaBuilder = session.criteriaBuilder
|
||||||
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java)))
|
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
|
||||||
val count = session.createQuery(criteriaQuery).singleResult
|
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java)))
|
||||||
attachmentCount.inc(count)
|
val count = session.createQuery(criteriaQuery).singleResult
|
||||||
|
attachmentCount.inc(count)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
@ -204,10 +221,8 @@ class NodeAttachmentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toToken(context: SerializeAsTokenContext) = Token(id, checkOnLoad)
|
override fun toToken(context: SerializeAsTokenContext) = Token(id, checkOnLoad)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// slightly complex 2 level approach to attachment caching:
|
// slightly complex 2 level approach to attachment caching:
|
||||||
// On the first level we cache attachment contents loaded from the DB by their key. This is a weight based
|
// On the first level we cache attachment contents loaded from the DB by their key. This is a weight based
|
||||||
// cache (we don't want to waste too much memory on this) and could be evicted quite aggressively. If we fail
|
// cache (we don't want to waste too much memory on this) and could be evicted quite aggressively. If we fail
|
||||||
@ -227,17 +242,18 @@ class NodeAttachmentService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun loadAttachmentContent(id: SecureHash): Pair<Attachment, ByteArray>? {
|
private fun loadAttachmentContent(id: SecureHash): Pair<Attachment, ByteArray>? {
|
||||||
val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString())
|
return database.transaction {
|
||||||
?: return null
|
val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) ?: return@transaction null
|
||||||
val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let {
|
val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let {
|
||||||
val contracts = attachment.contractClassNames
|
val contracts = attachment.contractClassNames
|
||||||
if (contracts != null && contracts.isNotEmpty()) {
|
if (contracts != null && contracts.isNotEmpty()) {
|
||||||
ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader)
|
ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader)
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Pair(attachmentImpl, attachment.content)
|
||||||
}
|
}
|
||||||
return Pair(attachmentImpl, attachment.content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val attachmentCache = NonInvalidatingCache<SecureHash, Optional<Attachment>>(
|
private val attachmentCache = NonInvalidatingCache<SecureHash, Optional<Attachment>>(
|
||||||
@ -273,32 +289,35 @@ class NodeAttachmentService(
|
|||||||
return import(jar, uploader, filename)
|
return import(jar, uploader, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasAttachment(attachmentId: AttachmentId): Boolean =
|
override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction {
|
||||||
currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
|
currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
|
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
|
||||||
private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId {
|
private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId {
|
||||||
return withContractsInJar(jar) { contractClassNames, inputStream ->
|
return database.transaction {
|
||||||
require(inputStream !is JarInputStream)
|
withContractsInJar(jar) { contractClassNames, inputStream ->
|
||||||
|
require(inputStream !is JarInputStream)
|
||||||
|
|
||||||
// Read the file into RAM and then calculate its hash. The attachment must fit into memory.
|
// Read the file into RAM and then calculate its hash. The attachment must fit into memory.
|
||||||
// TODO: Switch to a two-phase insert so we can handle attachments larger than RAM.
|
// TODO: Switch to a two-phase insert so we can handle attachments larger than RAM.
|
||||||
// To do this we must pipe stream into the database without knowing its hash, which we will learn only once
|
// To do this we must pipe stream into the database without knowing its hash, which we will learn only once
|
||||||
// the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not
|
// the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not
|
||||||
// set the hash field of the new attachment record.
|
// set the hash field of the new attachment record.
|
||||||
|
|
||||||
val bytes = inputStream.readFully()
|
val bytes = inputStream.readFully()
|
||||||
val id = bytes.sha256()
|
val id = bytes.sha256()
|
||||||
if (!hasAttachment(id)) {
|
if (!hasAttachment(id)) {
|
||||||
checkIsAValidJAR(bytes.inputStream())
|
checkIsAValidJAR(bytes.inputStream())
|
||||||
val session = currentDBSession()
|
val session = currentDBSession()
|
||||||
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames)
|
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames)
|
||||||
session.save(attachment)
|
session.save(attachment)
|
||||||
attachmentCount.inc()
|
attachmentCount.inc()
|
||||||
log.info("Stored new attachment $id")
|
log.info("Stored new attachment $id")
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
throw DuplicateAttachmentException(id.toString())
|
throw DuplicateAttachmentException(id.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,24 +331,23 @@ class NodeAttachmentService(
|
|||||||
|
|
||||||
override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
|
||||||
log.info("Attachment query criteria: $criteria, sorting: $sorting")
|
log.info("Attachment query criteria: $criteria, sorting: $sorting")
|
||||||
val session = currentDBSession()
|
return database.transaction {
|
||||||
val criteriaBuilder = session.criteriaBuilder
|
val session = currentDBSession()
|
||||||
|
val criteriaBuilder = session.criteriaBuilder
|
||||||
|
|
||||||
val criteriaQuery = criteriaBuilder.createQuery(DBAttachment::class.java)
|
val criteriaQuery = criteriaBuilder.createQuery(DBAttachment::class.java)
|
||||||
val root = criteriaQuery.from(DBAttachment::class.java)
|
val root = criteriaQuery.from(DBAttachment::class.java)
|
||||||
|
|
||||||
val criteriaParser = HibernateAttachmentQueryCriteriaParser(criteriaBuilder, criteriaQuery, root)
|
val criteriaParser = HibernateAttachmentQueryCriteriaParser(criteriaBuilder, criteriaQuery, root)
|
||||||
|
|
||||||
// parse criteria and build where predicates
|
// parse criteria and build where predicates
|
||||||
criteriaParser.parse(criteria, sorting)
|
criteriaParser.parse(criteria, sorting)
|
||||||
|
|
||||||
// prepare query for execution
|
// prepare query for execution
|
||||||
val query = session.createQuery(criteriaQuery)
|
val query = session.createQuery(criteriaQuery)
|
||||||
|
|
||||||
// execution
|
// execution
|
||||||
val results = query.resultList
|
query.resultList.map { AttachmentId.parse(it.attId) }
|
||||||
|
}
|
||||||
return results.map { AttachmentId.parse(it.attId) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ import net.corda.node.services.statemachine.transitions.StateMachine
|
|||||||
import net.corda.node.services.statemachine.transitions.StateMachineConfiguration
|
import net.corda.node.services.statemachine.transitions.StateMachineConfiguration
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
import net.corda.serialization.internal.SerializeAsTokenContextImpl
|
import net.corda.serialization.internal.SerializeAsTokenContextImpl
|
||||||
import net.corda.serialization.internal.withTokenContext
|
import net.corda.serialization.internal.withTokenContext
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
@ -185,7 +186,9 @@ class SingleThreadedStateMachineManager(
|
|||||||
*/
|
*/
|
||||||
override fun track(): DataFeed<List<FlowLogic<*>>, StateMachineManager.Change> {
|
override fun track(): DataFeed<List<FlowLogic<*>>, StateMachineManager.Change> {
|
||||||
return mutex.locked {
|
return mutex.locked {
|
||||||
DataFeed(flows.values.map { it.fiber.logic }, changesPublisher.bufferUntilSubscribed())
|
database.transaction {
|
||||||
|
DataFeed(flows.values.map { it.fiber.logic }, changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction(database))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +485,7 @@ class SingleThreadedStateMachineManager(
|
|||||||
throw SessionRejectException("${message.initiatorFlowClassName} is not a flow")
|
throw SessionRejectException("${message.initiatorFlowClassName} is not a flow")
|
||||||
}
|
}
|
||||||
return serviceHub.getFlowFactory(initiatingFlowClass) ?:
|
return serviceHub.getFlowFactory(initiatingFlowClass) ?:
|
||||||
throw SessionRejectException("$initiatingFlowClass is not registered")
|
throw SessionRejectException("$initiatingFlowClass is not registered")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <A> startInitiatedFlow(
|
private fun <A> startInitiatedFlow(
|
||||||
|
@ -58,7 +58,8 @@ class NodeVaultService(
|
|||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val keyManagementService: KeyManagementService,
|
private val keyManagementService: KeyManagementService,
|
||||||
private val servicesForResolution: ServicesForResolution,
|
private val servicesForResolution: ServicesForResolution,
|
||||||
hibernateConfig: HibernateConfiguration
|
hibernateConfig: HibernateConfiguration,
|
||||||
|
private val database: CordaPersistence
|
||||||
) : SingletonSerializeAsToken(), VaultServiceInternal {
|
) : SingletonSerializeAsToken(), VaultServiceInternal {
|
||||||
private companion object {
|
private companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
@ -235,19 +236,23 @@ class NodeVaultService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun addNoteToTransaction(txnId: SecureHash, noteText: String) {
|
override fun addNoteToTransaction(txnId: SecureHash, noteText: String) {
|
||||||
val txnNoteEntity = VaultSchemaV1.VaultTxnNote(txnId.toString(), noteText)
|
database.transaction {
|
||||||
currentDBSession().save(txnNoteEntity)
|
val txnNoteEntity = VaultSchemaV1.VaultTxnNote(txnId.toString(), noteText)
|
||||||
|
currentDBSession().save(txnNoteEntity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTransactionNotes(txnId: SecureHash): Iterable<String> {
|
override fun getTransactionNotes(txnId: SecureHash): Iterable<String> {
|
||||||
val session = currentDBSession()
|
return database.transaction {
|
||||||
val criteriaBuilder = session.criteriaBuilder
|
val session = currentDBSession()
|
||||||
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultTxnNote::class.java)
|
val criteriaBuilder = session.criteriaBuilder
|
||||||
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultTxnNote::class.java)
|
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultTxnNote::class.java)
|
||||||
val txIdPredicate = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultTxnNote::txId.name), txnId.toString())
|
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultTxnNote::class.java)
|
||||||
criteriaQuery.where(txIdPredicate)
|
val txIdPredicate = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultTxnNote::txId.name), txnId.toString())
|
||||||
val results = session.createQuery(criteriaQuery).resultList
|
criteriaQuery.where(txIdPredicate)
|
||||||
return results.asIterable().map { it.note }
|
val results = session.createQuery(criteriaQuery).resultList
|
||||||
|
results.asIterable().map { it.note }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(StatesNotAvailableException::class)
|
@Throws(StatesNotAvailableException::class)
|
||||||
@ -412,90 +417,94 @@ class NodeVaultService(
|
|||||||
|
|
||||||
@Throws(VaultQueryException::class)
|
@Throws(VaultQueryException::class)
|
||||||
private fun <T : ContractState> _queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>, skipPagingChecks: Boolean): Vault.Page<T> {
|
private fun <T : ContractState> _queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>, skipPagingChecks: Boolean): Vault.Page<T> {
|
||||||
log.debug {"Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting" }
|
log.debug { "Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting" }
|
||||||
// calculate total results where a page specification has been defined
|
return database.transaction {
|
||||||
var totalStates = -1L
|
// calculate total results where a page specification has been defined
|
||||||
if (!skipPagingChecks && !paging.isDefault) {
|
var totalStates = -1L
|
||||||
val count = builder { VaultSchemaV1.VaultStates::recordedTime.count() }
|
if (!skipPagingChecks && !paging.isDefault) {
|
||||||
val countCriteria = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.ALL)
|
val count = builder { VaultSchemaV1.VaultStates::recordedTime.count() }
|
||||||
val results = _queryBy(criteria.and(countCriteria), PageSpecification(), Sort(emptyList()), contractStateType, true) // only skip pagination checks for total results count query
|
val countCriteria = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.ALL)
|
||||||
totalStates = results.otherResults.last() as Long
|
val results = _queryBy(criteria.and(countCriteria), PageSpecification(), Sort(emptyList()), contractStateType, true) // only skip pagination checks for total results count query
|
||||||
}
|
totalStates = results.otherResults.last() as Long
|
||||||
|
}
|
||||||
|
|
||||||
val session = getSession()
|
val session = getSession()
|
||||||
|
|
||||||
val criteriaQuery = criteriaBuilder.createQuery(Tuple::class.java)
|
val criteriaQuery = criteriaBuilder.createQuery(Tuple::class.java)
|
||||||
val queryRootVaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
|
val queryRootVaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
|
||||||
|
|
||||||
// TODO: revisit (use single instance of parser for all queries)
|
// TODO: revisit (use single instance of parser for all queries)
|
||||||
val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates)
|
val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates)
|
||||||
|
|
||||||
// parse criteria and build where predicates
|
// parse criteria and build where predicates
|
||||||
criteriaParser.parse(criteria, sorting)
|
criteriaParser.parse(criteria, sorting)
|
||||||
|
|
||||||
// prepare query for execution
|
// prepare query for execution
|
||||||
val query = session.createQuery(criteriaQuery)
|
val query = session.createQuery(criteriaQuery)
|
||||||
|
|
||||||
// pagination checks
|
// pagination checks
|
||||||
if (!skipPagingChecks && !paging.isDefault) {
|
if (!skipPagingChecks && !paging.isDefault) {
|
||||||
// pagination
|
// pagination
|
||||||
if (paging.pageNumber < DEFAULT_PAGE_NUM) throw VaultQueryException("Page specification: invalid page number ${paging.pageNumber} [page numbers start from $DEFAULT_PAGE_NUM]")
|
if (paging.pageNumber < DEFAULT_PAGE_NUM) throw VaultQueryException("Page specification: invalid page number ${paging.pageNumber} [page numbers start from $DEFAULT_PAGE_NUM]")
|
||||||
if (paging.pageSize < 1) throw VaultQueryException("Page specification: invalid page size ${paging.pageSize} [must be a value between 1 and $MAX_PAGE_SIZE]")
|
if (paging.pageSize < 1) throw VaultQueryException("Page specification: invalid page size ${paging.pageSize} [must be a value between 1 and $MAX_PAGE_SIZE]")
|
||||||
}
|
}
|
||||||
|
|
||||||
query.firstResult = if (paging.pageNumber > 0) (paging.pageNumber - 1) * paging.pageSize else 0 //some DB don't allow a negative value in SELECT TOP query
|
query.firstResult = if (paging.pageNumber > 0) (paging.pageNumber - 1) * paging.pageSize else 0 //some DB don't allow a negative value in SELECT TOP query
|
||||||
query.maxResults = paging.pageSize + 1 // detection too many results
|
query.maxResults = paging.pageSize + 1 // detection too many results
|
||||||
|
|
||||||
// execution
|
// execution
|
||||||
val results = query.resultList
|
val results = query.resultList
|
||||||
|
|
||||||
// final pagination check (fail-fast on too many results when no pagination specified)
|
// final pagination check (fail-fast on too many results when no pagination specified)
|
||||||
if (!skipPagingChecks && paging.isDefault && results.size > DEFAULT_PAGE_SIZE)
|
if (!skipPagingChecks && paging.isDefault && results.size > DEFAULT_PAGE_SIZE)
|
||||||
throw VaultQueryException("Please specify a `PageSpecification` as there are more results [${results.size}] than the default page size [$DEFAULT_PAGE_SIZE]")
|
throw VaultQueryException("Please specify a `PageSpecification` as there are more results [${results.size}] than the default page size [$DEFAULT_PAGE_SIZE]")
|
||||||
|
|
||||||
val statesAndRefs: MutableList<StateAndRef<T>> = mutableListOf()
|
val statesAndRefs: MutableList<StateAndRef<T>> = mutableListOf()
|
||||||
val statesMeta: MutableList<Vault.StateMetadata> = mutableListOf()
|
val statesMeta: MutableList<Vault.StateMetadata> = mutableListOf()
|
||||||
val otherResults: MutableList<Any> = mutableListOf()
|
val otherResults: MutableList<Any> = mutableListOf()
|
||||||
val stateRefs = mutableSetOf<StateRef>()
|
val stateRefs = mutableSetOf<StateRef>()
|
||||||
|
|
||||||
results.asSequence()
|
results.asSequence()
|
||||||
.forEachIndexed { index, result ->
|
.forEachIndexed { index, result ->
|
||||||
if (result[0] is VaultSchemaV1.VaultStates) {
|
if (result[0] is VaultSchemaV1.VaultStates) {
|
||||||
if (!paging.isDefault && index == paging.pageSize) // skip last result if paged
|
if (!paging.isDefault && index == paging.pageSize) // skip last result if paged
|
||||||
return@forEachIndexed
|
return@forEachIndexed
|
||||||
val vaultState = result[0] as VaultSchemaV1.VaultStates
|
val vaultState = result[0] as VaultSchemaV1.VaultStates
|
||||||
val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!)
|
val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!)
|
||||||
stateRefs.add(stateRef)
|
stateRefs.add(stateRef)
|
||||||
statesMeta.add(Vault.StateMetadata(stateRef,
|
statesMeta.add(Vault.StateMetadata(stateRef,
|
||||||
vaultState.contractStateClassName,
|
vaultState.contractStateClassName,
|
||||||
vaultState.recordedTime,
|
vaultState.recordedTime,
|
||||||
vaultState.consumedTime,
|
vaultState.consumedTime,
|
||||||
vaultState.stateStatus,
|
vaultState.stateStatus,
|
||||||
vaultState.notary,
|
vaultState.notary,
|
||||||
vaultState.lockId,
|
vaultState.lockId,
|
||||||
vaultState.lockUpdateTime))
|
vaultState.lockUpdateTime))
|
||||||
} else {
|
} else {
|
||||||
// TODO: improve typing of returned other results
|
// TODO: improve typing of returned other results
|
||||||
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }
|
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }
|
||||||
otherResults.addAll(result.toArray().asList())
|
otherResults.addAll(result.toArray().asList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (stateRefs.isNotEmpty())
|
||||||
if (stateRefs.isNotEmpty())
|
statesAndRefs.addAll(uncheckedCast(servicesForResolution.loadStates(stateRefs)))
|
||||||
statesAndRefs.addAll(uncheckedCast(servicesForResolution.loadStates(stateRefs)))
|
|
||||||
|
|
||||||
return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults)
|
Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(VaultQueryException::class)
|
@Throws(VaultQueryException::class)
|
||||||
override fun <T : ContractState> _trackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
override fun <T : ContractState> _trackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
|
||||||
return concurrentBox.exclusive {
|
return database.transaction {
|
||||||
val snapshotResults = _queryBy(criteria, paging, sorting, contractStateType)
|
concurrentBox.exclusive {
|
||||||
val updates: Observable<Vault.Update<T>> = uncheckedCast(_updatesPublisher.bufferUntilSubscribed().filter { it.containsType(contractStateType, snapshotResults.stateTypes) })
|
val snapshotResults = _queryBy(criteria, paging, sorting, contractStateType)
|
||||||
DataFeed(snapshotResults, updates)
|
val updates: Observable<Vault.Update<T>> = uncheckedCast(_updatesPublisher.bufferUntilSubscribed().filter { it.containsType(contractStateType, snapshotResults.stateTypes) })
|
||||||
|
DataFeed(snapshotResults, updates)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSession() = contextDatabase.currentOrNew().session
|
private fun getSession() = database.currentOrNew().session
|
||||||
/**
|
/**
|
||||||
* Derive list from existing vault states and then incrementally update using vault observables
|
* Derive list from existing vault states and then incrementally update using vault observables
|
||||||
*/
|
*/
|
||||||
|
@ -24,11 +24,8 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.messaging.startFlow
|
|
||||||
import net.corda.core.messaging.vaultQueryBy
|
|
||||||
import net.corda.core.messaging.vaultTrackBy
|
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.node.services.queryBy
|
import net.corda.core.node.services.queryBy
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -58,8 +55,7 @@ import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
|||||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||||
import net.corda.testing.node.testActor
|
import net.corda.testing.node.testActor
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.*
|
||||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -115,9 +111,12 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash issue accepted`() {
|
fun `cash issue accepted`() {
|
||||||
|
withPermissions(
|
||||||
withPermissions(invokeRpc("vaultTrackBy"), invokeRpc("vaultQueryBy"), invokeRpc(CordaRPCOps::stateMachinesFeed), startFlow<CashIssueFlow>()) {
|
invokeRpc("vaultTrackBy"),
|
||||||
|
invokeRpc("vaultQueryBy"),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
|
startFlow<CashIssueFlow>()
|
||||||
|
) {
|
||||||
aliceNode.database.transaction {
|
aliceNode.database.transaction {
|
||||||
stateMachineUpdates = rpc.stateMachinesFeed().updates
|
stateMachineUpdates = rpc.stateMachinesFeed().updates
|
||||||
vaultTrackCash = rpc.vaultTrackBy<Cash.State>().updates
|
vaultTrackCash = rpc.vaultTrackBy<Cash.State>().updates
|
||||||
@ -168,7 +167,6 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `issue and move`() {
|
fun `issue and move`() {
|
||||||
|
|
||||||
withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed),
|
withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed),
|
invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed),
|
||||||
invokeRpc("vaultTrackBy"),
|
invokeRpc("vaultTrackBy"),
|
||||||
@ -278,9 +276,9 @@ class CordaRPCOpsImplTest {
|
|||||||
withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) {
|
withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) {
|
||||||
val inputJar1 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
val inputJar1 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
||||||
val inputJar2 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
val inputJar2 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar)
|
||||||
val secureHash1 = rpc.uploadAttachment(inputJar1)
|
rpc.uploadAttachment(inputJar1)
|
||||||
assertThatExceptionOfType(java.nio.file.FileAlreadyExistsException::class.java).isThrownBy {
|
assertThatExceptionOfType(java.nio.file.FileAlreadyExistsException::class.java).isThrownBy {
|
||||||
val secureHash2 = rpc.uploadAttachment(inputJar2)
|
rpc.uploadAttachment(inputJar2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,13 +309,14 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `kill a stuck flow through RPC`() {
|
fun `kill a stuck flow through RPC`() {
|
||||||
|
withPermissions(
|
||||||
withPermissions(startFlow<NewJoinerFlow>(), invokeRpc(CordaRPCOps::killFlow), invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::stateMachinesSnapshot)) {
|
startFlow<NewJoinerFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::killFlow),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesSnapshot)
|
||||||
|
) {
|
||||||
val flow = rpc.startFlow(::NewJoinerFlow)
|
val flow = rpc.startFlow(::NewJoinerFlow)
|
||||||
|
|
||||||
val killed = rpc.killFlow(flow.id)
|
val killed = rpc.killFlow(flow.id)
|
||||||
|
|
||||||
assertThat(killed).isTrue()
|
assertThat(killed).isTrue()
|
||||||
assertThat(rpc.stateMachinesSnapshot().map { info -> info.id }).doesNotContain(flow.id)
|
assertThat(rpc.stateMachinesSnapshot().map { info -> info.id }).doesNotContain(flow.id)
|
||||||
}
|
}
|
||||||
@ -325,13 +324,14 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `kill a waiting flow through RPC`() {
|
fun `kill a waiting flow through RPC`() {
|
||||||
|
withPermissions(
|
||||||
withPermissions(startFlow<HopefulFlow>(), invokeRpc(CordaRPCOps::killFlow), invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::stateMachinesSnapshot)) {
|
startFlow<HopefulFlow>(),
|
||||||
|
invokeRpc(CordaRPCOps::killFlow),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
|
invokeRpc(CordaRPCOps::stateMachinesSnapshot)
|
||||||
|
) {
|
||||||
val flow = rpc.startFlow(::HopefulFlow, alice)
|
val flow = rpc.startFlow(::HopefulFlow, alice)
|
||||||
|
|
||||||
val killed = rpc.killFlow(flow.id)
|
val killed = rpc.killFlow(flow.id)
|
||||||
|
|
||||||
assertThat(killed).isTrue()
|
assertThat(killed).isTrue()
|
||||||
assertThat(rpc.stateMachinesSnapshot().map { info -> info.id }).doesNotContain(flow.id)
|
assertThat(rpc.stateMachinesSnapshot().map { info -> info.id }).doesNotContain(flow.id)
|
||||||
}
|
}
|
||||||
@ -339,23 +339,26 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `kill a nonexistent flow through RPC`() {
|
fun `kill a nonexistent flow through RPC`() {
|
||||||
|
|
||||||
withPermissions(invokeRpc(CordaRPCOps::killFlow)) {
|
withPermissions(invokeRpc(CordaRPCOps::killFlow)) {
|
||||||
|
|
||||||
val nonexistentFlowId = StateMachineRunId.createRandom()
|
val nonexistentFlowId = StateMachineRunId.createRandom()
|
||||||
|
|
||||||
val killed = rpc.killFlow(nonexistentFlowId)
|
val killed = rpc.killFlow(nonexistentFlowId)
|
||||||
|
|
||||||
assertThat(killed).isFalse()
|
assertThat(killed).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `non-ContractState class for the contractStateType param in vault queries`() {
|
||||||
|
val nonContractStateClass: Class<out ContractState> = uncheckedCast(Cash::class.java)
|
||||||
|
withPermissions(invokeRpc("vaultTrack"), invokeRpc("vaultQuery")) {
|
||||||
|
assertThatThrownBy { rpc.vaultQuery(nonContractStateClass) }.hasMessageContaining(Cash::class.java.name)
|
||||||
|
assertThatThrownBy { rpc.vaultTrack(nonContractStateClass) }.hasMessageContaining(Cash::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class NewJoinerFlow : FlowLogic<String>() {
|
class NewJoinerFlow : FlowLogic<String>() {
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String {
|
override fun call(): String {
|
||||||
|
|
||||||
logger.info("When can I join you say? Almost there buddy...")
|
logger.info("When can I join you say? Almost there buddy...")
|
||||||
Fiber.currentFiber().join()
|
Fiber.currentFiber().join()
|
||||||
return "You'll never get me!"
|
return "You'll never get me!"
|
||||||
@ -364,13 +367,10 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class HopefulFlow(private val party: Party) : FlowLogic<String>() {
|
class HopefulFlow(private val party: Party) : FlowLogic<String>() {
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String {
|
override fun call(): String {
|
||||||
|
|
||||||
logger.info("Waiting for a miracle...")
|
logger.info("Waiting for a miracle...")
|
||||||
val miracle = initiateFlow(party).receive<String>().unwrap { it }
|
return initiateFlow(party).receive<String>().unwrap { it }
|
||||||
return miracle
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,17 +394,15 @@ class CordaRPCOpsImplTest {
|
|||||||
override fun call(): Void? = null
|
override fun call(): Void? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withPermissions(vararg permissions: String, action: () -> Unit) {
|
private inline fun withPermissions(vararg permissions: String, action: () -> Unit) {
|
||||||
|
|
||||||
val previous = CURRENT_RPC_CONTEXT.get()
|
val previous = CURRENT_RPC_CONTEXT.get()
|
||||||
try {
|
try {
|
||||||
CURRENT_RPC_CONTEXT.set(previous.copy(authorizer =
|
CURRENT_RPC_CONTEXT.set(previous.copy(authorizer = buildSubject(previous.principal, permissions.toSet())))
|
||||||
buildSubject(previous.principal, permissions.toSet())))
|
|
||||||
action.invoke()
|
action.invoke()
|
||||||
} finally {
|
} finally {
|
||||||
CURRENT_RPC_CONTEXT.set(previous)
|
CURRENT_RPC_CONTEXT.set(previous)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withoutAnyPermissions(action: () -> Unit) = withPermissions(action = action)
|
private inline fun withoutAnyPermissions(action: () -> Unit) = withPermissions(action = action)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class AbstractNodeTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `logVendorString does not leak connection`() {
|
fun `logVendorString does not leak connection`() {
|
||||||
// Note this test also covers a transaction that CordaPersistence does while it's instantiating:
|
// Note this test also covers a transaction that CordaPersistence does while it's instantiating:
|
||||||
val database = configureDatabase(hikariProperties(freshURL()), DatabaseConfig(), rigorousMock())
|
val database = configureDatabase(hikariProperties(freshURL()), DatabaseConfig(), { null }, { null })
|
||||||
val log = mock<Logger>() // Don't care what happens here.
|
val log = mock<Logger>() // Don't care what happens here.
|
||||||
// Actually 10 is enough to reproduce old code hang, as pool size is 10 and we leaked 9 connections and 1 is in flight:
|
// Actually 10 is enough to reproduce old code hang, as pool size is 10 and we leaked 9 connections and 1 is in flight:
|
||||||
repeat(100) {
|
repeat(100) {
|
||||||
|
@ -79,7 +79,7 @@ class NodeTest {
|
|||||||
doReturn("tsp").whenever(it).trustStorePassword
|
doReturn("tsp").whenever(it).trustStorePassword
|
||||||
doReturn("ksp").whenever(it).keyStorePassword
|
doReturn("ksp").whenever(it).keyStorePassword
|
||||||
}
|
}
|
||||||
configureDatabase(dataSourceProperties, databaseConfig, rigorousMock()).use { _ ->
|
configureDatabase(dataSourceProperties, databaseConfig, { null }, { null }).use { _ ->
|
||||||
val node = Node(configuration, info, initialiseSerialization = false)
|
val node = Node(configuration, info, initialiseSerialization = false)
|
||||||
assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial)
|
assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package net.corda.node.services
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.flows.FinalityFlow
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.packageName
|
||||||
|
import net.corda.core.node.services.queryBy
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.contracts.asset.Cash
|
||||||
|
import net.corda.finance.issuedBy
|
||||||
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
|
import net.corda.testing.node.internal.startFlow
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
class ServiceHubConcurrentUsageTest {
|
||||||
|
|
||||||
|
private val mockNet = InternalMockNetwork(listOf(Cash::class.packageName))
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun stopNodes() {
|
||||||
|
mockNet.stopNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `operations requiring a transaction work from another thread`() {
|
||||||
|
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
var successful = false
|
||||||
|
val initiatingFlow = TestFlow(mockNet.defaultNotaryIdentity)
|
||||||
|
val node = mockNet.createPartyNode()
|
||||||
|
|
||||||
|
node.services.validatedTransactions.updates.observeOn(Schedulers.io()).subscribe { _ ->
|
||||||
|
try {
|
||||||
|
node.services.vaultService.queryBy<ContractState>().states
|
||||||
|
successful = true
|
||||||
|
} finally {
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val flow = node.services.startFlow(initiatingFlow)
|
||||||
|
mockNet.runNetwork()
|
||||||
|
flow.resultFuture.getOrThrow()
|
||||||
|
latch.await()
|
||||||
|
assertThat(successful).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestFlow(private val notary: Party) : FlowLogic<SignedTransaction>() {
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): SignedTransaction {
|
||||||
|
|
||||||
|
val builder = TransactionBuilder(notary)
|
||||||
|
val issuer = ourIdentity.ref(OpaqueBytes.of(0))
|
||||||
|
Cash().generateIssue(builder, 10.DOLLARS.issuedBy(issuer), ourIdentity, notary)
|
||||||
|
val stx = serviceHub.signInitialTransaction(builder)
|
||||||
|
return subFlow(FinalityFlow(stx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -155,7 +155,7 @@ class MockScheduledFlowRepository : ScheduledFlowRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() {
|
class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() {
|
||||||
private val database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
private val database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null })
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun closeDatabase() {
|
fun closeDatabase() {
|
||||||
@ -306,7 +306,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test that correct item is returned`() {
|
fun `test that correct item is returned`() {
|
||||||
val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
||||||
val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock())
|
val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null })
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val repo = PersistentScheduledFlowRepository(database)
|
val repo = PersistentScheduledFlowRepository(database)
|
||||||
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
||||||
@ -325,7 +325,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() {
|
|||||||
val timeInTheFuture = mark + 1.days
|
val timeInTheFuture = mark + 1.days
|
||||||
val stateRef = StateRef(SecureHash.zeroHash, 0)
|
val stateRef = StateRef(SecureHash.zeroHash, 0)
|
||||||
|
|
||||||
configureDatabase(dataSourceProps, databaseConfig, rigorousMock()).use { database ->
|
configureDatabase(dataSourceProps, databaseConfig, { null }, { null }).use { database ->
|
||||||
val scheduler = database.transaction {
|
val scheduler = database.transaction {
|
||||||
createScheduler(database)
|
createScheduler(database)
|
||||||
}
|
}
|
||||||
@ -347,7 +347,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() {
|
|||||||
transactionStates[stateRef] = transactionStateMock(logicRef, timeInTheFuture)
|
transactionStates[stateRef] = transactionStateMock(logicRef, timeInTheFuture)
|
||||||
flows[logicRef] = flowLogic
|
flows[logicRef] = flowLogic
|
||||||
|
|
||||||
configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()).use { database ->
|
configureDatabase(dataSourceProps, DatabaseConfig(), { null }, { null }).use { database ->
|
||||||
val newScheduler = database.transaction {
|
val newScheduler = database.transaction {
|
||||||
createScheduler(database)
|
createScheduler(database)
|
||||||
}
|
}
|
||||||
@ -370,7 +370,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() {
|
|||||||
val logicRef = rigorousMock<FlowLogicRef>()
|
val logicRef = rigorousMock<FlowLogicRef>()
|
||||||
val flowLogic = rigorousMock<FlowLogic<*>>()
|
val flowLogic = rigorousMock<FlowLogic<*>>()
|
||||||
|
|
||||||
configureDatabase(dataSourceProps, databaseConfig, rigorousMock()).use { database ->
|
configureDatabase(dataSourceProps, databaseConfig, { null }, { null }).use { database ->
|
||||||
val scheduler = database.transaction {
|
val scheduler = database.transaction {
|
||||||
createScheduler(database)
|
createScheduler(database)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class PersistentScheduledFlowRepositoryTest {
|
|||||||
fun `test that earliest item is returned`() {
|
fun `test that earliest item is returned`() {
|
||||||
val laterTime = mark + 1.days
|
val laterTime = mark + 1.days
|
||||||
val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
||||||
val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock())
|
val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null })
|
||||||
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val repo = PersistentScheduledFlowRepository(database)
|
val repo = PersistentScheduledFlowRepository(database)
|
||||||
@ -43,7 +43,7 @@ class PersistentScheduledFlowRepositoryTest {
|
|||||||
fun `test that item is rescheduled`() {
|
fun `test that item is rescheduled`() {
|
||||||
val laterTime = mark + 1.days
|
val laterTime = mark + 1.days
|
||||||
val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
||||||
val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock())
|
val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null })
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val repo = PersistentScheduledFlowRepository(database)
|
val repo = PersistentScheduledFlowRepository(database)
|
||||||
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
|
||||||
|
@ -24,7 +24,11 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
|
|||||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.ALICE_NAME
|
||||||
|
import net.corda.testing.core.BOB_NAME
|
||||||
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
|
import net.corda.testing.core.TestIdentity
|
||||||
|
import net.corda.testing.core.getTestPartyAndCertificate
|
||||||
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
|
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
|
||||||
import net.corda.testing.internal.DEV_ROOT_CA
|
import net.corda.testing.internal.DEV_ROOT_CA
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
@ -33,6 +37,7 @@ import org.junit.After
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
@ -60,8 +65,12 @@ class PersistentIdentityServiceTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
identityService = PersistentIdentityService(DEV_ROOT_CA.certificate)
|
val identityServiceRef = AtomicReference<IdentityService>()
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), identityService)
|
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||||
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true),
|
||||||
|
{ name -> identityServiceRef.get().wellKnownPartyFromX500Name(name) },
|
||||||
|
{ party -> identityServiceRef.get().wellKnownPartyFromAnonymous(party) })
|
||||||
|
identityService = PersistentIdentityService(DEV_ROOT_CA.certificate, database).also(identityServiceRef::set)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -72,78 +81,55 @@ class PersistentIdentityServiceTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `get all identities`() {
|
fun `get all identities`() {
|
||||||
// Nothing registered, so empty set
|
// Nothing registered, so empty set
|
||||||
database.transaction {
|
assertNull(identityService.getAllIdentities().firstOrNull())
|
||||||
assertNull(identityService.getAllIdentities().firstOrNull())
|
|
||||||
}
|
|
||||||
|
|
||||||
database.transaction {
|
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
||||||
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
|
||||||
}
|
|
||||||
var expected = setOf(ALICE)
|
var expected = setOf(ALICE)
|
||||||
var actual = database.transaction {
|
var actual = identityService.getAllIdentities().map { it.party }.toHashSet()
|
||||||
identityService.getAllIdentities().map { it.party }.toHashSet()
|
|
||||||
}
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
|
|
||||||
// Add a second party and check we get both back
|
// Add a second party and check we get both back
|
||||||
database.transaction {
|
identityService.verifyAndRegisterIdentity(BOB_IDENTITY)
|
||||||
identityService.verifyAndRegisterIdentity(BOB_IDENTITY)
|
|
||||||
}
|
|
||||||
expected = setOf(ALICE, BOB)
|
expected = setOf(ALICE, BOB)
|
||||||
actual = database.transaction {
|
actual = identityService.getAllIdentities().map { it.party }.toHashSet()
|
||||||
identityService.getAllIdentities().map { it.party }.toHashSet()
|
|
||||||
}
|
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get identity by key`() {
|
fun `get identity by key`() {
|
||||||
database.transaction {
|
assertNull(identityService.partyFromKey(ALICE_PUBKEY))
|
||||||
assertNull(identityService.partyFromKey(ALICE_PUBKEY))
|
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
||||||
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
assertEquals(ALICE, identityService.partyFromKey(ALICE_PUBKEY))
|
||||||
assertEquals(ALICE, identityService.partyFromKey(ALICE_PUBKEY))
|
assertNull(identityService.partyFromKey(BOB_PUBKEY))
|
||||||
assertNull(identityService.partyFromKey(BOB_PUBKEY))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get identity by name with no registered identities`() {
|
fun `get identity by name with no registered identities`() {
|
||||||
database.transaction {
|
assertNull(identityService.wellKnownPartyFromX500Name(ALICE.name))
|
||||||
assertNull(identityService.wellKnownPartyFromX500Name(ALICE.name))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get identity by substring match`() {
|
fun `get identity by substring match`() {
|
||||||
database.transaction {
|
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
||||||
identityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
identityService.verifyAndRegisterIdentity(BOB_IDENTITY)
|
||||||
identityService.verifyAndRegisterIdentity(BOB_IDENTITY)
|
|
||||||
}
|
|
||||||
val alicente = getTestPartyAndCertificate(CordaX500Name(organisation = "Alicente Worldwide", locality = "London", country = "GB"), generateKeyPair().public)
|
val alicente = getTestPartyAndCertificate(CordaX500Name(organisation = "Alicente Worldwide", locality = "London", country = "GB"), generateKeyPair().public)
|
||||||
database.transaction {
|
identityService.verifyAndRegisterIdentity(alicente)
|
||||||
identityService.verifyAndRegisterIdentity(alicente)
|
assertEquals(setOf(ALICE, alicente.party), identityService.partiesFromName("Alice", false))
|
||||||
assertEquals(setOf(ALICE, alicente.party), identityService.partiesFromName("Alice", false))
|
assertEquals(setOf(ALICE), identityService.partiesFromName("Alice Corp", true))
|
||||||
assertEquals(setOf(ALICE), identityService.partiesFromName("Alice Corp", true))
|
assertEquals(setOf(BOB), identityService.partiesFromName("Bob Plc", true))
|
||||||
assertEquals(setOf(BOB), identityService.partiesFromName("Bob Plc", true))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get identity by name`() {
|
fun `get identity by name`() {
|
||||||
val identities = listOf("Organisation A", "Organisation B", "Organisation C")
|
val identities = listOf("Organisation A", "Organisation B", "Organisation C")
|
||||||
.map { getTestPartyAndCertificate(CordaX500Name(organisation = it, locality = "London", country = "GB"), generateKeyPair().public) }
|
.map { getTestPartyAndCertificate(CordaX500Name(organisation = it, locality = "London", country = "GB"), generateKeyPair().public) }
|
||||||
database.transaction {
|
assertNull(identityService.wellKnownPartyFromX500Name(identities.first().name))
|
||||||
assertNull(identityService.wellKnownPartyFromX500Name(identities.first().name))
|
identities.forEach {
|
||||||
|
identityService.verifyAndRegisterIdentity(it)
|
||||||
}
|
}
|
||||||
identities.forEach {
|
identities.forEach {
|
||||||
database.transaction {
|
assertEquals(it.party, identityService.wellKnownPartyFromX500Name(it.name))
|
||||||
identityService.verifyAndRegisterIdentity(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
identities.forEach {
|
|
||||||
database.transaction {
|
|
||||||
assertEquals(it.party, identityService.wellKnownPartyFromX500Name(it.name))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,9 +145,7 @@ class PersistentIdentityServiceTests {
|
|||||||
val txIdentity = AnonymousParty(txKey.public)
|
val txIdentity = AnonymousParty(txKey.public)
|
||||||
|
|
||||||
assertFailsWith<UnknownAnonymousPartyException> {
|
assertFailsWith<UnknownAnonymousPartyException> {
|
||||||
database.transaction {
|
identityService.assertOwnership(identity, txIdentity)
|
||||||
identityService.assertOwnership(identity, txIdentity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,25 +159,15 @@ class PersistentIdentityServiceTests {
|
|||||||
val (_, bobTxIdentity) = createConfidentialIdentity(ALICE.name)
|
val (_, bobTxIdentity) = createConfidentialIdentity(ALICE.name)
|
||||||
|
|
||||||
// Now we have identities, construct the service and let it know about both
|
// Now we have identities, construct the service and let it know about both
|
||||||
database.transaction {
|
identityService.verifyAndRegisterIdentity(alice)
|
||||||
identityService.verifyAndRegisterIdentity(alice)
|
identityService.verifyAndRegisterIdentity(aliceTxIdentity)
|
||||||
identityService.verifyAndRegisterIdentity(aliceTxIdentity)
|
|
||||||
}
|
|
||||||
|
|
||||||
var actual = database.transaction {
|
var actual = identityService.certificateFromKey(aliceTxIdentity.party.owningKey)
|
||||||
identityService.certificateFromKey(aliceTxIdentity.party.owningKey)
|
|
||||||
}
|
|
||||||
assertEquals(aliceTxIdentity, actual!!)
|
assertEquals(aliceTxIdentity, actual!!)
|
||||||
|
|
||||||
database.transaction {
|
assertNull(identityService.certificateFromKey(bobTxIdentity.party.owningKey))
|
||||||
assertNull(identityService.certificateFromKey(bobTxIdentity.party.owningKey))
|
identityService.verifyAndRegisterIdentity(bobTxIdentity)
|
||||||
}
|
actual = identityService.certificateFromKey(bobTxIdentity.party.owningKey)
|
||||||
database.transaction {
|
|
||||||
identityService.verifyAndRegisterIdentity(bobTxIdentity)
|
|
||||||
}
|
|
||||||
actual = database.transaction {
|
|
||||||
identityService.certificateFromKey(bobTxIdentity.party.owningKey)
|
|
||||||
}
|
|
||||||
assertEquals(bobTxIdentity, actual!!)
|
assertEquals(bobTxIdentity, actual!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,34 +180,24 @@ class PersistentIdentityServiceTests {
|
|||||||
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
|
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
|
||||||
val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
|
val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
|
||||||
|
|
||||||
database.transaction {
|
// Now we have identities, construct the service and let it know about both
|
||||||
// Now we have identities, construct the service and let it know about both
|
identityService.verifyAndRegisterIdentity(anonymousAlice)
|
||||||
identityService.verifyAndRegisterIdentity(anonymousAlice)
|
identityService.verifyAndRegisterIdentity(anonymousBob)
|
||||||
identityService.verifyAndRegisterIdentity(anonymousBob)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that paths are verified
|
// Verify that paths are verified
|
||||||
database.transaction {
|
identityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
||||||
identityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
identityService.assertOwnership(bob.party, anonymousBob.party.anonymise())
|
||||||
identityService.assertOwnership(bob.party, anonymousBob.party.anonymise())
|
assertFailsWith<IllegalArgumentException> {
|
||||||
|
identityService.assertOwnership(alice.party, anonymousBob.party.anonymise())
|
||||||
}
|
}
|
||||||
assertFailsWith<IllegalArgumentException> {
|
assertFailsWith<IllegalArgumentException> {
|
||||||
database.transaction {
|
identityService.assertOwnership(bob.party, anonymousAlice.party.anonymise())
|
||||||
identityService.assertOwnership(alice.party, anonymousBob.party.anonymise())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertFailsWith<IllegalArgumentException> {
|
|
||||||
database.transaction {
|
|
||||||
identityService.assertOwnership(bob.party, anonymousAlice.party.anonymise())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertFailsWith<IllegalArgumentException> {
|
assertFailsWith<IllegalArgumentException> {
|
||||||
val owningKey = DEV_INTERMEDIATE_CA.certificate.publicKey
|
val owningKey = DEV_INTERMEDIATE_CA.certificate.publicKey
|
||||||
database.transaction {
|
val subject = CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.subjectX500Principal)
|
||||||
val subject = CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.subjectX500Principal)
|
identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise())
|
||||||
identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,33 +206,24 @@ class PersistentIdentityServiceTests {
|
|||||||
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
|
val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name)
|
||||||
val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
|
val (bob, anonymousBob) = createConfidentialIdentity(BOB.name)
|
||||||
|
|
||||||
database.transaction {
|
// Register well known identities
|
||||||
// Register well known identities
|
identityService.verifyAndRegisterIdentity(alice)
|
||||||
identityService.verifyAndRegisterIdentity(alice)
|
identityService.verifyAndRegisterIdentity(bob)
|
||||||
identityService.verifyAndRegisterIdentity(bob)
|
// Register an anonymous identities
|
||||||
// Register an anonymous identities
|
identityService.verifyAndRegisterIdentity(anonymousAlice)
|
||||||
identityService.verifyAndRegisterIdentity(anonymousAlice)
|
identityService.verifyAndRegisterIdentity(anonymousBob)
|
||||||
identityService.verifyAndRegisterIdentity(anonymousBob)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new identity service mounted onto same DB
|
// Create new identity service mounted onto same DB
|
||||||
val newPersistentIdentityService = database.transaction {
|
val newPersistentIdentityService = PersistentIdentityService(DEV_ROOT_CA.certificate, database)
|
||||||
PersistentIdentityService(DEV_ROOT_CA.certificate)
|
|
||||||
}
|
|
||||||
|
|
||||||
database.transaction {
|
newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
||||||
newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
newPersistentIdentityService.assertOwnership(bob.party, anonymousBob.party.anonymise())
|
||||||
newPersistentIdentityService.assertOwnership(bob.party, anonymousBob.party.anonymise())
|
|
||||||
}
|
val aliceParent = newPersistentIdentityService.wellKnownPartyFromAnonymous(anonymousAlice.party.anonymise())
|
||||||
|
|
||||||
val aliceParent = database.transaction {
|
|
||||||
newPersistentIdentityService.wellKnownPartyFromAnonymous(anonymousAlice.party.anonymise())
|
|
||||||
}
|
|
||||||
assertEquals(alice.party, aliceParent!!)
|
assertEquals(alice.party, aliceParent!!)
|
||||||
|
|
||||||
val bobReload = database.transaction {
|
val bobReload = newPersistentIdentityService.certificateFromKey(anonymousBob.party.owningKey)
|
||||||
newPersistentIdentityService.certificateFromKey(anonymousBob.party.owningKey)
|
|
||||||
}
|
|
||||||
assertEquals(anonymousBob, bobReload!!)
|
assertEquals(anonymousBob, bobReload!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +87,8 @@ class ArtemisMessagingTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null })
|
||||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()).start(), rigorousMock())
|
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()).start(), rigorousMock(), database)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -6,7 +6,7 @@ import net.corda.node.internal.configureDatabase
|
|||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
@ -58,7 +58,7 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) {
|
|||||||
|
|
||||||
private val database = configureDatabase(makeTestDataSourceProperties(),
|
private val database = configureDatabase(makeTestDataSourceProperties(),
|
||||||
DatabaseConfig(),
|
DatabaseConfig(),
|
||||||
rigorousMock(),
|
{ null }, { null },
|
||||||
NodeSchemaService(setOf(MappedSchema(AppendOnlyPersistentMapTest::class.java, 1, listOf(PersistentMapEntry::class.java)))))
|
NodeSchemaService(setOf(MappedSchema(AppendOnlyPersistentMapTest::class.java, 1, listOf(PersistentMapEntry::class.java)))))
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -27,7 +27,6 @@ import net.corda.testing.core.ALICE_NAME
|
|||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
import net.corda.testing.internal.LogHelper
|
import net.corda.testing.internal.LogHelper
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -55,7 +54,7 @@ class DBCheckpointStorageTests {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null })
|
||||||
newCheckpointStorage()
|
newCheckpointStorage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,14 +18,17 @@ import net.corda.core.crypto.TransactionSignature
|
|||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
|
||||||
import net.corda.node.internal.configureDatabase
|
import net.corda.node.internal.configureDatabase
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
|
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.ALICE_NAME
|
||||||
|
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||||
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
|
import net.corda.testing.core.TestIdentity
|
||||||
|
import net.corda.testing.core.dummyCommand
|
||||||
import net.corda.testing.internal.LogHelper
|
import net.corda.testing.internal.LogHelper
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -51,7 +54,7 @@ class DBTransactionStorageTests {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
val dataSourceProps = makeTestDataSourceProperties()
|
val dataSourceProps = makeTestDataSourceProperties()
|
||||||
database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), rigorousMock())
|
database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), { null }, { null })
|
||||||
newTransactionStorage()
|
newTransactionStorage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,51 +66,33 @@ class DBTransactionStorageTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `empty store`() {
|
fun `empty store`() {
|
||||||
database.transaction {
|
assertThat(transactionStorage.getTransaction(newTransaction().id)).isNull()
|
||||||
assertThat(transactionStorage.getTransaction(newTransaction().id)).isNull()
|
assertThat(transactionStorage.transactions).isEmpty()
|
||||||
}
|
|
||||||
database.transaction {
|
|
||||||
assertThat(transactionStorage.transactions).isEmpty()
|
|
||||||
}
|
|
||||||
newTransactionStorage()
|
newTransactionStorage()
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).isEmpty()
|
||||||
assertThat(transactionStorage.transactions).isEmpty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `one transaction`() {
|
fun `one transaction`() {
|
||||||
val transaction = newTransaction()
|
val transaction = newTransaction()
|
||||||
database.transaction {
|
transactionStorage.addTransaction(transaction)
|
||||||
transactionStorage.addTransaction(transaction)
|
|
||||||
}
|
|
||||||
assertTransactionIsRetrievable(transaction)
|
assertTransactionIsRetrievable(transaction)
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).containsExactly(transaction)
|
||||||
assertThat(transactionStorage.transactions).containsExactly(transaction)
|
|
||||||
}
|
|
||||||
newTransactionStorage()
|
newTransactionStorage()
|
||||||
assertTransactionIsRetrievable(transaction)
|
assertTransactionIsRetrievable(transaction)
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).containsExactly(transaction)
|
||||||
assertThat(transactionStorage.transactions).containsExactly(transaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `two transactions across restart`() {
|
fun `two transactions across restart`() {
|
||||||
val firstTransaction = newTransaction()
|
val firstTransaction = newTransaction()
|
||||||
val secondTransaction = newTransaction()
|
val secondTransaction = newTransaction()
|
||||||
database.transaction {
|
transactionStorage.addTransaction(firstTransaction)
|
||||||
transactionStorage.addTransaction(firstTransaction)
|
|
||||||
}
|
|
||||||
newTransactionStorage()
|
newTransactionStorage()
|
||||||
database.transaction {
|
transactionStorage.addTransaction(secondTransaction)
|
||||||
transactionStorage.addTransaction(secondTransaction)
|
|
||||||
}
|
|
||||||
assertTransactionIsRetrievable(firstTransaction)
|
assertTransactionIsRetrievable(firstTransaction)
|
||||||
assertTransactionIsRetrievable(secondTransaction)
|
assertTransactionIsRetrievable(secondTransaction)
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction)
|
||||||
assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -120,24 +105,18 @@ class DBTransactionStorageTests {
|
|||||||
rollback()
|
rollback()
|
||||||
}
|
}
|
||||||
|
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).isEmpty()
|
||||||
assertThat(transactionStorage.transactions).isEmpty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `two transactions in same DB transaction scope`() {
|
fun `two transactions in same DB transaction scope`() {
|
||||||
val firstTransaction = newTransaction()
|
val firstTransaction = newTransaction()
|
||||||
val secondTransaction = newTransaction()
|
val secondTransaction = newTransaction()
|
||||||
database.transaction {
|
transactionStorage.addTransaction(firstTransaction)
|
||||||
transactionStorage.addTransaction(firstTransaction)
|
transactionStorage.addTransaction(secondTransaction)
|
||||||
transactionStorage.addTransaction(secondTransaction)
|
|
||||||
}
|
|
||||||
assertTransactionIsRetrievable(firstTransaction)
|
assertTransactionIsRetrievable(firstTransaction)
|
||||||
assertTransactionIsRetrievable(secondTransaction)
|
assertTransactionIsRetrievable(secondTransaction)
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction)
|
||||||
assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -148,36 +127,29 @@ class DBTransactionStorageTests {
|
|||||||
transactionStorage.addTransaction(firstTransaction)
|
transactionStorage.addTransaction(firstTransaction)
|
||||||
}
|
}
|
||||||
assertTransactionIsRetrievable(firstTransaction)
|
assertTransactionIsRetrievable(firstTransaction)
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).containsOnly(firstTransaction)
|
||||||
assertThat(transactionStorage.transactions).containsOnly(firstTransaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `transaction saved twice in two DB transaction scopes`() {
|
fun `transaction saved twice in two DB transaction scopes`() {
|
||||||
val firstTransaction = newTransaction()
|
val firstTransaction = newTransaction()
|
||||||
val secondTransaction = newTransaction()
|
val secondTransaction = newTransaction()
|
||||||
database.transaction {
|
|
||||||
transactionStorage.addTransaction(firstTransaction)
|
transactionStorage.addTransaction(firstTransaction)
|
||||||
}
|
|
||||||
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
transactionStorage.addTransaction(secondTransaction)
|
transactionStorage.addTransaction(secondTransaction)
|
||||||
transactionStorage.addTransaction(firstTransaction)
|
transactionStorage.addTransaction(firstTransaction)
|
||||||
}
|
}
|
||||||
assertTransactionIsRetrievable(firstTransaction)
|
assertTransactionIsRetrievable(firstTransaction)
|
||||||
database.transaction {
|
assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction)
|
||||||
assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `updates are fired`() {
|
fun `updates are fired`() {
|
||||||
val future = transactionStorage.updates.toFuture()
|
val future = transactionStorage.updates.toFuture()
|
||||||
val expected = newTransaction()
|
val expected = newTransaction()
|
||||||
database.transaction {
|
transactionStorage.addTransaction(expected)
|
||||||
transactionStorage.addTransaction(expected)
|
|
||||||
}
|
|
||||||
val actual = future.get(1, TimeUnit.SECONDS)
|
val actual = future.get(1, TimeUnit.SECONDS)
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
@ -196,15 +168,11 @@ class DBTransactionStorageTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) {
|
private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) {
|
||||||
database.transaction {
|
transactionStorage = DBTransactionStorage(cacheSizeBytesOverride ?: NodeConfiguration.defaultTransactionCacheSize, database)
|
||||||
transactionStorage = DBTransactionStorage(cacheSizeBytesOverride ?: NodeConfiguration.defaultTransactionCacheSize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertTransactionIsRetrievable(transaction: SignedTransaction) {
|
private fun assertTransactionIsRetrievable(transaction: SignedTransaction) {
|
||||||
database.transaction {
|
assertThat(transactionStorage.getTransaction(transaction.id)).isEqualTo(transaction)
|
||||||
assertThat(transactionStorage.getTransaction(transaction.id)).isEqualTo(transaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newTransaction(): SignedTransaction {
|
private fun newTransaction(): SignedTransaction {
|
||||||
|
@ -122,7 +122,7 @@ class HibernateConfigurationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val schemaService = NodeSchemaService(extraSchemas = setOf(CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3, DummyLinearStateSchemaV1, DummyLinearStateSchemaV2, DummyDealStateSchemaV1 ))
|
val schemaService = NodeSchemaService(extraSchemas = setOf(CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3, DummyLinearStateSchemaV1, DummyLinearStateSchemaV2, DummyDealStateSchemaV1 ))
|
||||||
database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), identityService, schemaService)
|
database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService)
|
||||||
database.transaction {
|
database.transaction {
|
||||||
hibernateConfig = database.hibernateConfig
|
hibernateConfig = database.hibernateConfig
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ class HibernateConfigurationTest {
|
|||||||
services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock<IdentityServiceInternal>().also {
|
services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock<IdentityServiceInternal>().also {
|
||||||
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME })
|
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME })
|
||||||
}, generateKeyPair(), dummyNotary.keyPair) {
|
}, generateKeyPair(), dummyNotary.keyPair) {
|
||||||
override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig)
|
override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig, database)
|
||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||||
for (stx in txs) {
|
for (stx in txs) {
|
||||||
(validatedTransactions as WritableTransactionStorage).addTransaction(stx)
|
(validatedTransactions as WritableTransactionStorage).addTransaction(stx)
|
||||||
|
@ -15,7 +15,11 @@ import com.google.common.jimfs.Configuration
|
|||||||
import com.google.common.jimfs.Jimfs
|
import com.google.common.jimfs.Jimfs
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.read
|
||||||
|
import net.corda.core.internal.readAll
|
||||||
|
import net.corda.core.internal.readFully
|
||||||
|
import net.corda.core.internal.write
|
||||||
|
import net.corda.core.internal.writeLines
|
||||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
import net.corda.core.node.services.vault.AttachmentQueryCriteria
|
||||||
import net.corda.core.node.services.vault.AttachmentSort
|
import net.corda.core.node.services.vault.AttachmentSort
|
||||||
import net.corda.core.node.services.vault.Builder
|
import net.corda.core.node.services.vault.Builder
|
||||||
@ -25,7 +29,6 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
|
|||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.testing.internal.LogHelper
|
import net.corda.testing.internal.LogHelper
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -45,14 +48,16 @@ class NodeAttachmentStorageTest {
|
|||||||
// Use an in memory file system for testing attachment storage.
|
// Use an in memory file system for testing attachment storage.
|
||||||
private lateinit var fs: FileSystem
|
private lateinit var fs: FileSystem
|
||||||
private lateinit var database: CordaPersistence
|
private lateinit var database: CordaPersistence
|
||||||
|
private lateinit var storage: NodeAttachmentService
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
|
|
||||||
val dataSourceProperties = makeTestDataSourceProperties()
|
val dataSourceProperties = makeTestDataSourceProperties()
|
||||||
database = configureDatabase(dataSourceProperties, DatabaseConfig(runMigration = true), rigorousMock())
|
database = configureDatabase(dataSourceProperties, DatabaseConfig(runMigration = true), { null }, { null })
|
||||||
fs = Jimfs.newFileSystem(Configuration.unix())
|
fs = Jimfs.newFileSystem(Configuration.unix())
|
||||||
|
storage = NodeAttachmentService(MetricRegistry(), database = database).also { it.start() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -62,28 +67,25 @@ class NodeAttachmentStorageTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `insert and retrieve`() {
|
fun `insert and retrieve`() {
|
||||||
val (testJar,expectedHash) = makeTestJar()
|
val (testJar, expectedHash) = makeTestJar()
|
||||||
|
|
||||||
database.transaction {
|
val id = testJar.read { storage.importAttachment(it) }
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
assertEquals(expectedHash, id)
|
||||||
val id = testJar.read { storage.importAttachment(it) }
|
|
||||||
assertEquals(expectedHash, id)
|
|
||||||
|
|
||||||
assertNull(storage.openAttachment(SecureHash.randomSHA256()))
|
assertNull(storage.openAttachment(SecureHash.randomSHA256()))
|
||||||
val stream = storage.openAttachment(expectedHash)!!.openAsJAR()
|
val stream = storage.openAttachment(expectedHash)!!.openAsJAR()
|
||||||
val e1 = stream.nextJarEntry!!
|
val e1 = stream.nextJarEntry!!
|
||||||
assertEquals("test1.txt", e1.name)
|
assertEquals("test1.txt", e1.name)
|
||||||
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content")
|
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content")
|
||||||
val e2 = stream.nextJarEntry!!
|
val e2 = stream.nextJarEntry!!
|
||||||
assertEquals("test2.txt", e2.name)
|
assertEquals("test2.txt", e2.name)
|
||||||
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content")
|
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content")
|
||||||
|
|
||||||
stream.close()
|
stream.close()
|
||||||
|
|
||||||
storage.openAttachment(id)!!.openAsJAR().use {
|
storage.openAttachment(id)!!.openAsJAR().use {
|
||||||
it.nextJarEntry
|
it.nextJarEntry
|
||||||
it.readBytes()
|
it.readBytes()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,138 +94,121 @@ class NodeAttachmentStorageTest {
|
|||||||
val (testJar, expectedHash) = makeTestJar()
|
val (testJar, expectedHash) = makeTestJar()
|
||||||
val (jarB, hashB) = makeTestJar(listOf(Pair("file", "content")))
|
val (jarB, hashB) = makeTestJar(listOf(Pair("file", "content")))
|
||||||
|
|
||||||
database.transaction {
|
val id = testJar.read { storage.importAttachment(it) }
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
assertEquals(expectedHash, id)
|
||||||
val id = testJar.read { storage.importAttachment(it) }
|
|
||||||
assertEquals(expectedHash, id)
|
|
||||||
|
|
||||||
|
|
||||||
assertNull(storage.openAttachment(hashB))
|
assertNull(storage.openAttachment(hashB))
|
||||||
val stream = storage.openAttachment(expectedHash)!!.openAsJAR()
|
val stream = storage.openAttachment(expectedHash)!!.openAsJAR()
|
||||||
val e1 = stream.nextJarEntry!!
|
val e1 = stream.nextJarEntry!!
|
||||||
assertEquals("test1.txt", e1.name)
|
assertEquals("test1.txt", e1.name)
|
||||||
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content")
|
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content")
|
||||||
val e2 = stream.nextJarEntry!!
|
val e2 = stream.nextJarEntry!!
|
||||||
assertEquals("test2.txt", e2.name)
|
assertEquals("test2.txt", e2.name)
|
||||||
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content")
|
assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content")
|
||||||
|
|
||||||
stream.close()
|
stream.close()
|
||||||
|
|
||||||
val idB = jarB.read { storage.importAttachment(it) }
|
val idB = jarB.read { storage.importAttachment(it) }
|
||||||
assertEquals(hashB, idB)
|
assertEquals(hashB, idB)
|
||||||
|
|
||||||
storage.openAttachment(id)!!.openAsJAR().use {
|
storage.openAttachment(id)!!.openAsJAR().use {
|
||||||
it.nextJarEntry
|
it.nextJarEntry
|
||||||
it.readBytes()
|
it.readBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.openAttachment(idB)!!.openAsJAR().use {
|
storage.openAttachment(idB)!!.openAsJAR().use {
|
||||||
it.nextJarEntry
|
it.nextJarEntry
|
||||||
it.readBytes()
|
it.readBytes()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `metadata can be used to search`() {
|
fun `metadata can be used to search`() {
|
||||||
val (jarA, _) = makeTestJar()
|
val (jarA, _) = makeTestJar()
|
||||||
val (jarB, hashB) = makeTestJar(listOf(Pair("file","content")))
|
val (jarB, hashB) = makeTestJar(listOf(Pair("file", "content")))
|
||||||
val (jarC, hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff")))
|
val (jarC, hashC) = makeTestJar(listOf(Pair("magic_file", "magic_content_puff")))
|
||||||
|
|
||||||
database.transaction {
|
jarA.read { storage.importAttachment(it) }
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
jarB.read { storage.importAttachment(it, "uploaderB", "fileB.zip") }
|
||||||
|
jarC.read { storage.importAttachment(it, "uploaderC", "fileC.zip") }
|
||||||
|
|
||||||
jarA.read { storage.importAttachment(it) }
|
assertEquals(
|
||||||
jarB.read { storage.importAttachment(it, "uploaderB", "fileB.zip") }
|
|
||||||
jarC.read { storage.importAttachment(it, "uploaderC", "fileC.zip") }
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
listOf(hashB),
|
listOf(hashB),
|
||||||
storage.queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria( Builder.equal("uploaderB")))
|
storage.queryAttachments(AttachmentQueryCriteria.AttachmentsQueryCriteria(Builder.equal("uploaderB")))
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals (
|
assertEquals(
|
||||||
listOf(hashB, hashC),
|
listOf(hashB, hashC),
|
||||||
storage.queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria( Builder.like ("%uploader%")))
|
storage.queryAttachments(AttachmentQueryCriteria.AttachmentsQueryCriteria(Builder.like("%uploader%")))
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `sorting and compound conditions work`() {
|
fun `sorting and compound conditions work`() {
|
||||||
val (jarA,hashA) = makeTestJar(listOf(Pair("a","a")))
|
val (jarA, hashA) = makeTestJar(listOf(Pair("a", "a")))
|
||||||
val (jarB,hashB) = makeTestJar(listOf(Pair("b","b")))
|
val (jarB, hashB) = makeTestJar(listOf(Pair("b", "b")))
|
||||||
val (jarC,hashC) = makeTestJar(listOf(Pair("c","c")))
|
val (jarC, hashC) = makeTestJar(listOf(Pair("c", "c")))
|
||||||
|
|
||||||
fun uploaderCondition(s:String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal(s))
|
fun uploaderCondition(s: String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal(s))
|
||||||
fun filenamerCondition(s:String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(filenameCondition = Builder.equal(s))
|
fun filenamerCondition(s: String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(filenameCondition = Builder.equal(s))
|
||||||
|
|
||||||
fun filenameSort(direction: Sort.Direction) = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.FILENAME, direction)))
|
fun filenameSort(direction: Sort.Direction) = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.FILENAME, direction)))
|
||||||
|
|
||||||
database.transaction {
|
jarA.read { storage.importAttachment(it, "complexA", "archiveA.zip") }
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
jarB.read { storage.importAttachment(it, "complexB", "archiveB.zip") }
|
||||||
|
jarC.read { storage.importAttachment(it, "complexC", "archiveC.zip") }
|
||||||
|
|
||||||
jarA.read { storage.importAttachment(it, "complexA", "archiveA.zip") }
|
// DOCSTART AttachmentQueryExample1
|
||||||
jarB.read { storage.importAttachment(it, "complexB", "archiveB.zip") }
|
|
||||||
jarC.read { storage.importAttachment(it, "complexC", "archiveC.zip") }
|
|
||||||
|
|
||||||
// DOCSTART AttachmentQueryExample1
|
assertEquals(
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
emptyList(),
|
emptyList(),
|
||||||
storage.queryAttachments(
|
storage.queryAttachments(
|
||||||
AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA"))
|
AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA"))
|
||||||
.and(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB"))))
|
.and(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB"))))
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(hashA, hashB),
|
listOf(hashA, hashB),
|
||||||
storage.queryAttachments(
|
storage.queryAttachments(
|
||||||
|
|
||||||
AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA"))
|
AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA"))
|
||||||
.or(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB"))))
|
.or(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB"))))
|
||||||
)
|
)
|
||||||
|
|
||||||
val complexCondition =
|
val complexCondition =
|
||||||
(uploaderCondition("complexB").and(filenamerCondition("archiveB.zip"))).or(filenamerCondition("archiveC.zip"))
|
(uploaderCondition("complexB").and(filenamerCondition("archiveB.zip"))).or(filenamerCondition("archiveC.zip"))
|
||||||
|
|
||||||
// DOCEND AttachmentQueryExample1
|
// DOCEND AttachmentQueryExample1
|
||||||
|
|
||||||
assertEquals (
|
assertEquals(
|
||||||
listOf(hashB, hashC),
|
listOf(hashB, hashC),
|
||||||
storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.ASC))
|
storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.ASC))
|
||||||
)
|
)
|
||||||
assertEquals (
|
assertEquals(
|
||||||
listOf(hashC, hashB),
|
listOf(hashC, hashB),
|
||||||
storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.DESC))
|
storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.DESC))
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore("We need to be able to restart nodes - make importing attachments idempotent?")
|
@Ignore("We need to be able to restart nodes - make importing attachments idempotent?")
|
||||||
@Test
|
@Test
|
||||||
fun `duplicates not allowed`() {
|
fun `duplicates not allowed`() {
|
||||||
val (testJar,_) = makeTestJar()
|
val (testJar, _) = makeTestJar()
|
||||||
database.transaction {
|
testJar.read {
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
storage.importAttachment(it)
|
||||||
|
}
|
||||||
|
assertFailsWith<FileAlreadyExistsException> {
|
||||||
testJar.read {
|
testJar.read {
|
||||||
storage.importAttachment(it)
|
storage.importAttachment(it)
|
||||||
}
|
}
|
||||||
assertFailsWith<FileAlreadyExistsException> {
|
|
||||||
testJar.read {
|
|
||||||
storage.importAttachment(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `corrupt entry throws exception`() {
|
fun `corrupt entry throws exception`() {
|
||||||
val (testJar,_) = makeTestJar()
|
val (testJar, _) = makeTestJar()
|
||||||
val id = database.transaction {
|
val id = database.transaction {
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
|
||||||
val id = testJar.read { storage.importAttachment(it) }
|
val id = testJar.read { storage.importAttachment(it) }
|
||||||
|
|
||||||
// Corrupt the file in the store.
|
// Corrupt the file in the store.
|
||||||
@ -234,37 +219,31 @@ class NodeAttachmentStorageTest {
|
|||||||
session.merge(corruptAttachment)
|
session.merge(corruptAttachment)
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
database.transaction {
|
val e = assertFailsWith<NodeAttachmentService.HashMismatchException> {
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
storage.openAttachment(id)!!.open().readFully()
|
||||||
val e = assertFailsWith<NodeAttachmentService.HashMismatchException> {
|
}
|
||||||
storage.openAttachment(id)!!.open().readFully()
|
assertEquals(e.expected, id)
|
||||||
}
|
|
||||||
assertEquals(e.expected, id)
|
|
||||||
|
|
||||||
// But if we skip around and read a single entry, no exception is thrown.
|
// But if we skip around and read a single entry, no exception is thrown.
|
||||||
storage.openAttachment(id)!!.openAsJAR().use {
|
storage.openAttachment(id)!!.openAsJAR().use {
|
||||||
it.nextJarEntry
|
it.nextJarEntry
|
||||||
it.readBytes()
|
it.readBytes()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `non jar rejected`() {
|
fun `non jar rejected`() {
|
||||||
database.transaction {
|
val path = fs.getPath("notajar")
|
||||||
val storage = NodeAttachmentService(MetricRegistry())
|
path.writeLines(listOf("Hey", "there!"))
|
||||||
val path = fs.getPath("notajar")
|
path.read {
|
||||||
path.writeLines(listOf("Hey", "there!"))
|
assertFailsWith<IllegalArgumentException>("either empty or not a JAR") {
|
||||||
path.read {
|
storage.importAttachment(it)
|
||||||
assertFailsWith<IllegalArgumentException>("either empty or not a JAR") {
|
|
||||||
storage.importAttachment(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var counter = 0
|
private var counter = 0
|
||||||
private fun makeTestJar(extraEntries: List<Pair<String,String>> = emptyList()): Pair<Path, SecureHash> {
|
private fun makeTestJar(extraEntries: List<Pair<String, String>> = emptyList()): Pair<Path, SecureHash> {
|
||||||
counter++
|
counter++
|
||||||
val file = fs.getPath("$counter.jar")
|
val file = fs.getPath("$counter.jar")
|
||||||
file.write {
|
file.write {
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.node.services.persistence
|
|||||||
|
|
||||||
import net.corda.node.internal.configureDatabase
|
import net.corda.node.internal.configureDatabase
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -10,7 +9,7 @@ import kotlin.test.assertEquals
|
|||||||
|
|
||||||
|
|
||||||
class TransactionCallbackTest {
|
class TransactionCallbackTest {
|
||||||
private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null })
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun closeDatabase() {
|
fun closeDatabase() {
|
||||||
|
@ -29,7 +29,6 @@ import net.corda.testing.internal.LogHelper
|
|||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -78,7 +77,7 @@ class HibernateObserverTests {
|
|||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock(), schemaService)
|
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }, schemaService)
|
||||||
HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig, schemaService)
|
HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig, schemaService)
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
|
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
|
||||||
|
@ -26,7 +26,6 @@ import net.corda.testing.core.SerializationEnvironmentRule
|
|||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
import net.corda.testing.core.generateStateRef
|
import net.corda.testing.core.generateStateRef
|
||||||
import net.corda.testing.internal.LogHelper
|
import net.corda.testing.internal.LogHelper
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -49,7 +48,7 @@ class PersistentUniquenessProviderTests {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock(), NodeSchemaService(includeNotarySchemas = true))
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }, NodeSchemaService(includeNotarySchemas = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -32,7 +32,6 @@ import net.corda.testing.core.ALICE_NAME
|
|||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.core.freeLocalHostAndPort
|
import net.corda.testing.core.freeLocalHostAndPort
|
||||||
import net.corda.testing.internal.LogHelper
|
import net.corda.testing.internal.LogHelper
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.hamcrest.Matchers.instanceOf
|
import org.hamcrest.Matchers.instanceOf
|
||||||
import org.junit.*
|
import org.junit.*
|
||||||
@ -159,7 +158,7 @@ class RaftTransactionCommitLogTests {
|
|||||||
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
|
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
|
||||||
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
|
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
|
||||||
val address = Address(myAddress.host, myAddress.port)
|
val address = Address(myAddress.host, myAddress.port)
|
||||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock(), NodeSchemaService(includeNotarySchemas = true))
|
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }, NodeSchemaService(includeNotarySchemas = true))
|
||||||
databases.add(database)
|
databases.add(database)
|
||||||
val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), RaftUniquenessProvider.Companion::createMap) }
|
val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), RaftUniquenessProvider.Companion::createMap) }
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
|
|||||||
@Ignore
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
fun createPersistentTestDb() {
|
fun createPersistentTestDb() {
|
||||||
val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(runMigration = true), identitySvc)
|
val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(runMigration = true), identitySvc::wellKnownPartyFromX500Name, identitySvc::wellKnownPartyFromAnonymous)
|
||||||
setUpDb(database, 5000)
|
setUpDb(database, 5000)
|
||||||
|
|
||||||
database.close()
|
database.close()
|
||||||
|
@ -35,6 +35,7 @@ import net.corda.core.utilities.getOrThrow
|
|||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.internal.InitiatedFlowFactory
|
import net.corda.node.internal.InitiatedFlowFactory
|
||||||
import net.corda.node.services.api.VaultServiceInternal
|
import net.corda.node.services.api.VaultServiceInternal
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
@ -93,9 +94,9 @@ class VaultSoftLockManagerTest {
|
|||||||
}
|
}
|
||||||
private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args ->
|
private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args ->
|
||||||
object : InternalMockNetwork.MockNode(args) {
|
object : InternalMockNetwork.MockNode(args) {
|
||||||
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal {
|
override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration, database: CordaPersistence): VaultServiceInternal {
|
||||||
val node = this
|
val node = this
|
||||||
val realVault = super.makeVaultService(keyManagementService, services, hibernateConfig)
|
val realVault = super.makeVaultService(keyManagementService, services, hibernateConfig, database)
|
||||||
return object : VaultServiceInternal by realVault {
|
return object : VaultServiceInternal by realVault {
|
||||||
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
|
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
|
||||||
// Should be called before flow is removed
|
// Should be called before flow is removed
|
||||||
|
@ -15,7 +15,6 @@ import net.corda.core.internal.bufferUntilSubscribed
|
|||||||
import net.corda.core.internal.tee
|
import net.corda.core.internal.tee
|
||||||
import net.corda.node.internal.configureDatabase
|
import net.corda.node.internal.configureDatabase
|
||||||
import net.corda.nodeapi.internal.persistence.*
|
import net.corda.nodeapi.internal.persistence.*
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -31,7 +30,7 @@ class ObservablesTests {
|
|||||||
private val toBeClosed = mutableListOf<Closeable>()
|
private val toBeClosed = mutableListOf<Closeable>()
|
||||||
|
|
||||||
private fun createDatabase(): CordaPersistence {
|
private fun createDatabase(): CordaPersistence {
|
||||||
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
|
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null })
|
||||||
toBeClosed += database
|
toBeClosed += database
|
||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,13 @@ import net.corda.core.crypto.SecureHash
|
|||||||
import net.corda.node.internal.configureDatabase
|
import net.corda.node.internal.configureDatabase
|
||||||
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class PersistentMapTests {
|
class PersistentMapTests {
|
||||||
private val databaseConfig = DatabaseConfig()
|
private val databaseConfig = DatabaseConfig()
|
||||||
private val database get() = configureDatabase(dataSourceProps, databaseConfig, rigorousMock())
|
private val database get() = configureDatabase(dataSourceProps, databaseConfig, { null }, { null })
|
||||||
private val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
private val dataSourceProps = MockServices.makeTestDataSourceProperties()
|
||||||
|
|
||||||
//create a test map using an existing db table
|
//create a test map using an existing db table
|
||||||
|
@ -77,7 +77,7 @@ class NodeInterestRatesTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null })
|
||||||
database.transaction {
|
database.transaction {
|
||||||
oracle = createMockCordaService(services, NodeInterestRates::Oracle)
|
oracle = createMockCordaService(services, NodeInterestRates::Oracle)
|
||||||
oracle.knownFixes = TEST_DATA
|
oracle.knownFixes = TEST_DATA
|
||||||
|
@ -104,10 +104,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
val node1 = banks[i].started!!
|
val node1 = banks[i].started!!
|
||||||
val node2 = banks[j].started!!
|
val node2 = banks[j].started!!
|
||||||
|
|
||||||
val swaps =
|
val swaps = node1.services.vaultService.queryBy<InterestRateSwap.State>().states
|
||||||
node1.database.transaction {
|
|
||||||
node1.services.vaultService.queryBy<InterestRateSwap.State>().states
|
|
||||||
}
|
|
||||||
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.single()
|
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.single()
|
||||||
|
|
||||||
// Do we have any more days left in this deal's lifetime? If not, return.
|
// Do we have any more days left in this deal's lifetime? If not, return.
|
||||||
|
@ -73,9 +73,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
|||||||
registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
|
registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java)
|
||||||
registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
|
registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java)
|
||||||
javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use {
|
javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use {
|
||||||
database.transaction {
|
services.cordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText())
|
||||||
services.cordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
package net.corda.serialization.internal.amqp
|
package net.corda.serialization.internal.amqp
|
||||||
|
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.core.utilities.trace
|
||||||
import org.apache.qpid.proton.amqp.Symbol
|
import org.apache.qpid.proton.amqp.Symbol
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
@ -21,30 +25,48 @@ import java.lang.reflect.Type
|
|||||||
*/
|
*/
|
||||||
open class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
|
open class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||||
companion object {
|
companion object {
|
||||||
fun make(type: Type, factory: SerializerFactory) = when (type) {
|
fun make(type: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||||
Array<Char>::class.java -> CharArraySerializer(factory)
|
contextLogger().debug { "Making array serializer, typename=${type.typeName}" }
|
||||||
else -> ArraySerializer(type, factory)
|
return when (type) {
|
||||||
|
Array<Char>::class.java -> CharArraySerializer(factory)
|
||||||
|
else -> ArraySerializer(type, factory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val logger = loggerFor<ArraySerializer>()
|
||||||
|
|
||||||
// because this might be an array of array of primitives (to any recursive depth) and
|
// because this might be an array of array of primitives (to any recursive depth) and
|
||||||
// because we care that the lowest type is unboxed we can't rely on the inbuilt type
|
// because we care that the lowest type is unboxed we can't rely on the inbuilt type
|
||||||
// id to generate it properly (it will always return [[[Ljava.lang.type -> type[][][]
|
// id to generate it properly (it will always return [[[Ljava.lang.type -> type[][][]
|
||||||
// for example).
|
// for example).
|
||||||
//
|
//
|
||||||
// We *need* to retain knowledge for AMQP deserialization weather that lowest primitive
|
// We *need* to retain knowledge for AMQP deserialization whether that lowest primitive
|
||||||
// was boxed or unboxed so just infer it recursively.
|
// was boxed or unboxed so just infer it recursively.
|
||||||
private fun calcTypeName(type: Type): String =
|
private fun calcTypeName(type: Type, debugOffset : Int = 0): String {
|
||||||
if (type.componentType().isArray()) {
|
logger.trace { "${"".padStart(debugOffset, ' ') } calcTypeName - ${type.typeName}" }
|
||||||
val typeName = calcTypeName(type.componentType()); "$typeName[]"
|
|
||||||
|
return if (type.componentType().isArray()) {
|
||||||
|
// Special case handler for primitive byte arrays. This is needed because we can silently
|
||||||
|
// coerce a byte[] to our own binary type. Normally, if the component type was itself an
|
||||||
|
// array we'd keep walking down the chain but for byte[] stop here and use binary instead
|
||||||
|
val typeName = if (SerializerFactory.isPrimitive(type.componentType())) {
|
||||||
|
SerializerFactory.nameForType(type.componentType())
|
||||||
} else {
|
} else {
|
||||||
val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]"
|
calcTypeName(type.componentType(), debugOffset + 4)
|
||||||
"${type.componentType().typeName}$arrayType"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"$typeName[]"
|
||||||
|
} else {
|
||||||
|
val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]"
|
||||||
|
"${type.componentType().typeName}$arrayType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val typeDescriptor: Symbol by lazy {
|
override val typeDescriptor: Symbol by lazy {
|
||||||
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val elementType: Type by lazy { type.componentType() }
|
internal val elementType: Type by lazy { type.componentType() }
|
||||||
internal open val typeName by lazy { calcTypeName(type) }
|
internal open val typeName by lazy { calcTypeName(type) }
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import com.google.common.primitives.Primitives
|
|||||||
import com.google.common.reflect.TypeResolver
|
import com.google.common.reflect.TypeResolver
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.ClassWhitelist
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.serialization.internal.carpenter.*
|
import net.corda.serialization.internal.carpenter.*
|
||||||
@ -255,7 +256,7 @@ open class SerializerFactory(
|
|||||||
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
||||||
val metaSchema = CarpenterMetaSchema.newInstance()
|
val metaSchema = CarpenterMetaSchema.newInstance()
|
||||||
for (typeNotation in schemaAndDescriptor.schemas.schema.types) {
|
for (typeNotation in schemaAndDescriptor.schemas.schema.types) {
|
||||||
logger.trace("descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}")
|
logger.debug { "descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}" }
|
||||||
try {
|
try {
|
||||||
val serialiser = processSchemaEntry(typeNotation)
|
val serialiser = processSchemaEntry(typeNotation)
|
||||||
// if we just successfully built a serializer for the type but the type fingerprint
|
// if we just successfully built a serializer for the type but the type fingerprint
|
||||||
|
@ -10,10 +10,7 @@
|
|||||||
|
|
||||||
package net.corda.serialization.internal.amqp
|
package net.corda.serialization.internal.amqp
|
||||||
|
|
||||||
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
|
import net.corda.serialization.internal.amqp.testutils.*
|
||||||
import net.corda.serialization.internal.amqp.testutils.deserialize
|
|
||||||
import net.corda.serialization.internal.amqp.testutils.serialize
|
|
||||||
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -263,6 +260,14 @@ class DeserializeSimpleTypesTests {
|
|||||||
assertEquals(c.c[0], deserializedC.c[0])
|
assertEquals(c.c[0], deserializedC.c[0])
|
||||||
assertEquals(c.c[1], deserializedC.c[1])
|
assertEquals(c.c[1], deserializedC.c[1])
|
||||||
assertEquals(c.c[2], deserializedC.c[2])
|
assertEquals(c.c[2], deserializedC.c[2])
|
||||||
|
|
||||||
|
val di = DeserializationInput(sf2)
|
||||||
|
val deserializedC2 = di.deserialize(serialisedC)
|
||||||
|
|
||||||
|
assertEquals(c.c.size, deserializedC2.c.size)
|
||||||
|
assertEquals(c.c[0], deserializedC2.c[0])
|
||||||
|
assertEquals(c.c[1], deserializedC2.c[1])
|
||||||
|
assertEquals(c.c[2], deserializedC2.c[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -504,7 +509,33 @@ class DeserializeSimpleTypesTests {
|
|||||||
assertEquals(3, da2.a?.a?.b)
|
assertEquals(3, da2.a?.a?.b)
|
||||||
assertEquals(2, da2.a?.a?.a?.b)
|
assertEquals(2, da2.a?.a?.a?.b)
|
||||||
assertEquals(1, da2.a?.a?.a?.a?.b)
|
assertEquals(1, da2.a?.a?.a?.a?.b)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replicates CORDA-1545
|
||||||
|
@Test
|
||||||
|
fun arrayOfByteArray() {
|
||||||
|
class A(val a : Array<ByteArray>)
|
||||||
|
|
||||||
|
val ba1 = ByteArray(3)
|
||||||
|
ba1[0] = 0b0001; ba1[1] = 0b0101; ba1[2] = 0b1111
|
||||||
|
|
||||||
|
val ba2 = ByteArray(3)
|
||||||
|
ba2[0] = 0b1000; ba2[1] = 0b1100; ba2[2] = 0b1110
|
||||||
|
|
||||||
|
val a = A(arrayOf(ba1, ba2))
|
||||||
|
|
||||||
|
val serializedA = TestSerializationOutput(VERBOSE, sf1).serializeAndReturnSchema(a)
|
||||||
|
|
||||||
|
serializedA.schema.types.forEach {
|
||||||
|
println(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This not throwing is the point of the test
|
||||||
|
DeserializationInput(sf1).deserialize(serializedA.obj)
|
||||||
|
|
||||||
|
// This not throwing is the point of the test
|
||||||
|
DeserializationInput(sf2).deserialize(serializedA.obj)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,12 @@ import net.corda.core.utilities.ByteSequence
|
|||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.node.services.messaging.*
|
import net.corda.node.services.messaging.DeduplicationHandler
|
||||||
|
import net.corda.node.services.messaging.Message
|
||||||
|
import net.corda.node.services.messaging.MessageHandler
|
||||||
|
import net.corda.node.services.messaging.MessageHandlerRegistration
|
||||||
|
import net.corda.node.services.messaging.MessagingService
|
||||||
|
import net.corda.node.services.messaging.ReceivedMessage
|
||||||
import net.corda.node.services.statemachine.DeduplicationId
|
import net.corda.node.services.statemachine.DeduplicationId
|
||||||
import net.corda.node.services.statemachine.ExternalEvent
|
import net.corda.node.services.statemachine.ExternalEvent
|
||||||
import net.corda.node.services.statemachine.SenderDeduplicationId
|
import net.corda.node.services.statemachine.SenderDeduplicationId
|
||||||
@ -47,6 +52,7 @@ import java.util.concurrent.LinkedBlockingQueue
|
|||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.jvm.Volatile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An in-memory network allows you to manufacture [InternalMockMessagingService]s for a set of participants. Each
|
* An in-memory network allows you to manufacture [InternalMockMessagingService]s for a set of participants. Each
|
||||||
@ -137,8 +143,7 @@ class InMemoryMessagingNetwork private constructor(
|
|||||||
id: Int,
|
id: Int,
|
||||||
executor: AffinityExecutor,
|
executor: AffinityExecutor,
|
||||||
notaryService: PartyAndCertificate?,
|
notaryService: PartyAndCertificate?,
|
||||||
description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK"),
|
description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK"))
|
||||||
database: CordaPersistence)
|
|
||||||
: InternalMockMessagingService {
|
: InternalMockMessagingService {
|
||||||
val peerHandle = PeerHandle(id, description)
|
val peerHandle = PeerHandle(id, description)
|
||||||
peersMapping[peerHandle.name] = peerHandle // Assume that the same name - the same entity in MockNetwork.
|
peersMapping[peerHandle.name] = peerHandle // Assume that the same name - the same entity in MockNetwork.
|
||||||
@ -146,7 +151,7 @@ class InMemoryMessagingNetwork private constructor(
|
|||||||
val serviceHandles = notaryService?.let { listOf(DistributedServiceHandle(it.party)) }
|
val serviceHandles = notaryService?.let { listOf(DistributedServiceHandle(it.party)) }
|
||||||
?: emptyList() //TODO only notary can be distributed?
|
?: emptyList() //TODO only notary can be distributed?
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database)
|
val node = InMemoryMessaging(manuallyPumped, peerHandle, executor)
|
||||||
val oldNode = handleEndpointMap.put(peerHandle, node)
|
val oldNode = handleEndpointMap.put(peerHandle, node)
|
||||||
if (oldNode != null) {
|
if (oldNode != null) {
|
||||||
node.inheritPendingRedelivery(oldNode)
|
node.inheritPendingRedelivery(oldNode)
|
||||||
@ -364,8 +369,7 @@ class InMemoryMessagingNetwork private constructor(
|
|||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
private inner class InMemoryMessaging(private val manuallyPumped: Boolean,
|
private inner class InMemoryMessaging(private val manuallyPumped: Boolean,
|
||||||
private val peerHandle: PeerHandle,
|
private val peerHandle: PeerHandle,
|
||||||
private val executor: AffinityExecutor,
|
private val executor: AffinityExecutor) : SingletonSerializeAsToken(), InternalMockMessagingService {
|
||||||
private val database: CordaPersistence) : SingletonSerializeAsToken(), InternalMockMessagingService {
|
|
||||||
private inner class Handler(val topicSession: String, val callback: MessageHandler) : MessageHandlerRegistration
|
private inner class Handler(val topicSession: String, val callback: MessageHandler) : MessageHandlerRegistration
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
@ -406,10 +410,8 @@ class InMemoryMessagingNetwork private constructor(
|
|||||||
val (handler, transfers) = state.locked {
|
val (handler, transfers) = state.locked {
|
||||||
val handler = Handler(topic, callback).apply { handlers.add(this) }
|
val handler = Handler(topic, callback).apply { handlers.add(this) }
|
||||||
val pending = ArrayList<MessageTransfer>()
|
val pending = ArrayList<MessageTransfer>()
|
||||||
database.transaction {
|
pending.addAll(pendingRedelivery)
|
||||||
pending.addAll(pendingRedelivery)
|
pendingRedelivery.clear()
|
||||||
pendingRedelivery.clear()
|
|
||||||
}
|
|
||||||
Pair(handler, pending)
|
Pair(handler, pending)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,9 +498,7 @@ class InMemoryMessagingNetwork private constructor(
|
|||||||
// up a handler for yet. Most unit tests don't run threaded, but we want to test true parallelism at
|
// up a handler for yet. Most unit tests don't run threaded, but we want to test true parallelism at
|
||||||
// least sometimes.
|
// least sometimes.
|
||||||
log.warn("Message to ${transfer.message.topic} could not be delivered")
|
log.warn("Message to ${transfer.message.topic} could not be delivered")
|
||||||
database.transaction {
|
pendingRedelivery.add(transfer)
|
||||||
pendingRedelivery.add(transfer)
|
|
||||||
}
|
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
matchingHandlers
|
matchingHandlers
|
||||||
@ -516,19 +516,17 @@ class InMemoryMessagingNetwork private constructor(
|
|||||||
val (transfer, deliverTo) = getNextQueue(q, block) ?: return null
|
val (transfer, deliverTo) = getNextQueue(q, block) ?: return null
|
||||||
if (transfer.message.uniqueMessageId !in processedMessages) {
|
if (transfer.message.uniqueMessageId !in processedMessages) {
|
||||||
executor.execute {
|
executor.execute {
|
||||||
database.transaction {
|
for (handler in deliverTo) {
|
||||||
for (handler in deliverTo) {
|
try {
|
||||||
try {
|
val receivedMessage = transfer.toReceivedMessage()
|
||||||
val receivedMessage = transfer.toReceivedMessage()
|
state.locked { pendingRedelivery.add(transfer) }
|
||||||
state.locked { pendingRedelivery.add(transfer) }
|
handler.callback(receivedMessage, handler, InMemoryDeduplicationHandler(receivedMessage, transfer))
|
||||||
handler.callback(receivedMessage, handler, InMemoryDeduplicationHandler(receivedMessage, transfer))
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
log.error("Caught exception in handler for $this/${handler.topicSession}", e)
|
||||||
log.error("Caught exception in handler for $this/${handler.topicSession}", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_receivedMessages.onNext(transfer)
|
|
||||||
messagesInFlight.countDown()
|
|
||||||
}
|
}
|
||||||
|
_receivedMessages.onNext(transfer)
|
||||||
|
messagesInFlight.countDown()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("Drop duplicate message ${transfer.message.uniqueMessageId}")
|
log.info("Drop duplicate message ${transfer.message.uniqueMessageId}")
|
||||||
|
@ -111,16 +111,17 @@ open class MockServices private constructor(
|
|||||||
val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages)
|
val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages)
|
||||||
val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation, SecureHash.randomSHA256().toString())
|
val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation, SecureHash.randomSHA256().toString())
|
||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService, schemaService)
|
val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService)
|
||||||
val mockService = database.transaction {
|
val mockService = database.transaction {
|
||||||
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
|
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
|
||||||
override val vaultService: VaultService = makeVaultService(database.hibernateConfig, schemaService)
|
override val vaultService: VaultService = makeVaultService(database.hibernateConfig, schemaService, database)
|
||||||
|
|
||||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||||
ServiceHubInternal.recordTransactions(statesToRecord, txs,
|
ServiceHubInternal.recordTransactions(statesToRecord, txs,
|
||||||
validatedTransactions as WritableTransactionStorage,
|
validatedTransactions as WritableTransactionStorage,
|
||||||
mockStateMachineRecordedTransactionMappingStorage,
|
mockStateMachineRecordedTransactionMappingStorage,
|
||||||
vaultService as VaultServiceInternal)
|
vaultService as VaultServiceInternal,
|
||||||
|
database)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun jdbcSession(): Connection = database.createSession()
|
override fun jdbcSession(): Connection = database.createSession()
|
||||||
@ -251,8 +252,8 @@ open class MockServices private constructor(
|
|||||||
|
|
||||||
protected val servicesForResolution: ServicesForResolution get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, validatedTransactions)
|
protected val servicesForResolution: ServicesForResolution get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, validatedTransactions)
|
||||||
|
|
||||||
internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal {
|
internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService, database: CordaPersistence): VaultServiceInternal {
|
||||||
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig)
|
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig, database)
|
||||||
HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService)
|
HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService)
|
||||||
return vaultService
|
return vaultService
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import net.corda.core.DoNotImplement
|
|||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
@ -281,15 +282,14 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
|||||||
id,
|
id,
|
||||||
serverThread,
|
serverThread,
|
||||||
myNotaryIdentity,
|
myNotaryIdentity,
|
||||||
configuration.myLegalName,
|
configuration.myLegalName).also { runOnStop += it::stop }
|
||||||
database).also { runOnStop += it::stop }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) {
|
fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) {
|
||||||
network = messagingServiceSpy
|
network = messagingServiceSpy
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>): KeyManagementService {
|
override fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>, database: CordaPersistence): KeyManagementService {
|
||||||
return E2ETestKeyManagementService(identityService, keyPairs)
|
return E2ETestKeyManagementService(identityService, keyPairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,8 +326,10 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
|||||||
override val serializationWhitelists: List<SerializationWhitelist>
|
override val serializationWhitelists: List<SerializationWhitelist>
|
||||||
get() = _serializationWhitelists
|
get() = _serializationWhitelists
|
||||||
private var dbCloser: (() -> Any?)? = null
|
private var dbCloser: (() -> Any?)? = null
|
||||||
override fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService): CordaPersistence {
|
override fun initialiseDatabasePersistence(schemaService: SchemaService,
|
||||||
return super.initialiseDatabasePersistence(schemaService, identityService).also { dbCloser = it::close }
|
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
|
||||||
|
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?): CordaPersistence {
|
||||||
|
return super.initialiseDatabasePersistence(schemaService, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous).also { dbCloser = it::close }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableDBCloseOnStop() {
|
fun disableDBCloseOnStop() {
|
||||||
|
@ -15,7 +15,22 @@
|
|||||||
</Properties>
|
</Properties>
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||||
<PatternLayout pattern="[%-5level] %date{HH:mm:ss,SSS} [%t] (%F:%L) %c{2}.%method - %msg %equals{%X}{{}}{}%n"/>
|
<PatternLayout>
|
||||||
|
<ScriptPatternSelector defaultPattern="[%-5level] %date{HH:mm:ss,SSS} [%t] (%F:%L) %c{2}.%method - %msg%n">
|
||||||
|
<Script name="MDCSelector" language="javascript"><![CDATA[
|
||||||
|
result = null;
|
||||||
|
if (!logEvent.getContextData().size() == 0) {
|
||||||
|
result = "WithMDC";
|
||||||
|
} else {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
result;
|
||||||
|
]]>
|
||||||
|
</Script>
|
||||||
|
<PatternMatch key="WithMDC" pattern="[%-5level] %date{HH:mm:ss,SSS} [%t] (%F:%L) %c{2}.%method - %msg %X%n"/>
|
||||||
|
</ScriptPatternSelector>
|
||||||
|
</PatternLayout>
|
||||||
|
<ThresholdFilter level="trace"/>
|
||||||
</Console>
|
</Console>
|
||||||
<!-- Required for printBasicInfo -->
|
<!-- Required for printBasicInfo -->
|
||||||
<Console name="Console-Appender-Println" target="SYSTEM_OUT">
|
<Console name="Console-Appender-Println" target="SYSTEM_OUT">
|
||||||
|
@ -18,7 +18,22 @@
|
|||||||
|
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||||
<PatternLayout pattern="[%-5level] %date{HH:mm:ss,SSS} [%t] (%F:%L) %c{2}.%method - %msg %equals{%X}{{}}{}%n" />
|
<PatternLayout>
|
||||||
|
<ScriptPatternSelector defaultPattern="[%-5level] %date{HH:mm:ss,SSS} [%t] (%F:%L) %c{2}.%method - %msg%n">
|
||||||
|
<Script name="MDCSelector" language="javascript"><![CDATA[
|
||||||
|
result = null;
|
||||||
|
if (!logEvent.getContextData().size() == 0) {
|
||||||
|
result = "WithMDC";
|
||||||
|
} else {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
result;
|
||||||
|
]]>
|
||||||
|
</Script>
|
||||||
|
<PatternMatch key="WithMDC" pattern="[%-5level] %date{HH:mm:ss,SSS} [%t] (%F:%L) %c{2}.%method - %msg %X%n"/>
|
||||||
|
</ScriptPatternSelector>
|
||||||
|
</PatternLayout>
|
||||||
|
<ThresholdFilter level="trace"/>
|
||||||
</Console>
|
</Console>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user