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:
sollecitom 2018-05-29 17:17:08 +01:00
commit de0c69a888
57 changed files with 1063 additions and 927 deletions

2
.idea/compiler.xml generated
View File

@ -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" />

View File

@ -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) {

View File

@ -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 -->

View File

@ -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.

View File

@ -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.

View File

@ -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
----------------- -----------------

View File

@ -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)

View File

@ -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)))

View File

@ -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))
}
} }
} }

View File

@ -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)
} }

View File

@ -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,

View File

@ -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}" }
}
} }

View File

@ -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>()

View File

@ -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<*>?

View File

@ -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()}."
}
} }
} }
} }

View File

@ -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 {

View File

@ -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<*>>()

View File

@ -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())
} }
} }

View File

@ -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 {

View File

@ -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

View File

@ -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())
}
} }
} }
} }

View File

@ -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() }
} }

View File

@ -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) }
} }
} }

View File

@ -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(

View File

@ -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
*/ */

View File

@ -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)
} }

View File

@ -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) {

View File

@ -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)
} }

View File

@ -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))
}
}
}

View File

@ -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)
} }

View File

@ -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)

View File

@ -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!!)
} }

View File

@ -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

View File

@ -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

View File

@ -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()
} }

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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()

View File

@ -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

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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())
}
} }
} }
} }

View File

@ -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) }

View File

@ -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

View File

@ -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)
}
} }

View File

@ -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}")

View File

@ -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
} }

View File

@ -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() {

View File

@ -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">

View File

@ -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>