diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 9844574b57..03aafa00c9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -98,6 +98,8 @@ + + diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index 12673dfb3c..0ab4ed96df 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -31,7 +31,7 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger 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.Subscription import rx.subjects.PublishSubject @@ -202,8 +202,11 @@ class NodeMonitorModel { val nodeInfo = _connection.proxy.nodeInfo() require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty()) _connection - } catch(secEx: ActiveMQSecurityException) { - // Happens when incorrect credentials provided - no point to retry connecting. + } catch(secEx: ActiveMQException) { + // Happens when: + // * incorrect credentials provided; + // * incorrect endpoint specified; + // - no point to retry connecting. throw secEx } catch(th: Throwable) { diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index d71b403691..c3da2f3636 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -23,7 +23,22 @@ - + + + + + + + diff --git a/docs/source/api-testing.rst b/docs/source/api-testing.rst index 5169f7b3b5..6c6ee9ddf5 100644 --- a/docs/source/api-testing.rst +++ b/docs/source/api-testing.rst @@ -252,26 +252,6 @@ The network must then be manually run before retrieving the future's value: 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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -281,15 +261,11 @@ Recorded states can be retrieved from the vault of a ``StartedMockNode`` using: .. sourcecode:: kotlin - nodeA.database.transaction { - val myStates = nodeA.services.vaultService.queryBy().states - } + val myStates = nodeA.services.vaultService.queryBy().states .. sourcecode:: java - node.getDatabase().transaction(tx -> { - List myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates(); - } + List 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. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 9bef076789..31831a7f17 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -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. +* ``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 the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file` and :doc:`permissioning` for details. diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index f40c62af5f..a81d9b1110 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -6,7 +6,7 @@ Software requirements Corda uses industry-standard tools: * **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** 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 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,16 +92,22 @@ Run from the command prompt Run from IntelliJ ^^^^^^^^^^^^^^^^^ 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". -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 -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. -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/ +3. Once the project is open, click ``File``, then ``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" +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/ .. _mac-label: @@ -128,7 +134,7 @@ Download a sample project ^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Open a terminal 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 ^^^^^^^^^^^^^^^^^^^^^ @@ -140,13 +146,22 @@ Run from the terminal Run from IntelliJ ^^^^^^^^^^^^^^^^^ 1. Open IntelliJ Community Edition -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 -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. -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/ +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! + +3. Once the project is open, click ``File``, then ``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" +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 ----------------- diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 5a6cc539f9..969e89a721 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -54,7 +54,7 @@ enum class TransactionIsolationLevel { val jdbcValue: Int = java.sql.Connection::class.java.getField(jdbcString).get(null) as Int } -private val _contextDatabase = ThreadLocal() +private val _contextDatabase = InheritableThreadLocal() var contextDatabase: CordaPersistence get() = _contextDatabase.get() ?: error("Was expecting to find CordaPersistence set on current thread: ${Strand.currentStrand()}") set(database) = _contextDatabase.set(database) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 26ed5e119b..80ef21c815 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -16,7 +16,11 @@ import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef 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.NotaryException 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.startFlow import org.hamcrest.Matchers.instanceOf -import org.junit.* +import org.junit.AfterClass import org.junit.Assert.assertThat +import org.junit.BeforeClass +import org.junit.Test import java.nio.file.Paths import java.time.Duration import java.time.Instant 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.assertFailsWith import kotlin.test.assertTrue @@ -127,9 +145,7 @@ class BFTNotaryServiceTests { val issueTx = signInitialTransaction(notary) { addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint) } - database.transaction { - services.recordTransactions(issueTx) - } + services.recordTransactions(issueTx) val spendTxs = (1..10).map { signInitialTransaction(notary) { addInputState(issueTx.tx.outRef(0)) @@ -171,9 +187,7 @@ class BFTNotaryServiceTests { val issueTx = signInitialTransaction(notary) { addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint) } - database.transaction { - services.recordTransactions(issueTx) - } + services.recordTransactions(issueTx) val spendTx = signInitialTransaction(notary) { addInputState(issueTx.tx.outRef(0)) setTimeWindow(TimeWindow.fromOnly(Instant.MAX)) @@ -188,7 +202,7 @@ class BFTNotaryServiceTests { } } - @Test + @Test fun `notarise issue tx with time-window`() { node.run { val issueTx = signInitialTransaction(notary) { @@ -209,9 +223,7 @@ class BFTNotaryServiceTests { val issueTx = signInitialTransaction(notary) { addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint) } - database.transaction { - services.recordTransactions(issueTx) - } + services.recordTransactions(issueTx) val spendTx = signInitialTransaction(notary) { addInputState(issueTx.tx.outRef(0)) setTimeWindow(TimeWindow.untilOnly(Instant.now() + Duration.ofHours(1))) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index e6d98c3e20..3bfa4ed2bb 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -21,10 +21,10 @@ import net.corda.core.internal.concurrent.map import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow 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.core.DUMMY_BANK_A_NAME import net.corda.testing.core.dummyCommand +import net.corda.testing.core.singleIdentity import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.driver.internal.InProcessImpl @@ -89,22 +89,17 @@ class RaftNotaryServiceTests : IntegrationTest() { notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3))) )) { 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)) - .setTimeWindow(bankA.services.clock.instant(), 30.seconds) - bankA.services.signInitialTransaction(builder) - } + val builder = DummyContract.generateInitial(Random().nextInt(), defaultNotaryIdentity, bankA.services.myInfo.singleIdentity().ref(0)) + .setTimeWindow(bankA.services.clock.instant(), 30.seconds) + val issueTx = bankA.services.signInitialTransaction(builder) bankA.startFlow(NotaryFlow.Client(issueTx)).getOrThrow() - } + } } 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) - nodeHandle.services.recordTransactions(stx) - StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0)) - } + val builder = DummyContract.generateInitial(Random().nextInt(), notary, nodeHandle.services.myInfo.singleIdentity().ref(0)) + val stx = nodeHandle.services.signInitialTransaction(builder) + nodeHandle.services.recordTransactions(stx) + return StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0)) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index 67a13063fd..c26cb269bb 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -19,7 +19,13 @@ import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.testing.internal.IntegrationTestSchemas 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 org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -58,12 +64,10 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { fun `unknown legal name`() { val alice = startNodesWithPort(listOf(ALICE))[0] val netMapCache = alice.services.networkMapCache - alice.database.transaction { - assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).isEmpty() - assertThat(netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME)).isNull() - assertThat(netMapCache.getPeerByLegalName(DUMMY_NOTARY_NAME)).isNull() - assertThat(netMapCache.getPeerCertificateByLegalName(DUMMY_NOTARY_NAME)).isNull() - } + assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).isEmpty() + assertThat(netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME)).isNull() + assertThat(netMapCache.getPeerByLegalName(DUMMY_NOTARY_NAME)).isNull() + assertThat(netMapCache.getPeerCertificateByLegalName(DUMMY_NOTARY_NAME)).isNull() } @Test @@ -71,48 +75,40 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { val alice = startNodesWithPort(listOf(ALICE))[0] val netMapCache = alice.services.networkMapCache - val distServiceNodeInfos = alice.database.transaction { - val distributedIdentity = TestIdentity(DUMMY_NOTARY_NAME).identity - (1..2).map { - val nodeInfo = NodeInfo( - addresses = listOf(NetworkHostAndPort("localhost", 1000 + it)), - legalIdentitiesAndCerts = listOf(TestIdentity.fresh("Org-$it").identity, distributedIdentity), - platformVersion = 3, - serial = 1 - ) - netMapCache.addNode(nodeInfo) - nodeInfo - } + val distributedIdentity = TestIdentity(DUMMY_NOTARY_NAME).identity + val distServiceNodeInfos = (1..2).map { + val nodeInfo = NodeInfo( + addresses = listOf(NetworkHostAndPort("localhost", 1000 + it)), + legalIdentitiesAndCerts = listOf(TestIdentity.fresh("Org-$it").identity, distributedIdentity), + platformVersion = 3, + serial = 1 + ) + netMapCache.addNode(nodeInfo) + nodeInfo } - alice.database.transaction { - assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).containsOnlyElementsOf(distServiceNodeInfos) - assertThatExceptionOfType(IllegalArgumentException::class.java) - .isThrownBy { netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME) } - .withMessageContaining(DUMMY_NOTARY_NAME.toString()) - } + assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).containsOnlyElementsOf(distServiceNodeInfos) + assertThatExceptionOfType(IllegalArgumentException::class.java) + .isThrownBy { netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME) } + .withMessageContaining(DUMMY_NOTARY_NAME.toString()) } @Test fun `get nodes by owning key and by name`() { val alice = startNodesWithPort(listOf(ALICE))[0] val netCache = alice.services.networkMapCache - alice.database.transaction { - val res = netCache.getNodeByLegalIdentity(alice.info.singleIdentity()) - assertEquals(alice.info, res) - val res2 = netCache.getNodeByLegalName(DUMMY_REGULATOR.name) - assertEquals(infos.singleOrNull { DUMMY_REGULATOR.name in it.legalIdentities.map { it.name } }, res2) - } + val res = netCache.getNodeByLegalIdentity(alice.info.singleIdentity()) + assertEquals(alice.info, res) + val res2 = netCache.getNodeByLegalName(DUMMY_REGULATOR.name) + assertEquals(infos.singleOrNull { DUMMY_REGULATOR.name in it.legalIdentities.map { it.name } }, res2) } @Test fun `get nodes by address`() { val alice = startNodesWithPort(listOf(ALICE))[0] val netCache = alice.services.networkMapCache - alice.database.transaction { - val res = netCache.getNodeByAddress(alice.info.addresses[0]) - assertEquals(alice.info, res) - } + val res = netCache.getNodeByAddress(alice.info.addresses[0]) + assertEquals(alice.info, res) } // 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 aliceCache = aliceNode.services.networkMapCache aliceCache.addNode(aliceNode.info.copy(legalIdentitiesAndCerts = listOf(charliePartyCert))) - val res = aliceNode.database.transaction { - aliceCache.allNodes.filter { aliceNode.info.addresses[0] in it.addresses } - } + val res = aliceCache.allNodes.filter { aliceNode.info.addresses[0] in it.addresses } assertEquals(2, res.size) } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index b3629b1c95..5b7172bf4e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -30,6 +30,7 @@ import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.NotaryChangeFlow import net.corda.core.flows.NotaryFlow import net.corda.core.flows.StartableByService +import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party 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.ServiceHub 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.CordaService 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.SingletonSerializeAsToken import net.corda.core.serialization.serialize -import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.days import net.corda.core.utilities.debug @@ -177,6 +176,8 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit.SECONDS +import java.util.concurrent.atomic.AtomicReference import kotlin.collections.set import kotlin.reflect.KClass import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair @@ -250,9 +251,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, @Volatile private var _started: StartedNode? = null /** 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>() // Mind that order is relevant here. proxies += ::AuthenticatedRpcOpsProxy @@ -279,7 +280,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration, initCertificate() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) 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() + 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 { // TODO The fact that we need to specify an empty list of notaries just to generate our node info looks // like a design smell. @@ -302,8 +308,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration, initialiseJVMAgents() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != 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() + + // 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) } val networkParameteresReader = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory) 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" } - // Do all of this in a database transaction so anything that might need a connection has one. - val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService).transaction { - val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService) + val (startedImpl, schedulerService) = database.transaction { + val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService, database) val (keyPairs, nodeInfo) = updateNodeInfo(networkMapCache, networkMapClient, identity, identityKeyPair) identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts) val metrics = MetricRegistry() val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes) 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") val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations) log.debug("Cordapp provider created") @@ -371,7 +384,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } makeVaultObservers(schedulerService, database.hibernateConfig, schemaService, flowLogicRefFactory) - val rpcOps = makeRPCOps(flowStarter, database, smm) + val rpcOps = makeRPCOps(flowStarter, smm) startMessagingService(rpcOps) installCoreFlows() val cordaServices = installCordaServices(flowStarter) @@ -735,7 +748,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, networkParameters: NetworkParameters): MutableList { checkpointStorage = DBCheckpointStorage() - val keyManagementService = makeKeyManagementService(identityService, keyPairs) + val keyManagementService = makeKeyManagementService(identityService, keyPairs, database) _services = ServiceHubInternalImpl( identityService, keyManagementService, @@ -757,7 +770,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, 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) { ScheduledActivityObserver.install(services.vaultService, schedulerService, flowLogicRefFactory) 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. 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 { val driverClasses = DriverManager.getDrivers().asSequence().map { it.javaClass.name } "Available JDBC drivers: $driverClasses" @@ -806,7 +821,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val props = configuration.dataSourceProperties 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. logVendorString(database, log) runOnStop += database::close @@ -832,8 +847,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set): KeyManagementService { - return PersistentKeyManagementService(identityService, keyPairs) + protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set, database: CordaPersistence): KeyManagementService { + return PersistentKeyManagementService(identityService, keyPairs, database) } 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 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 @@ -945,8 +960,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } protected open fun generateKeyPair() = cryptoGenerateKeyPair() - protected open fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration): VaultServiceInternal { - return NodeVaultService(platformClock, keyManagementService, services, hibernateConfig) + protected open fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration, database: CordaPersistence): VaultServiceInternal { + return NodeVaultService(platformClock, keyManagementService, services, hibernateConfig, database) } /** Load configured JVM agents */ @@ -983,10 +998,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private val servicesForResolution: ServicesForResolution ) : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution { override val rpcFlows = ArrayList>>() - override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() + override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database) override val auditService = DummyAuditService() 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 attachments: AttachmentStorage get() = this@AbstractNode.attachments override val networkService: MessagingService get() = network @@ -1002,12 +1017,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return flowFactories[initiatingFlowClass] } - override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { - database.transaction { - super.recordTransactions(statesToRecord, txs) - } - } - override fun jdbcSession(): Connection = database.createSession() // 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, databaseConfig: DatabaseConfig, - identityService: IdentityService, + wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, + wellKnownPartyFromAnonymous: (AbstractParty) -> Party?, schemaService: SchemaService = NodeSchemaService()): CordaPersistence { // 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 // 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. - JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService)) + JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) val dataSource = DataSourceFactory.createDataSource(hikariProperties) - val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(identityService)) + val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) val jdbcUrl = hikariProperties.getProperty("dataSource.url", "") SchemaMigration( schemaService.schemaOptions.keys, diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 339fc7b184..b686c2fcaa 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -27,12 +27,26 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.RPC_UPLOADER import net.corda.core.internal.STRUCTURAL_STEP_PREFIX 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.services.AttachmentId 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.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.transactions.SignedTransaction 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.nodeapi.exceptions.NonRpcFlowException import net.corda.nodeapi.exceptions.RejectedCommandException -import net.corda.nodeapi.internal.persistence.CordaPersistence import rx.Observable import java.io.InputStream import java.security.PublicKey @@ -55,7 +68,6 @@ import java.time.Instant internal class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, - private val database: CordaPersistence, private val flowStarter: FlowStarter, private val shutdownNode: () -> Unit ) : CordaRPCOps { @@ -78,18 +90,15 @@ internal class CordaRPCOpsImpl( } override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> { - return database.transaction { - services.networkMapCache.track() - } + return services.networkMapCache.track() } override fun vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): Vault.Page { - return database.transaction { - services.vaultService._queryBy(criteria, paging, sorting, contractStateType) - } + contractStateType.checkIsA() + return services.vaultService._queryBy(criteria, paging, sorting, contractStateType) } @RPCReturnsObservables @@ -97,9 +106,8 @@ internal class CordaRPCOpsImpl( paging: PageSpecification, sorting: Sort, contractStateType: Class): DataFeed, Vault.Update> { - return database.transaction { - services.vaultService._trackBy(criteria, paging, sorting, contractStateType) - } + contractStateType.checkIsA() + return services.vaultService._trackBy(criteria, paging, sorting, contractStateType) } @Suppress("OverridingDeprecatedMember") @@ -111,9 +119,7 @@ internal class CordaRPCOpsImpl( @Suppress("OverridingDeprecatedMember") override fun internalVerifiedTransactionsFeed(): DataFeed, SignedTransaction> { - return database.transaction { - services.validatedTransactions.track() - } + return services.validatedTransactions.track() } override fun stateMachinesSnapshot(): List { @@ -125,13 +131,11 @@ internal class CordaRPCOpsImpl( override fun killFlow(id: StateMachineRunId) = smm.killFlow(id) override fun stateMachinesFeed(): DataFeed, StateMachineUpdate> { - return database.transaction { - val (allStateMachines, changes) = smm.track() - DataFeed( - allStateMachines.map { stateMachineInfoFromFlowLogic(it) }, - changes.map { stateMachineUpdateFromStateMachineChange(it) } - ) - } + val (allStateMachines, changes) = smm.track() + return DataFeed( + allStateMachines.map { stateMachineInfoFromFlowLogic(it) }, + changes.map { stateMachineUpdateFromStateMachineChange(it) } + ) } override fun stateMachineRecordedTransactionMappingSnapshot(): List { @@ -141,9 +145,7 @@ internal class CordaRPCOpsImpl( } override fun stateMachineRecordedTransactionMappingFeed(): DataFeed, StateMachineTransactionMapping> { - return database.transaction { - services.stateMachineRecordedTransactionMapping.track() - } + return services.stateMachineRecordedTransactionMapping.track() } override fun nodeInfo(): NodeInfo { @@ -155,15 +157,11 @@ internal class CordaRPCOpsImpl( } 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 { - return database.transaction { - services.vaultService.getTransactionNotes(txnId) - } + return services.vaultService.getTransactionNotes(txnId) } override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle { @@ -191,38 +189,23 @@ internal class CordaRPCOpsImpl( } override fun attachmentExists(id: SecureHash): Boolean { - // TODO: this operation should not require an explicit transaction - return database.transaction { - services.attachments.openAttachment(id) != null - } + return services.attachments.openAttachment(id) != null } override fun openAttachment(id: SecureHash): InputStream { - // TODO: this operation should not require an explicit transaction - return database.transaction { - services.attachments.openAttachment(id)!!.open() - } + return services.attachments.openAttachment(id)!!.open() } override fun uploadAttachment(jar: InputStream): SecureHash { - // TODO: this operation should not require an explicit transaction - return database.transaction { - services.attachments.importAttachment(jar, RPC_UPLOADER, null) - } + return services.attachments.importAttachment(jar, RPC_UPLOADER, null) } - override fun uploadAttachmentWithMetadata(jar: InputStream, uploader:String, filename:String): SecureHash { - // TODO: this operation should not require an explicit transaction - return database.transaction { - services.attachments.importAttachment(jar, uploader, filename) - } + override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash { + return services.attachments.importAttachment(jar, uploader, filename) } override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List { - // TODO: this operation should not require an explicit transaction - return database.transaction { - services.attachments.queryAttachments(query, sorting) - } + return services.attachments.queryAttachments(query, sorting) } override fun currentNodeTime(): Instant = Instant.now(services.clock) @@ -230,43 +213,31 @@ internal class CordaRPCOpsImpl( override fun waitUntilNetworkReady(): CordaFuture = services.networkMapCache.nodeReady override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { - return database.transaction { - services.identityService.wellKnownPartyFromAnonymous(party) - } + return services.identityService.wellKnownPartyFromAnonymous(party) } override fun partyFromKey(key: PublicKey): Party? { - return database.transaction { - services.identityService.partyFromKey(key) - } + return services.identityService.partyFromKey(key) } override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? { - return database.transaction { - services.identityService.wellKnownPartyFromX500Name(x500Name) - } + return services.identityService.wellKnownPartyFromX500Name(x500Name) } override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? = services.networkMapCache.getNotary(x500Name) override fun partiesFromName(query: String, exactMatch: Boolean): Set { - return database.transaction { - services.identityService.partiesFromName(query, exactMatch) - } + return services.identityService.partiesFromName(query, exactMatch) } override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? { - return database.transaction { - services.networkMapCache.getNodeByLegalIdentity(party) - } + return services.networkMapCache.getNodeByLegalIdentity(party) } override fun registeredFlows(): List = services.rpcFlows.map { it.name }.sorted() override fun clearNetworkMapCache() { - database.transaction { - services.networkMapCache.clearNetworkMapCache() - } + services.networkMapCache.clearNetworkMapCache() } override fun vaultQuery(contractStateType: Class): Vault.Page { @@ -325,14 +296,25 @@ internal class CordaRPCOpsImpl( } private fun InvocationContext.toFlowInitiator(): FlowInitiator { - val principal = origin.principal().name return when (origin) { 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) InvocationOrigin.Shell -> FlowInitiator.Shell 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 Class<*>.checkIsA() { + require(TARGET::class.java.isAssignableFrom(this)) { "$name is not a ${TARGET::class.java.name}" } + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index bf60b130c5..9970060018 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -13,7 +13,9 @@ package net.corda.node.internal import com.codahale.metrics.JmxReporter import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.core.concurrent.CordaFuture +import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.Emoji import net.corda.core.internal.concurrent.openFuture 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.NodeInfo import net.corda.core.node.ServiceHub -import net.corda.core.node.services.IdentityService import net.corda.core.node.services.TransactionVerifierService import net.corda.core.serialization.internal.SerializationEnvironmentImpl 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 * 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 h2Prefix = "jdbc:h2:file:" if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) { @@ -363,7 +366,7 @@ open class Node(configuration: NodeConfiguration, else if (databaseUrl != null) { printBasicNodeInfo("Database connection url is", databaseUrl) } - return super.initialiseDatabasePersistence(schemaService, identityService) + return super.initialiseDatabasePersistence(schemaService, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous) } private val _startupComplete = openFuture() diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 8239c4b36d..5035c6024f 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -34,6 +34,7 @@ import net.corda.node.services.network.NetworkMapUpdater import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.contextDatabase interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { @@ -62,55 +63,58 @@ interface ServiceHubInternal : ServiceHub { fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable, validatedTransactions: WritableTransactionStorage, stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage, - vaultService: VaultServiceInternal) { + vaultService: VaultServiceInternal, + database: CordaPersistence) { - require(txs.any()) { "No transactions passed in for recording" } - val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) } - val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id - if (stateMachineRunId != null) { - recordedTransactions.forEach { - stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id) + database.transaction { + require(txs.any()) { "No transactions passed in for recording" } + val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) } + val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id + if (stateMachineRunId != null) { + 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) { - // 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 - // is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net). - // - // The reason for this is three-fold: - // - // 1) We are putting in place the observer mode feature relatively quickly to meet specific customer - // launch target dates. - // - // 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. - // - // Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the - // Bitcoin equivalent of observer nodes: - // - // 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 - // 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 - // 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 - // 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. - // - // 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 - // 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. - // - // 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. - vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction }) + if (statesToRecord != StatesToRecord.NONE) { + // 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 + // is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net). + // + // The reason for this is three-fold: + // + // 1) We are putting in place the observer mode feature relatively quickly to meet specific customer + // launch target dates. + // + // 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. + // + // Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the + // Bitcoin equivalent of observer nodes: + // + // 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 + // 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 + // 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 + // 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. + // + // 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 + // 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. + // + // 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. + vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction }) + } } } } @@ -135,7 +139,7 @@ interface ServiceHubInternal : ServiceHub { val networkMapUpdater: NetworkMapUpdater override val cordappProvider: CordappProviderInternal override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { - recordTransactions(statesToRecord, txs, validatedTransactions, stateMachineRecordedTransactionMapping, vaultService) + recordTransactions(statesToRecord, txs, validatedTransactions, stateMachineRecordedTransactionMapping, vaultService, database) } fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 9490751842..f74e890c04 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -26,6 +26,7 @@ import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates +import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import java.io.Serializable @@ -48,6 +49,7 @@ import javax.persistence.Lob // TODO There is duplicated logic between this and InMemoryIdentityService @ThreadSafe class PersistentIdentityService(override val trustRoot: X509Certificate, + private val database: CordaPersistence, caCertificates: List = emptyList()) : SingletonSerializeAsToken(), IdentityServiceInternal { companion object { @@ -120,72 +122,82 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, /** Requires a database transaction. */ fun loadIdentities(identities: Iterable = emptySet(), confidentialIdentities: Iterable = emptySet()) { - identities.forEach { - val key = mapToKey(it) - keyToParties.addWithDuplicatesAllowed(key, it, false) - principalToParties.addWithDuplicatesAllowed(it.name, key, false) + database.transaction { + identities.forEach { + val key = mapToKey(it) + 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) override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { - // Validate the chain first, before we do anything clever with it - val identityCertChain = identity.certPath.x509Certificates - try { - identity.verify(trustAnchor) - } catch (e: CertPathValidatorException) { - log.warn(e.localizedMessage) - log.warn("Path = ") - identityCertChain.reversed().forEach { - log.warn(it.subjectX500Principal.toString()) + return database.transaction { + + // Validate the chain first, before we do anything clever with it + val identityCertChain = identity.certPath.x509Certificates + try { + identity.verify(trustAnchor) + } catch (e: CertPathValidatorException) { + log.warn(e.localizedMessage) + 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 - val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false } - if (wellKnownCert != identity.certificate) { - val idx = identityCertChain.lastIndexOf(wellKnownCert) - val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size)) - verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) - } + // Ensure we record the first identity of the same name, first + val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false } + if (wellKnownCert != identity.certificate) { + val idx = identityCertChain.lastIndexOf(wellKnownCert) + val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size)) + verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) + } - log.debug { "Registering identity $identity" } - val key = mapToKey(identity) - keyToParties.addWithDuplicatesAllowed(key, identity, false) - // Always keep the first party we registered, as that's the well known identity - principalToParties.addWithDuplicatesAllowed(identity.name, key, false) - val parentId = mapToKey(identityCertChain[1].publicKey) - return keyToParties[parentId] + log.debug { "Registering identity $identity" } + val key = mapToKey(identity) + keyToParties.addWithDuplicatesAllowed(key, identity, false) + // Always keep the first party we registered, as that's the well known identity + principalToParties.addWithDuplicatesAllowed(identity.name, key, false) + val parentId = mapToKey(identityCertChain[1].publicKey) + 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? { - val partyId = principalToParties[name] - return if (partyId != null) { - keyToParties[partyId] - } else null + return database.transaction { + val partyId = principalToParties[name] + if (partyId != null) { + keyToParties[partyId] + } else null + } } // We give the caller a copy of the data set to avoid any locking problems - override fun getAllIdentities(): Iterable = keyToParties.allPersisted().map { it.second }.asIterable() + override fun getAllIdentities(): Iterable = database.transaction { keyToParties.allPersisted().map { it.second }.asIterable() } override fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.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), - // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key - // into a party, and from there figure out the well known party. - val candidate = partyFromKey(party.owningKey) - // TODO: This should be done via the network map cache, which is the authoritative source of well known identities - return if (candidate != null) { - wellKnownPartyFromX500Name(candidate.name) - } else { - null + return database.transaction { + // The original version of this would return the party as-is if it was a Party (rather than AnonymousParty), + // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key + // into a party, and from there figure out the well known party. + val candidate = partyFromKey(party.owningKey) + // TODO: This should be done via the network map cache, which is the authoritative source of well known identities + if (candidate != null) { + wellKnownPartyFromX500Name(candidate.name) + } else { + null + } } } @@ -195,20 +207,23 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, } override fun partiesFromName(query: String, exactMatch: Boolean): Set { - val results = LinkedHashSet() - for ((x500name, partyId) in principalToParties.allPersisted()) { - partiesFromName(query, exactMatch, x500name, results, keyToParties[partyId]!!.party) + return database.transaction { + val results = LinkedHashSet() + for ((x500name, partyId) in principalToParties.allPersisted()) { + partiesFromName(query, exactMatch, x500name, results, keyToParties[partyId]!!.party) + } + results } - return results } @Throws(UnknownAnonymousPartyException::class) override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) { - val anonymousIdentity = certificateFromKey(anonymousParty.owningKey) ?: - throw UnknownAnonymousPartyException("Unknown $anonymousParty") - val issuingCert = anonymousIdentity.certPath.certificates[1] - require(issuingCert.publicKey == party.owningKey) { - "Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}." + database.transaction { + val anonymousIdentity = certificateFromKey(anonymousParty.owningKey) ?: throw UnknownAnonymousPartyException("Unknown $anonymousParty") + val issuingCert = anonymousIdentity.certPath.certificates[1] + require(issuingCert.publicKey == party.owningKey) { + "Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}." + } } } } diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index 3d06fef7e7..a39ff6140b 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -17,6 +17,7 @@ import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY 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. */ class PersistentKeyManagementService(val identityService: IdentityService, - initialKeys: Set) : SingletonSerializeAsToken(), KeyManagementService { + initialKeys: Set, + private val database: CordaPersistence) : SingletonSerializeAsToken(), KeyManagementService { @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs") @@ -76,17 +78,23 @@ class PersistentKeyManagementService(val identityService: IdentityService, val keysMap = createKeyMap() 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 get() = keysMap.allPersisted().map { it.first }.toSet() + override val keys: Set get() = database.transaction { keysMap.allPersisted().map { it.first }.toSet() } - override fun filterMyKeys(candidateKeys: Iterable): Iterable = - candidateKeys.filter { keysMap[it] != null } + override fun filterMyKeys(candidateKeys: Iterable): Iterable = database.transaction { + candidateKeys.filter { keysMap[it] != null } + } override fun freshKey(): PublicKey { val keyPair = generateKeyPair() - keysMap[keyPair.public] = keyPair.private + database.transaction { + keysMap[keyPair.public] = keyPair.private + } 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 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 KeyPair(pk, keysMap[pk]!!) + return database.transaction { + 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 { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index a9e7e4885d..7ddba49ca3 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -163,9 +163,7 @@ class P2PMessagingClient(val config: NodeConfiguration, fun sendMessage(address: String, message: ClientMessage) = producer!!.send(address, message) } - private val messagesToRedeliver = database.transaction { - createMessageToRedeliver() - } + private val messagesToRedeliver = createMessageToRedeliver() private val scheduledMessageRedeliveries = ConcurrentHashMap>() diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index edffa8fbe9..e80e998d6f 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -48,8 +48,9 @@ import kotlin.collections.HashSet class NetworkMapCacheImpl( networkMapCacheBase: NetworkMapCacheBaseInternal, - private val identityService: IdentityService -) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { + private val identityService: IdentityService, + private val database: CordaPersistence +) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal, SingletonSerializeAsToken() { companion object { private val logger = loggerFor() } @@ -72,9 +73,11 @@ class NetworkMapCacheImpl( } override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { - val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party) - return wellKnownParty?.let { - getNodesByLegalIdentityKey(it.owningKey).firstOrNull() + return database.transaction { + val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party) + wellKnownParty?.let { + getNodesByLegalIdentityKey(it.owningKey).firstOrNull() + } } } } @@ -193,7 +196,7 @@ open class PersistentNetworkMapCache( override fun track(): DataFeed, MapChange> { 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()) } } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt index a32d21505d..96c2bbde32 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt @@ -12,22 +12,23 @@ package net.corda.node.services.persistence import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party import net.corda.core.internal.uncheckedCast -import net.corda.core.node.services.IdentityService import net.corda.core.utilities.contextLogger import org.hibernate.type.descriptor.WrapperOptions import org.hibernate.type.descriptor.java.AbstractTypeDescriptor import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan import org.hibernate.type.descriptor.java.MutabilityPlan -class AbstractPartyDescriptor(private val identityService: IdentityService) : AbstractTypeDescriptor(AbstractParty::class.java) { +class AbstractPartyDescriptor(private val wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, + private val wellKnownPartyFromAnonymous: (AbstractParty) -> Party?) : AbstractTypeDescriptor(AbstractParty::class.java) { companion object { private val log = contextLogger() } override fun fromString(dbData: String?): AbstractParty? { 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") party } else { @@ -39,7 +40,7 @@ class AbstractPartyDescriptor(private val identityService: IdentityService) : Ab override fun toString(party: AbstractParty?): String? { 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") partyName.toString() } else { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt index 54511e037c..9ac1b4bc43 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt @@ -12,7 +12,7 @@ package net.corda.node.services.persistence import net.corda.core.identity.AbstractParty 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 javax.persistence.AttributeConverter import javax.persistence.Converter @@ -22,14 +22,15 @@ import javax.persistence.Converter * Completely anonymous parties are stored as null (to preserve privacy). */ @Converter(autoApply = true) -class AbstractPartyToX500NameAsStringConverter(private val identityService: IdentityService) : AttributeConverter { +class AbstractPartyToX500NameAsStringConverter(private val wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, + private val wellKnownPartyFromAnonymous: (AbstractParty) -> Party?) : AttributeConverter { companion object { private val log = contextLogger() } override fun convertToDatabaseColumn(party: AbstractParty?): String? { if (party != null) { - val partyName = identityService.wellKnownPartyFromAnonymous(party)?.toString() + val partyName = wellKnownPartyFromAnonymous(party)?.toString() if (partyName != null) return partyName 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? { if (dbData != null) { - val party = identityService.wellKnownPartyFromX500Name(CordaX500Name.parse(dbData)) + val party = wellKnownPartyFromX500Name(CordaX500Name.parse(dbData)) if (party != null) return party } return null // non resolvable anonymous parties are stored as nulls diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt index 55110aa88d..3ac3bd8d52 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt @@ -18,6 +18,7 @@ import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage 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.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction @@ -36,7 +37,7 @@ import javax.persistence.Id * RPC API to correlate transaction creation with flows. */ @ThreadSafe -class DBTransactionMappingStorage : StateMachineRecordedTransactionMappingStorage { +class DBTransactionMappingStorage(private val database: CordaPersistence) : StateMachineRecordedTransactionMappingStorage { @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}transaction_mappings") @@ -73,16 +74,20 @@ class DBTransactionMappingStorage : StateMachineRecordedTransactionMappingStorag private val concurrentBox = ConcurrentBox(InnerState()) override fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash) { - concurrentBox.concurrent { - stateMachineTransactionMap.addWithDuplicatesAllowed(transactionId, stateMachineRunId) - updates.bufferUntilDatabaseCommit().onNext(StateMachineTransactionMapping(stateMachineRunId, transactionId)) + database.transaction { + concurrentBox.concurrent { + stateMachineTransactionMap.addWithDuplicatesAllowed(transactionId, stateMachineRunId) + updates.bufferUntilDatabaseCommit().onNext(StateMachineTransactionMapping(stateMachineRunId, transactionId)) + } } } override fun track(): DataFeed, StateMachineTransactionMapping> { - return concurrentBox.exclusive { - DataFeed(stateMachineTransactionMap.allPersisted().map { StateMachineTransactionMapping(it.second, it.first) }.toList(), - updates.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + return database.transaction { + concurrentBox.exclusive { + DataFeed(stateMachineTransactionMap.allPersisted().map { StateMachineTransactionMapping(it.second, it.first) }.toList(), + updates.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + } } } } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index cf8a94439d..77e668228f 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -18,13 +18,18 @@ import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.concurrent.doneFuture import net.corda.core.messaging.DataFeed -import net.corda.core.serialization.* +import net.corda.core.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.transactions.CoreTransaction import net.corda.core.transactions.SignedTransaction import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.utilities.AppendOnlyPersistentMapBase 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.bufferUntilDatabaseCommit 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.subjects.PublishSubject 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 typealias TxCacheValue = Pair, List> + fun TxCacheValue.toSignedTx() = SignedTransaction(this.first, this.second) fun SignedTransaction.toTxCacheValue() = TxCacheValue(this.txBits, this.sigs) -class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, SingletonSerializeAsToken() { +class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPersistence) : WritableTransactionStorage, SingletonSerializeAsToken() { @Entity @Table(name = "${NODE_DATABASE_PREFIX}transactions") @@ -93,35 +103,41 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S private val txStorage = ConcurrentBox(createTransactionsMap(cacheSizeBytes)) - override fun addTransaction(transaction: SignedTransaction): Boolean = - txStorage.concurrent { - addWithDuplicatesAllowed(transaction.id, transaction.toTxCacheValue()).apply { - updatesPublisher.bufferUntilDatabaseCommit().onNext(transaction) - } + override fun addTransaction(transaction: SignedTransaction): Boolean = database.transaction { + txStorage.concurrent { + addWithDuplicatesAllowed(transaction.id, transaction.toTxCacheValue()).apply { + 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().toSerialized() override val updates: Observable = updatesPublisher.wrapWithDatabaseTransaction() override fun track(): DataFeed, SignedTransaction> { - return txStorage.exclusive { - DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updates.bufferUntilSubscribed()) + return database.transaction { + txStorage.exclusive { + DataFeed(allPersisted().map { it.second.toSignedTx() }.toList(), updates.bufferUntilSubscribed()) + } } } override fun trackTransaction(id: SecureHash): CordaFuture { - return txStorage.exclusive { - val existingTransaction = get(id) - if (existingTransaction == null) { - updates.filter { it.id == id }.toFuture() - } else { - doneFuture(existingTransaction.toSignedTx()) + return database.transaction { + txStorage.exclusive { + val existingTransaction = get(id) + if (existingTransaction == null) { + updates.filter { it.id == id }.toFuture() + } else { + doneFuture(existingTransaction.toSignedTx()) + } } } } @VisibleForTesting - val transactions: Iterable get() = txStorage.content.allPersisted().map { it.second.toSignedTx() }.toList() + val transactions: Iterable + get() = database.transaction { txStorage.content.allPersisted().map { it.second.toSignedTx() }.toList() } } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index f6abc1a060..0ad2814b31 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -30,13 +30,18 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.vault.AttachmentQueryCriteria 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.node.services.config.NodeConfiguration import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser import net.corda.node.utilities.NonInvalidatingCache import net.corda.node.utilities.NonInvalidatingWeightBasedCache 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.currentDBSession import net.corda.nodeapi.internal.withContractsInJar @@ -49,7 +54,16 @@ import java.time.Instant import java.util.* import java.util.jar.JarInputStream 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. @@ -58,7 +72,8 @@ import javax.persistence.* class NodeAttachmentService( metrics: MetricRegistry, attachmentContentCacheSize: Long = NodeConfiguration.defaultAttachmentContentCacheSize, - attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound + attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound, + private val database: CordaPersistence ) : AttachmentStorage, SingletonSerializeAsToken( ) { @@ -117,13 +132,15 @@ class NodeAttachmentService( private val attachmentCount = metrics.counter("Attachments") - init { - val session = currentDBSession() - val criteriaBuilder = session.criteriaBuilder - val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) - criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) - val count = session.createQuery(criteriaQuery).singleResult - attachmentCount.inc(count) + fun start() { + database.transaction { + val session = currentDBSession() + val criteriaBuilder = session.criteriaBuilder + val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) + criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) + val count = session.createQuery(criteriaQuery).singleResult + attachmentCount.inc(count) + } } @CordaSerializable @@ -204,10 +221,8 @@ class NodeAttachmentService( } override fun toToken(context: SerializeAsTokenContext) = Token(id, checkOnLoad) - } - // 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 // 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? { - val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) - ?: return null - val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let { - val contracts = attachment.contractClassNames - if (contracts != null && contracts.isNotEmpty()) { - ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader) - } else { - it + return database.transaction { + val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) ?: return@transaction null + val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let { + val contracts = attachment.contractClassNames + if (contracts != null && contracts.isNotEmpty()) { + ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader) + } else { + it + } } + Pair(attachmentImpl, attachment.content) } - return Pair(attachmentImpl, attachment.content) } private val attachmentCache = NonInvalidatingCache>( @@ -273,32 +289,35 @@ class NodeAttachmentService( return import(jar, uploader, filename) } - override fun hasAttachment(attachmentId: AttachmentId): Boolean = - currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null + override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction { + 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. private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId { - return withContractsInJar(jar) { contractClassNames, inputStream -> - require(inputStream !is JarInputStream) + return database.transaction { + withContractsInJar(jar) { contractClassNames, inputStream -> + require(inputStream !is JarInputStream) - // 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. - // 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 - // set the hash field of the new attachment record. + // 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. + // 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 + // set the hash field of the new attachment record. - val bytes = inputStream.readFully() - val id = bytes.sha256() - if (!hasAttachment(id)) { - checkIsAValidJAR(bytes.inputStream()) - val session = currentDBSession() - val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames) - session.save(attachment) - attachmentCount.inc() - log.info("Stored new attachment $id") - id - } else { - throw DuplicateAttachmentException(id.toString()) + val bytes = inputStream.readFully() + val id = bytes.sha256() + if (!hasAttachment(id)) { + checkIsAValidJAR(bytes.inputStream()) + val session = currentDBSession() + val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames) + session.save(attachment) + attachmentCount.inc() + log.info("Stored new attachment $id") + id + } else { + throw DuplicateAttachmentException(id.toString()) + } } } } @@ -312,24 +331,23 @@ class NodeAttachmentService( override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { log.info("Attachment query criteria: $criteria, sorting: $sorting") - val session = currentDBSession() - val criteriaBuilder = session.criteriaBuilder + return database.transaction { + val session = currentDBSession() + val criteriaBuilder = session.criteriaBuilder - val criteriaQuery = criteriaBuilder.createQuery(DBAttachment::class.java) - val root = criteriaQuery.from(DBAttachment::class.java) + val criteriaQuery = criteriaBuilder.createQuery(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 - criteriaParser.parse(criteria, sorting) + // parse criteria and build where predicates + criteriaParser.parse(criteria, sorting) - // prepare query for execution - val query = session.createQuery(criteriaQuery) + // prepare query for execution + val query = session.createQuery(criteriaQuery) - // execution - val results = query.resultList - - return results.map { AttachmentId.parse(it.attId) } + // execution + query.resultList.map { AttachmentId.parse(it.attId) } + } } - } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt index 18ecaa59f4..a4b370c96b 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt @@ -47,6 +47,7 @@ import net.corda.node.services.statemachine.transitions.StateMachine import net.corda.node.services.statemachine.transitions.StateMachineConfiguration import net.corda.node.utilities.AffinityExecutor 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.withTokenContext import org.apache.activemq.artemis.utils.ReusableLatch @@ -185,7 +186,9 @@ class SingleThreadedStateMachineManager( */ override fun track(): DataFeed>, StateMachineManager.Change> { 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") } return serviceHub.getFlowFactory(initiatingFlowClass) ?: - throw SessionRejectException("$initiatingFlowClass is not registered") + throw SessionRejectException("$initiatingFlowClass is not registered") } private fun startInitiatedFlow( diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 6b5056acd9..fa3eedda0f 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -58,7 +58,8 @@ class NodeVaultService( private val clock: Clock, private val keyManagementService: KeyManagementService, private val servicesForResolution: ServicesForResolution, - hibernateConfig: HibernateConfiguration + hibernateConfig: HibernateConfiguration, + private val database: CordaPersistence ) : SingletonSerializeAsToken(), VaultServiceInternal { private companion object { private val log = contextLogger() @@ -235,19 +236,23 @@ class NodeVaultService( } override fun addNoteToTransaction(txnId: SecureHash, noteText: String) { - val txnNoteEntity = VaultSchemaV1.VaultTxnNote(txnId.toString(), noteText) - currentDBSession().save(txnNoteEntity) + database.transaction { + val txnNoteEntity = VaultSchemaV1.VaultTxnNote(txnId.toString(), noteText) + currentDBSession().save(txnNoteEntity) + } } override fun getTransactionNotes(txnId: SecureHash): Iterable { - val session = currentDBSession() - val criteriaBuilder = session.criteriaBuilder - val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultTxnNote::class.java) - val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultTxnNote::class.java) - val txIdPredicate = criteriaBuilder.equal(vaultStates.get(VaultSchemaV1.VaultTxnNote::txId.name), txnId.toString()) - criteriaQuery.where(txIdPredicate) - val results = session.createQuery(criteriaQuery).resultList - return results.asIterable().map { it.note } + return database.transaction { + val session = currentDBSession() + val criteriaBuilder = session.criteriaBuilder + val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultTxnNote::class.java) + val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultTxnNote::class.java) + val txIdPredicate = criteriaBuilder.equal(vaultStates.get(VaultSchemaV1.VaultTxnNote::txId.name), txnId.toString()) + criteriaQuery.where(txIdPredicate) + val results = session.createQuery(criteriaQuery).resultList + results.asIterable().map { it.note } + } } @Throws(StatesNotAvailableException::class) @@ -412,90 +417,94 @@ class NodeVaultService( @Throws(VaultQueryException::class) private fun _queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class, skipPagingChecks: Boolean): Vault.Page { - log.debug {"Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting" } - // calculate total results where a page specification has been defined - var totalStates = -1L - if (!skipPagingChecks && !paging.isDefault) { - val count = builder { VaultSchemaV1.VaultStates::recordedTime.count() } - val countCriteria = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.ALL) - 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 - } + log.debug { "Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting" } + return database.transaction { + // calculate total results where a page specification has been defined + var totalStates = -1L + if (!skipPagingChecks && !paging.isDefault) { + val count = builder { VaultSchemaV1.VaultStates::recordedTime.count() } + val countCriteria = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.ALL) + 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 queryRootVaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java) + val criteriaQuery = criteriaBuilder.createQuery(Tuple::class.java) + val queryRootVaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java) - // TODO: revisit (use single instance of parser for all queries) - val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates) + // TODO: revisit (use single instance of parser for all queries) + val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates) - // parse criteria and build where predicates - criteriaParser.parse(criteria, sorting) + // parse criteria and build where predicates + criteriaParser.parse(criteria, sorting) - // prepare query for execution - val query = session.createQuery(criteriaQuery) + // prepare query for execution + val query = session.createQuery(criteriaQuery) - // pagination checks - if (!skipPagingChecks && !paging.isDefault) { - // 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.pageSize < 1) throw VaultQueryException("Page specification: invalid page size ${paging.pageSize} [must be a value between 1 and $MAX_PAGE_SIZE]") - } + // pagination checks + if (!skipPagingChecks && !paging.isDefault) { + // 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.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.maxResults = paging.pageSize + 1 // detection too many results + 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 - // execution - val results = query.resultList + // execution + val results = query.resultList - // final pagination check (fail-fast on too many results when no pagination specified) - 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]") + // final pagination check (fail-fast on too many results when no pagination specified) + 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]") - val statesAndRefs: MutableList> = mutableListOf() - val statesMeta: MutableList = mutableListOf() - val otherResults: MutableList = mutableListOf() - val stateRefs = mutableSetOf() + val statesAndRefs: MutableList> = mutableListOf() + val statesMeta: MutableList = mutableListOf() + val otherResults: MutableList = mutableListOf() + val stateRefs = mutableSetOf() - results.asSequence() - .forEachIndexed { index, result -> - if (result[0] is VaultSchemaV1.VaultStates) { - if (!paging.isDefault && index == paging.pageSize) // skip last result if paged - return@forEachIndexed - val vaultState = result[0] as VaultSchemaV1.VaultStates - val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!) - stateRefs.add(stateRef) - statesMeta.add(Vault.StateMetadata(stateRef, - vaultState.contractStateClassName, - vaultState.recordedTime, - vaultState.consumedTime, - vaultState.stateStatus, - vaultState.notary, - vaultState.lockId, - vaultState.lockUpdateTime)) - } else { - // TODO: improve typing of returned other results - log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" } - otherResults.addAll(result.toArray().asList()) + results.asSequence() + .forEachIndexed { index, result -> + if (result[0] is VaultSchemaV1.VaultStates) { + if (!paging.isDefault && index == paging.pageSize) // skip last result if paged + return@forEachIndexed + val vaultState = result[0] as VaultSchemaV1.VaultStates + val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!) + stateRefs.add(stateRef) + statesMeta.add(Vault.StateMetadata(stateRef, + vaultState.contractStateClassName, + vaultState.recordedTime, + vaultState.consumedTime, + vaultState.stateStatus, + vaultState.notary, + vaultState.lockId, + vaultState.lockUpdateTime)) + } else { + // TODO: improve typing of returned other results + log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" } + otherResults.addAll(result.toArray().asList()) + } } - } - if (stateRefs.isNotEmpty()) - statesAndRefs.addAll(uncheckedCast(servicesForResolution.loadStates(stateRefs))) + if (stateRefs.isNotEmpty()) + 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) override fun _trackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): DataFeed, Vault.Update> { - return concurrentBox.exclusive { - val snapshotResults = _queryBy(criteria, paging, sorting, contractStateType) - val updates: Observable> = uncheckedCast(_updatesPublisher.bufferUntilSubscribed().filter { it.containsType(contractStateType, snapshotResults.stateTypes) }) - DataFeed(snapshotResults, updates) + return database.transaction { + concurrentBox.exclusive { + val snapshotResults = _queryBy(criteria, paging, sorting, contractStateType) + val updates: Observable> = 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 */ diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 2c7087fc5f..09281fd0f3 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -24,11 +24,8 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.StateMachineUpdate -import net.corda.core.messaging.startFlow -import net.corda.core.messaging.vaultQueryBy -import net.corda.core.messaging.vaultTrackBy +import net.corda.core.internal.uncheckedCast +import net.corda.core.messaging.* import net.corda.core.node.services.Vault import net.corda.core.node.services.queryBy 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.testActor import org.apache.commons.io.IOUtils -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatExceptionOfType +import org.assertj.core.api.Assertions.* import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Before @@ -115,9 +111,12 @@ class CordaRPCOpsImplTest { @Test fun `cash issue accepted`() { - - withPermissions(invokeRpc("vaultTrackBy"), invokeRpc("vaultQueryBy"), invokeRpc(CordaRPCOps::stateMachinesFeed), startFlow()) { - + withPermissions( + invokeRpc("vaultTrackBy"), + invokeRpc("vaultQueryBy"), + invokeRpc(CordaRPCOps::stateMachinesFeed), + startFlow() + ) { aliceNode.database.transaction { stateMachineUpdates = rpc.stateMachinesFeed().updates vaultTrackCash = rpc.vaultTrackBy().updates @@ -168,7 +167,6 @@ class CordaRPCOpsImplTest { @Test fun `issue and move`() { - withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), invokeRpc("vaultTrackBy"), @@ -278,9 +276,9 @@ class CordaRPCOpsImplTest { withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) { val inputJar1 = 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 { - val secureHash2 = rpc.uploadAttachment(inputJar2) + rpc.uploadAttachment(inputJar2) } } } @@ -311,13 +309,14 @@ class CordaRPCOpsImplTest { @Test fun `kill a stuck flow through RPC`() { - - withPermissions(startFlow(), invokeRpc(CordaRPCOps::killFlow), invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::stateMachinesSnapshot)) { - + withPermissions( + startFlow(), + invokeRpc(CordaRPCOps::killFlow), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::stateMachinesSnapshot) + ) { val flow = rpc.startFlow(::NewJoinerFlow) - val killed = rpc.killFlow(flow.id) - assertThat(killed).isTrue() assertThat(rpc.stateMachinesSnapshot().map { info -> info.id }).doesNotContain(flow.id) } @@ -325,13 +324,14 @@ class CordaRPCOpsImplTest { @Test fun `kill a waiting flow through RPC`() { - - withPermissions(startFlow(), invokeRpc(CordaRPCOps::killFlow), invokeRpc(CordaRPCOps::stateMachinesFeed), invokeRpc(CordaRPCOps::stateMachinesSnapshot)) { - + withPermissions( + startFlow(), + invokeRpc(CordaRPCOps::killFlow), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::stateMachinesSnapshot) + ) { val flow = rpc.startFlow(::HopefulFlow, alice) - val killed = rpc.killFlow(flow.id) - assertThat(killed).isTrue() assertThat(rpc.stateMachinesSnapshot().map { info -> info.id }).doesNotContain(flow.id) } @@ -339,23 +339,26 @@ class CordaRPCOpsImplTest { @Test fun `kill a nonexistent flow through RPC`() { - withPermissions(invokeRpc(CordaRPCOps::killFlow)) { - val nonexistentFlowId = StateMachineRunId.createRandom() - val killed = rpc.killFlow(nonexistentFlowId) - assertThat(killed).isFalse() } } + @Test + fun `non-ContractState class for the contractStateType param in vault queries`() { + val nonContractStateClass: Class = 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 class NewJoinerFlow : FlowLogic() { - @Suspendable override fun call(): String { - logger.info("When can I join you say? Almost there buddy...") Fiber.currentFiber().join() return "You'll never get me!" @@ -364,13 +367,10 @@ class CordaRPCOpsImplTest { @StartableByRPC class HopefulFlow(private val party: Party) : FlowLogic() { - @Suspendable override fun call(): String { - logger.info("Waiting for a miracle...") - val miracle = initiateFlow(party).receive().unwrap { it } - return miracle + return initiateFlow(party).receive().unwrap { it } } } @@ -394,17 +394,15 @@ class CordaRPCOpsImplTest { 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() try { - CURRENT_RPC_CONTEXT.set(previous.copy(authorizer = - buildSubject(previous.principal, permissions.toSet()))) + CURRENT_RPC_CONTEXT.set(previous.copy(authorizer = buildSubject(previous.principal, permissions.toSet()))) action.invoke() } finally { CURRENT_RPC_CONTEXT.set(previous) } } - private fun withoutAnyPermissions(action: () -> Unit) = withPermissions(action = action) -} \ No newline at end of file + private inline fun withoutAnyPermissions(action: () -> Unit) = withPermissions(action = action) +} diff --git a/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt b/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt index 235e2b8076..4bb103edeb 100644 --- a/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt @@ -42,7 +42,7 @@ class AbstractNodeTests { @Test fun `logVendorString does not leak connection`() { // 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() // 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: repeat(100) { diff --git a/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt b/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt index 9c2557baf2..c5d9b34e76 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt @@ -79,7 +79,7 @@ class NodeTest { doReturn("tsp").whenever(it).trustStorePassword doReturn("ksp").whenever(it).keyStorePassword } - configureDatabase(dataSourceProperties, databaseConfig, rigorousMock()).use { _ -> + configureDatabase(dataSourceProperties, databaseConfig, { null }, { null }).use { _ -> val node = Node(configuration, info, initialiseSerialization = false) assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial) } diff --git a/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt b/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt new file mode 100644 index 0000000000..92f35bb37c --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt @@ -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().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() { + + @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)) + } + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index af55b3c7a6..f57a5d6acb 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -155,7 +155,7 @@ class MockScheduledFlowRepository : ScheduledFlowRepository { } class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { - private val database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) + private val database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }) @After fun closeDatabase() { @@ -306,7 +306,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { @Test fun `test that correct item is returned`() { val dataSourceProps = MockServices.makeTestDataSourceProperties() - val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) + val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null }) database.transaction { val repo = PersistentScheduledFlowRepository(database) val stateRef = StateRef(SecureHash.randomSHA256(), 0) @@ -325,7 +325,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { val timeInTheFuture = mark + 1.days val stateRef = StateRef(SecureHash.zeroHash, 0) - configureDatabase(dataSourceProps, databaseConfig, rigorousMock()).use { database -> + configureDatabase(dataSourceProps, databaseConfig, { null }, { null }).use { database -> val scheduler = database.transaction { createScheduler(database) } @@ -347,7 +347,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { transactionStates[stateRef] = transactionStateMock(logicRef, timeInTheFuture) flows[logicRef] = flowLogic - configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()).use { database -> + configureDatabase(dataSourceProps, DatabaseConfig(), { null }, { null }).use { database -> val newScheduler = database.transaction { createScheduler(database) } @@ -370,7 +370,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { val logicRef = rigorousMock() val flowLogic = rigorousMock>() - configureDatabase(dataSourceProps, databaseConfig, rigorousMock()).use { database -> + configureDatabase(dataSourceProps, databaseConfig, { null }, { null }).use { database -> val scheduler = database.transaction { createScheduler(database) } diff --git a/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt b/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt index d6421757fd..f25588ea74 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepositoryTest.kt @@ -21,7 +21,7 @@ class PersistentScheduledFlowRepositoryTest { fun `test that earliest item is returned`() { val laterTime = mark + 1.days val dataSourceProps = MockServices.makeTestDataSourceProperties() - val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) + val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null }) database.transaction { val repo = PersistentScheduledFlowRepository(database) @@ -43,7 +43,7 @@ class PersistentScheduledFlowRepositoryTest { fun `test that item is rescheduled`() { val laterTime = mark + 1.days val dataSourceProps = MockServices.makeTestDataSourceProperties() - val database = configureDatabase(dataSourceProps, databaseConfig, rigorousMock()) + val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null }) database.transaction { val repo = PersistentScheduledFlowRepository(database) val stateRef = StateRef(SecureHash.randomSHA256(), 0) diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index 6c57ee85ee..f577aaf2c5 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -24,7 +24,11 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence 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_ROOT_CA import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties @@ -33,6 +37,7 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import java.util.concurrent.atomic.AtomicReference import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNull @@ -60,8 +65,12 @@ class PersistentIdentityServiceTests { @Before fun setup() { - identityService = PersistentIdentityService(DEV_ROOT_CA.certificate) - database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), identityService) + val identityServiceRef = AtomicReference() + // 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 @@ -72,78 +81,55 @@ class PersistentIdentityServiceTests { @Test fun `get all identities`() { // 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 actual = database.transaction { - identityService.getAllIdentities().map { it.party }.toHashSet() - } + var actual = identityService.getAllIdentities().map { it.party }.toHashSet() + assertEquals(expected, actual) // Add a second party and check we get both back - database.transaction { - identityService.verifyAndRegisterIdentity(BOB_IDENTITY) - } + identityService.verifyAndRegisterIdentity(BOB_IDENTITY) expected = setOf(ALICE, BOB) - actual = database.transaction { - identityService.getAllIdentities().map { it.party }.toHashSet() - } + actual = identityService.getAllIdentities().map { it.party }.toHashSet() assertEquals(expected, actual) } @Test fun `get identity by key`() { - database.transaction { - assertNull(identityService.partyFromKey(ALICE_PUBKEY)) - identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) - assertEquals(ALICE, identityService.partyFromKey(ALICE_PUBKEY)) - assertNull(identityService.partyFromKey(BOB_PUBKEY)) - } + assertNull(identityService.partyFromKey(ALICE_PUBKEY)) + identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) + assertEquals(ALICE, identityService.partyFromKey(ALICE_PUBKEY)) + assertNull(identityService.partyFromKey(BOB_PUBKEY)) } @Test fun `get identity by name with no registered identities`() { - database.transaction { - assertNull(identityService.wellKnownPartyFromX500Name(ALICE.name)) - } + assertNull(identityService.wellKnownPartyFromX500Name(ALICE.name)) } @Test fun `get identity by substring match`() { - database.transaction { - identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) - identityService.verifyAndRegisterIdentity(BOB_IDENTITY) - } + identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) + identityService.verifyAndRegisterIdentity(BOB_IDENTITY) val alicente = getTestPartyAndCertificate(CordaX500Name(organisation = "Alicente Worldwide", locality = "London", country = "GB"), generateKeyPair().public) - database.transaction { - identityService.verifyAndRegisterIdentity(alicente) - assertEquals(setOf(ALICE, alicente.party), identityService.partiesFromName("Alice", false)) - assertEquals(setOf(ALICE), identityService.partiesFromName("Alice Corp", true)) - assertEquals(setOf(BOB), identityService.partiesFromName("Bob Plc", true)) - } + identityService.verifyAndRegisterIdentity(alicente) + assertEquals(setOf(ALICE, alicente.party), identityService.partiesFromName("Alice", false)) + assertEquals(setOf(ALICE), identityService.partiesFromName("Alice Corp", true)) + assertEquals(setOf(BOB), identityService.partiesFromName("Bob Plc", true)) } @Test fun `get identity by name`() { val identities = listOf("Organisation A", "Organisation B", "Organisation C") .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 { - database.transaction { - identityService.verifyAndRegisterIdentity(it) - } - } - identities.forEach { - database.transaction { - assertEquals(it.party, identityService.wellKnownPartyFromX500Name(it.name)) - } + assertEquals(it.party, identityService.wellKnownPartyFromX500Name(it.name)) } } @@ -159,9 +145,7 @@ class PersistentIdentityServiceTests { val txIdentity = AnonymousParty(txKey.public) assertFailsWith { - database.transaction { - identityService.assertOwnership(identity, txIdentity) - } + identityService.assertOwnership(identity, txIdentity) } } @@ -175,25 +159,15 @@ class PersistentIdentityServiceTests { val (_, bobTxIdentity) = createConfidentialIdentity(ALICE.name) // Now we have identities, construct the service and let it know about both - database.transaction { - identityService.verifyAndRegisterIdentity(alice) - identityService.verifyAndRegisterIdentity(aliceTxIdentity) - } + identityService.verifyAndRegisterIdentity(alice) + identityService.verifyAndRegisterIdentity(aliceTxIdentity) - var actual = database.transaction { - identityService.certificateFromKey(aliceTxIdentity.party.owningKey) - } + var actual = identityService.certificateFromKey(aliceTxIdentity.party.owningKey) assertEquals(aliceTxIdentity, actual!!) - database.transaction { - assertNull(identityService.certificateFromKey(bobTxIdentity.party.owningKey)) - } - database.transaction { - identityService.verifyAndRegisterIdentity(bobTxIdentity) - } - actual = database.transaction { - identityService.certificateFromKey(bobTxIdentity.party.owningKey) - } + assertNull(identityService.certificateFromKey(bobTxIdentity.party.owningKey)) + identityService.verifyAndRegisterIdentity(bobTxIdentity) + actual = identityService.certificateFromKey(bobTxIdentity.party.owningKey) assertEquals(bobTxIdentity, actual!!) } @@ -206,34 +180,24 @@ class PersistentIdentityServiceTests { val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name) val (bob, anonymousBob) = createConfidentialIdentity(BOB.name) - database.transaction { - // Now we have identities, construct the service and let it know about both - identityService.verifyAndRegisterIdentity(anonymousAlice) - identityService.verifyAndRegisterIdentity(anonymousBob) - } + // Now we have identities, construct the service and let it know about both + identityService.verifyAndRegisterIdentity(anonymousAlice) + identityService.verifyAndRegisterIdentity(anonymousBob) // Verify that paths are verified - database.transaction { - identityService.assertOwnership(alice.party, anonymousAlice.party.anonymise()) - identityService.assertOwnership(bob.party, anonymousBob.party.anonymise()) + identityService.assertOwnership(alice.party, anonymousAlice.party.anonymise()) + identityService.assertOwnership(bob.party, anonymousBob.party.anonymise()) + assertFailsWith { + identityService.assertOwnership(alice.party, anonymousBob.party.anonymise()) } assertFailsWith { - database.transaction { - identityService.assertOwnership(alice.party, anonymousBob.party.anonymise()) - } - } - assertFailsWith { - database.transaction { - identityService.assertOwnership(bob.party, anonymousAlice.party.anonymise()) - } + identityService.assertOwnership(bob.party, anonymousAlice.party.anonymise()) } assertFailsWith { val owningKey = DEV_INTERMEDIATE_CA.certificate.publicKey - database.transaction { - val subject = CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.subjectX500Principal) - identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise()) - } + val subject = CordaX500Name.build(DEV_INTERMEDIATE_CA.certificate.subjectX500Principal) + identityService.assertOwnership(Party(subject, owningKey), anonymousAlice.party.anonymise()) } } @@ -242,33 +206,24 @@ class PersistentIdentityServiceTests { val (alice, anonymousAlice) = createConfidentialIdentity(ALICE.name) val (bob, anonymousBob) = createConfidentialIdentity(BOB.name) - database.transaction { - // Register well known identities - identityService.verifyAndRegisterIdentity(alice) - identityService.verifyAndRegisterIdentity(bob) - // Register an anonymous identities - identityService.verifyAndRegisterIdentity(anonymousAlice) - identityService.verifyAndRegisterIdentity(anonymousBob) - } + // Register well known identities + identityService.verifyAndRegisterIdentity(alice) + identityService.verifyAndRegisterIdentity(bob) + // Register an anonymous identities + identityService.verifyAndRegisterIdentity(anonymousAlice) + identityService.verifyAndRegisterIdentity(anonymousBob) // Create new identity service mounted onto same DB - val newPersistentIdentityService = database.transaction { - PersistentIdentityService(DEV_ROOT_CA.certificate) - } + val newPersistentIdentityService = PersistentIdentityService(DEV_ROOT_CA.certificate, database) - database.transaction { - newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise()) - newPersistentIdentityService.assertOwnership(bob.party, anonymousBob.party.anonymise()) - } + newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.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!!) - val bobReload = database.transaction { - newPersistentIdentityService.certificateFromKey(anonymousBob.party.owningKey) - } + val bobReload = newPersistentIdentityService.certificateFromKey(anonymousBob.party.owningKey) assertEquals(anonymousBob, bobReload!!) } diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt index 5c7263609f..f4d7eb3133 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt @@ -87,8 +87,8 @@ class ArtemisMessagingTest { } LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock()) - networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()).start(), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()).start(), rigorousMock(), database) } @After diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt index b10042dcc6..8d0b0800e5 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt @@ -6,7 +6,7 @@ import net.corda.node.internal.configureDatabase import net.corda.node.services.schema.NodeSchemaService import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.testing.internal.rigorousMock + import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After import org.junit.Assert.* @@ -58,7 +58,7 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) { private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), - rigorousMock(), + { null }, { null }, NodeSchemaService(setOf(MappedSchema(AppendOnlyPersistentMapTest::class.java, 1, listOf(PersistentMapEntry::class.java))))) @After diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt index 147c7712ac..b3df9c6c06 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBCheckpointStorageTests.kt @@ -27,7 +27,6 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.internal.LogHelper -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -55,7 +54,7 @@ class DBCheckpointStorageTests { @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) - database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }) newCheckpointStorage() } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 258017a121..3d83853133 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -18,14 +18,17 @@ import net.corda.core.crypto.TransactionSignature import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction -import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.internal.configureDatabase import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.nodeapi.internal.persistence.CordaPersistence 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.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -51,7 +54,7 @@ class DBTransactionStorageTests { fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) val dataSourceProps = makeTestDataSourceProperties() - database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), rigorousMock()) + database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), { null }, { null }) newTransactionStorage() } @@ -63,51 +66,33 @@ class DBTransactionStorageTests { @Test fun `empty store`() { - database.transaction { - assertThat(transactionStorage.getTransaction(newTransaction().id)).isNull() - } - database.transaction { - assertThat(transactionStorage.transactions).isEmpty() - } + assertThat(transactionStorage.getTransaction(newTransaction().id)).isNull() + assertThat(transactionStorage.transactions).isEmpty() newTransactionStorage() - database.transaction { - assertThat(transactionStorage.transactions).isEmpty() - } + assertThat(transactionStorage.transactions).isEmpty() } @Test fun `one transaction`() { val transaction = newTransaction() - database.transaction { - transactionStorage.addTransaction(transaction) - } + transactionStorage.addTransaction(transaction) assertTransactionIsRetrievable(transaction) - database.transaction { - assertThat(transactionStorage.transactions).containsExactly(transaction) - } + assertThat(transactionStorage.transactions).containsExactly(transaction) newTransactionStorage() assertTransactionIsRetrievable(transaction) - database.transaction { - assertThat(transactionStorage.transactions).containsExactly(transaction) - } + assertThat(transactionStorage.transactions).containsExactly(transaction) } @Test fun `two transactions across restart`() { val firstTransaction = newTransaction() val secondTransaction = newTransaction() - database.transaction { - transactionStorage.addTransaction(firstTransaction) - } + transactionStorage.addTransaction(firstTransaction) newTransactionStorage() - database.transaction { - transactionStorage.addTransaction(secondTransaction) - } + transactionStorage.addTransaction(secondTransaction) assertTransactionIsRetrievable(firstTransaction) assertTransactionIsRetrievable(secondTransaction) - database.transaction { - assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction) - } + assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction) } @Test @@ -120,24 +105,18 @@ class DBTransactionStorageTests { rollback() } - database.transaction { - assertThat(transactionStorage.transactions).isEmpty() - } + assertThat(transactionStorage.transactions).isEmpty() } @Test fun `two transactions in same DB transaction scope`() { val firstTransaction = newTransaction() val secondTransaction = newTransaction() - database.transaction { - transactionStorage.addTransaction(firstTransaction) - transactionStorage.addTransaction(secondTransaction) - } + transactionStorage.addTransaction(firstTransaction) + transactionStorage.addTransaction(secondTransaction) assertTransactionIsRetrievable(firstTransaction) assertTransactionIsRetrievable(secondTransaction) - database.transaction { - assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction) - } + assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction) } @Test @@ -148,36 +127,29 @@ class DBTransactionStorageTests { transactionStorage.addTransaction(firstTransaction) } assertTransactionIsRetrievable(firstTransaction) - database.transaction { - assertThat(transactionStorage.transactions).containsOnly(firstTransaction) - } + assertThat(transactionStorage.transactions).containsOnly(firstTransaction) } @Test fun `transaction saved twice in two DB transaction scopes`() { val firstTransaction = newTransaction() val secondTransaction = newTransaction() - database.transaction { - transactionStorage.addTransaction(firstTransaction) - } + + transactionStorage.addTransaction(firstTransaction) database.transaction { transactionStorage.addTransaction(secondTransaction) transactionStorage.addTransaction(firstTransaction) } assertTransactionIsRetrievable(firstTransaction) - database.transaction { - assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction) - } + assertThat(transactionStorage.transactions).containsOnly(firstTransaction, secondTransaction) } @Test fun `updates are fired`() { val future = transactionStorage.updates.toFuture() val expected = newTransaction() - database.transaction { - transactionStorage.addTransaction(expected) - } + transactionStorage.addTransaction(expected) val actual = future.get(1, TimeUnit.SECONDS) assertEquals(expected, actual) } @@ -196,15 +168,11 @@ class DBTransactionStorageTests { } private fun newTransactionStorage(cacheSizeBytesOverride: Long? = null) { - database.transaction { - transactionStorage = DBTransactionStorage(cacheSizeBytesOverride ?: NodeConfiguration.defaultTransactionCacheSize) - } + transactionStorage = DBTransactionStorage(cacheSizeBytesOverride ?: NodeConfiguration.defaultTransactionCacheSize, database) } 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 { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 8918f6c125..80ba084244 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -122,7 +122,7 @@ class HibernateConfigurationTest { } } 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 { hibernateConfig = database.hibernateConfig @@ -130,7 +130,7 @@ class HibernateConfigurationTest { services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock().also { doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME }) }, 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) { for (stx in txs) { (validatedTransactions as WritableTransactionStorage).addTransaction(stx) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index 1f2c3dc38d..6bb7517973 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -15,7 +15,11 @@ import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import net.corda.core.crypto.SecureHash 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.AttachmentSort 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.DatabaseConfig import net.corda.testing.internal.LogHelper -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After import org.junit.Before @@ -45,14 +48,16 @@ class NodeAttachmentStorageTest { // Use an in memory file system for testing attachment storage. private lateinit var fs: FileSystem private lateinit var database: CordaPersistence + private lateinit var storage: NodeAttachmentService @Before fun setUp() { LogHelper.setLevel(PersistentUniquenessProvider::class) val dataSourceProperties = makeTestDataSourceProperties() - database = configureDatabase(dataSourceProperties, DatabaseConfig(runMigration = true), rigorousMock()) + database = configureDatabase(dataSourceProperties, DatabaseConfig(runMigration = true), { null }, { null }) fs = Jimfs.newFileSystem(Configuration.unix()) + storage = NodeAttachmentService(MetricRegistry(), database = database).also { it.start() } } @After @@ -62,28 +67,25 @@ class NodeAttachmentStorageTest { @Test fun `insert and retrieve`() { - val (testJar,expectedHash) = makeTestJar() + val (testJar, expectedHash) = makeTestJar() - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) - val id = testJar.read { storage.importAttachment(it) } - assertEquals(expectedHash, id) + val id = testJar.read { storage.importAttachment(it) } + assertEquals(expectedHash, id) - assertNull(storage.openAttachment(SecureHash.randomSHA256())) - val stream = storage.openAttachment(expectedHash)!!.openAsJAR() - val e1 = stream.nextJarEntry!! - assertEquals("test1.txt", e1.name) - assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content") - val e2 = stream.nextJarEntry!! - assertEquals("test2.txt", e2.name) - assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content") + assertNull(storage.openAttachment(SecureHash.randomSHA256())) + val stream = storage.openAttachment(expectedHash)!!.openAsJAR() + val e1 = stream.nextJarEntry!! + assertEquals("test1.txt", e1.name) + assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content") + val e2 = stream.nextJarEntry!! + assertEquals("test2.txt", e2.name) + assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content") - stream.close() + stream.close() - storage.openAttachment(id)!!.openAsJAR().use { - it.nextJarEntry - it.readBytes() - } + storage.openAttachment(id)!!.openAsJAR().use { + it.nextJarEntry + it.readBytes() } } @@ -92,138 +94,121 @@ class NodeAttachmentStorageTest { val (testJar, expectedHash) = makeTestJar() val (jarB, hashB) = makeTestJar(listOf(Pair("file", "content"))) - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) - val id = testJar.read { storage.importAttachment(it) } - assertEquals(expectedHash, id) + val id = testJar.read { storage.importAttachment(it) } + assertEquals(expectedHash, id) - assertNull(storage.openAttachment(hashB)) - val stream = storage.openAttachment(expectedHash)!!.openAsJAR() - val e1 = stream.nextJarEntry!! - assertEquals("test1.txt", e1.name) - assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content") - val e2 = stream.nextJarEntry!! - assertEquals("test2.txt", e2.name) - assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content") + assertNull(storage.openAttachment(hashB)) + val stream = storage.openAttachment(expectedHash)!!.openAsJAR() + val e1 = stream.nextJarEntry!! + assertEquals("test1.txt", e1.name) + assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "This is some useful content") + val e2 = stream.nextJarEntry!! + assertEquals("test2.txt", e2.name) + assertEquals(stream.readBytes().toString(StandardCharsets.UTF_8), "Some more useful content") - stream.close() + stream.close() - val idB = jarB.read { storage.importAttachment(it) } - assertEquals(hashB, idB) + val idB = jarB.read { storage.importAttachment(it) } + assertEquals(hashB, idB) - storage.openAttachment(id)!!.openAsJAR().use { - it.nextJarEntry - it.readBytes() - } + storage.openAttachment(id)!!.openAsJAR().use { + it.nextJarEntry + it.readBytes() + } - storage.openAttachment(idB)!!.openAsJAR().use { - it.nextJarEntry - it.readBytes() - } + storage.openAttachment(idB)!!.openAsJAR().use { + it.nextJarEntry + it.readBytes() } } - @Test fun `metadata can be used to search`() { val (jarA, _) = makeTestJar() - val (jarB, hashB) = makeTestJar(listOf(Pair("file","content"))) - val (jarC, hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff"))) + val (jarB, hashB) = makeTestJar(listOf(Pair("file", "content"))) + val (jarC, hashC) = makeTestJar(listOf(Pair("magic_file", "magic_content_puff"))) - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) + jarA.read { storage.importAttachment(it) } + jarB.read { storage.importAttachment(it, "uploaderB", "fileB.zip") } + jarC.read { storage.importAttachment(it, "uploaderC", "fileC.zip") } - jarA.read { storage.importAttachment(it) } - jarB.read { storage.importAttachment(it, "uploaderB", "fileB.zip") } - jarC.read { storage.importAttachment(it, "uploaderC", "fileC.zip") } - - assertEquals( + assertEquals( listOf(hashB), - storage.queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria( Builder.equal("uploaderB"))) - ) + storage.queryAttachments(AttachmentQueryCriteria.AttachmentsQueryCriteria(Builder.equal("uploaderB"))) + ) - assertEquals ( - listOf(hashB, hashC), - storage.queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria( Builder.like ("%uploader%"))) - ) - } + assertEquals( + listOf(hashB, hashC), + storage.queryAttachments(AttachmentQueryCriteria.AttachmentsQueryCriteria(Builder.like("%uploader%"))) + ) } @Test fun `sorting and compound conditions work`() { - val (jarA,hashA) = makeTestJar(listOf(Pair("a","a"))) - val (jarB,hashB) = makeTestJar(listOf(Pair("b","b"))) - val (jarC,hashC) = makeTestJar(listOf(Pair("c","c"))) + val (jarA, hashA) = makeTestJar(listOf(Pair("a", "a"))) + val (jarB, hashB) = makeTestJar(listOf(Pair("b", "b"))) + val (jarC, hashC) = makeTestJar(listOf(Pair("c", "c"))) - fun uploaderCondition(s:String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal(s)) - fun filenamerCondition(s:String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(filenameCondition = Builder.equal(s)) + fun uploaderCondition(s: String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = 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))) - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) + jarA.read { storage.importAttachment(it, "complexA", "archiveA.zip") } + jarB.read { storage.importAttachment(it, "complexB", "archiveB.zip") } + jarC.read { storage.importAttachment(it, "complexC", "archiveC.zip") } - jarA.read { storage.importAttachment(it, "complexA", "archiveA.zip") } - jarB.read { storage.importAttachment(it, "complexB", "archiveB.zip") } - jarC.read { storage.importAttachment(it, "complexC", "archiveC.zip") } + // DOCSTART AttachmentQueryExample1 - // DOCSTART AttachmentQueryExample1 - - assertEquals( + assertEquals( emptyList(), storage.queryAttachments( - AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA")) - .and(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB")))) - ) + AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA")) + .and(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB")))) + ) - assertEquals( + assertEquals( listOf(hashA, hashB), storage.queryAttachments( - AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA")) - .or(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB")))) - ) + AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA")) + .or(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB")))) + ) - val complexCondition = - (uploaderCondition("complexB").and(filenamerCondition("archiveB.zip"))).or(filenamerCondition("archiveC.zip")) + val complexCondition = + (uploaderCondition("complexB").and(filenamerCondition("archiveB.zip"))).or(filenamerCondition("archiveC.zip")) - // DOCEND AttachmentQueryExample1 + // DOCEND AttachmentQueryExample1 - assertEquals ( - listOf(hashB, hashC), + assertEquals( + listOf(hashB, hashC), storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.ASC)) - ) - assertEquals ( - listOf(hashC, hashB), + ) + assertEquals( + listOf(hashC, hashB), storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.DESC)) - ) - - } + ) } @Ignore("We need to be able to restart nodes - make importing attachments idempotent?") @Test fun `duplicates not allowed`() { - val (testJar,_) = makeTestJar() - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) + val (testJar, _) = makeTestJar() + testJar.read { + storage.importAttachment(it) + } + assertFailsWith { testJar.read { storage.importAttachment(it) } - assertFailsWith { - testJar.read { - storage.importAttachment(it) - } - } } } @Test fun `corrupt entry throws exception`() { - val (testJar,_) = makeTestJar() + val (testJar, _) = makeTestJar() val id = database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) val id = testJar.read { storage.importAttachment(it) } // Corrupt the file in the store. @@ -234,37 +219,31 @@ class NodeAttachmentStorageTest { session.merge(corruptAttachment) id } - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) - val e = assertFailsWith { - storage.openAttachment(id)!!.open().readFully() - } - assertEquals(e.expected, id) + val e = assertFailsWith { + storage.openAttachment(id)!!.open().readFully() + } + assertEquals(e.expected, id) - // But if we skip around and read a single entry, no exception is thrown. - storage.openAttachment(id)!!.openAsJAR().use { - it.nextJarEntry - it.readBytes() - } + // But if we skip around and read a single entry, no exception is thrown. + storage.openAttachment(id)!!.openAsJAR().use { + it.nextJarEntry + it.readBytes() } } @Test fun `non jar rejected`() { - database.transaction { - val storage = NodeAttachmentService(MetricRegistry()) - val path = fs.getPath("notajar") - path.writeLines(listOf("Hey", "there!")) - path.read { - assertFailsWith("either empty or not a JAR") { - storage.importAttachment(it) - } + val path = fs.getPath("notajar") + path.writeLines(listOf("Hey", "there!")) + path.read { + assertFailsWith("either empty or not a JAR") { + storage.importAttachment(it) } } } private var counter = 0 - private fun makeTestJar(extraEntries: List> = emptyList()): Pair { + private fun makeTestJar(extraEntries: List> = emptyList()): Pair { counter++ val file = fs.getPath("$counter.jar") file.write { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt index cd46899392..7231c3afed 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/TransactionCallbackTest.kt @@ -2,7 +2,6 @@ package net.corda.node.services.persistence import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After import org.junit.Test @@ -10,7 +9,7 @@ import kotlin.test.assertEquals class TransactionCallbackTest { - private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) + private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }) @After fun closeDatabase() { diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 00c29ef524..e338f199dd 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -29,7 +29,6 @@ import net.corda.testing.internal.LogHelper import net.corda.testing.core.TestIdentity import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.internal.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test @@ -78,7 +77,7 @@ class HibernateObserverTests { 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) database.transaction { val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index 7e62930b50..c1d8cc05ff 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -26,7 +26,6 @@ import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.core.generateStateRef import net.corda.testing.internal.LogHelper -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After import org.junit.Before @@ -49,7 +48,7 @@ class PersistentUniquenessProviderTests { @Before fun setUp() { 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 diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt index 815eef3daa..0df56851ea 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLogTests.kt @@ -32,7 +32,6 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.freeLocalHostAndPort import net.corda.testing.internal.LogHelper -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.hamcrest.Matchers.instanceOf import org.junit.* @@ -159,7 +158,7 @@ class RaftTransactionCommitLogTests { private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture { val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build() 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) val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), RaftUniquenessProvider.Companion::createMap) } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 8054711fd1..72ef3f6346 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -195,7 +195,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties { @Ignore @Test fun createPersistentTestDb() { - val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(runMigration = true), identitySvc) + val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(runMigration = true), identitySvc::wellKnownPartyFromX500Name, identitySvc::wellKnownPartyFromAnonymous) setUpDb(database, 5000) database.close() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index e4698681d6..b234afa306 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -35,6 +35,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.VaultServiceInternal +import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.testing.core.singleIdentity import net.corda.testing.internal.rigorousMock @@ -93,9 +94,9 @@ class VaultSoftLockManagerTest { } private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { 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 realVault = super.makeVaultService(keyManagementService, services, hibernateConfig) + val realVault = super.makeVaultService(keyManagementService, services, hibernateConfig, database) return object : VaultServiceInternal by realVault { override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { // Should be called before flow is removed diff --git a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt index acaf457c9b..1e470c2576 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ObservablesTests.kt @@ -15,7 +15,6 @@ import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.tee import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.persistence.* -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -31,7 +30,7 @@ class ObservablesTests { private val toBeClosed = mutableListOf() private fun createDatabase(): CordaPersistence { - val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock()) + val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }) toBeClosed += database return database } diff --git a/node/src/test/kotlin/net/corda/node/utilities/PersistentMapTests.kt b/node/src/test/kotlin/net/corda/node/utilities/PersistentMapTests.kt index c5add42c8a..671e9c1fc8 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/PersistentMapTests.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/PersistentMapTests.kt @@ -4,14 +4,13 @@ import net.corda.core.crypto.SecureHash import net.corda.node.internal.configureDatabase import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import org.junit.Test import kotlin.test.assertEquals class PersistentMapTests { 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() //create a test map using an existing db table diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index fa68ac46be..69c4d8c6a8 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -77,7 +77,7 @@ class NodeInterestRatesTest { @Before fun setUp() { - database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock()) + database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), { null }, { null }) database.transaction { oracle = createMockCordaService(services, NodeInterestRates::Oracle) oracle.knownFixes = TEST_DATA diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index a50f00e430..fffcb7ff23 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -104,10 +104,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten val node1 = banks[i].started!! val node2 = banks[j].started!! - val swaps = - node1.database.transaction { - node1.services.vaultService.queryBy().states - } + val swaps = node1.services.vaultService.queryBy().states val theDealRef: StateAndRef = swaps.single() // Do we have any more days left in this deal's lifetime? If not, return. diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 83be14b1d2..d94e57e868 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -73,9 +73,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) 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()) } } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt index 01f28ca5ce..7e3132c3b8 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt @@ -11,6 +11,10 @@ package net.corda.serialization.internal.amqp 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.codec.Data import java.io.NotSerializableException @@ -21,30 +25,48 @@ import java.lang.reflect.Type */ open class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQPSerializer { companion object { - fun make(type: Type, factory: SerializerFactory) = when (type) { - Array::class.java -> CharArraySerializer(factory) - else -> ArraySerializer(type, factory) + fun make(type: Type, factory: SerializerFactory) : AMQPSerializer { + contextLogger().debug { "Making array serializer, typename=${type.typeName}" } + return when (type) { + Array::class.java -> CharArraySerializer(factory) + else -> ArraySerializer(type, factory) + } } } + private val logger = loggerFor() + // 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 // id to generate it properly (it will always return [[[Ljava.lang.type -> type[][][] // 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. - private fun calcTypeName(type: Type): String = - if (type.componentType().isArray()) { - val typeName = calcTypeName(type.componentType()); "$typeName[]" + private fun calcTypeName(type: Type, debugOffset : Int = 0): String { + logger.trace { "${"".padStart(debugOffset, ' ') } calcTypeName - ${type.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 { - val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]" - "${type.componentType().typeName}$arrayType" + calcTypeName(type.componentType(), debugOffset + 4) } + "$typeName[]" + } else { + val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]" + "${type.componentType().typeName}$arrayType" + } + } + override val typeDescriptor: Symbol by lazy { Symbol.valueOf("$DESCRIPTOR_DOMAIN:${factory.fingerPrinter.fingerprint(type)}") } + internal val elementType: Type by lazy { type.componentType() } internal open val typeName by lazy { calcTypeName(type) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt index 418a2428c4..382c530210 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt @@ -14,6 +14,7 @@ import com.google.common.primitives.Primitives import com.google.common.reflect.TypeResolver import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.ClassWhitelist +import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import net.corda.serialization.internal.carpenter.* @@ -255,7 +256,7 @@ open class SerializerFactory( private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) { val metaSchema = CarpenterMetaSchema.newInstance() for (typeNotation in schemaAndDescriptor.schemas.schema.types) { - logger.trace("descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}") + logger.debug { "descriptor=${schemaAndDescriptor.typeDescriptor}, typeNotation=${typeNotation.name}" } try { val serialiser = processSchemaEntry(typeNotation) // if we just successfully built a serializer for the type but the type fingerprint diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeSimpleTypesTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeSimpleTypesTests.kt index 3137d92fb3..bd9f074525 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeSimpleTypesTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeSimpleTypesTests.kt @@ -10,10 +10,7 @@ package net.corda.serialization.internal.amqp -import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput -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 net.corda.serialization.internal.amqp.testutils.* import org.junit.Test import kotlin.test.assertEquals @@ -263,6 +260,14 @@ class DeserializeSimpleTypesTests { assertEquals(c.c[0], deserializedC.c[0]) assertEquals(c.c[1], deserializedC.c[1]) 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 @@ -504,7 +509,33 @@ class DeserializeSimpleTypesTests { assertEquals(3, da2.a?.a?.b) assertEquals(2, da2.a?.a?.a?.b) assertEquals(1, da2.a?.a?.a?.a?.b) - } + + // Replicates CORDA-1545 + @Test + fun arrayOfByteArray() { + class A(val a : Array) + + 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) + } + } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index c77f72a821..615931eb29 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -28,7 +28,12 @@ import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow 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.ExternalEvent import net.corda.node.services.statemachine.SenderDeduplicationId @@ -47,6 +52,7 @@ import java.util.concurrent.LinkedBlockingQueue import javax.annotation.concurrent.ThreadSafe import kotlin.concurrent.schedule import kotlin.concurrent.thread +import kotlin.jvm.Volatile /** * 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, executor: AffinityExecutor, notaryService: PartyAndCertificate?, - description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK"), - database: CordaPersistence) + description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK")) : InternalMockMessagingService { val peerHandle = PeerHandle(id, description) 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)) } ?: emptyList() //TODO only notary can be distributed? synchronized(this) { - val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database) + val node = InMemoryMessaging(manuallyPumped, peerHandle, executor) val oldNode = handleEndpointMap.put(peerHandle, node) if (oldNode != null) { node.inheritPendingRedelivery(oldNode) @@ -364,8 +369,7 @@ class InMemoryMessagingNetwork private constructor( @ThreadSafe private inner class InMemoryMessaging(private val manuallyPumped: Boolean, private val peerHandle: PeerHandle, - private val executor: AffinityExecutor, - private val database: CordaPersistence) : SingletonSerializeAsToken(), InternalMockMessagingService { + private val executor: AffinityExecutor) : SingletonSerializeAsToken(), InternalMockMessagingService { private inner class Handler(val topicSession: String, val callback: MessageHandler) : MessageHandlerRegistration @Volatile @@ -406,10 +410,8 @@ class InMemoryMessagingNetwork private constructor( val (handler, transfers) = state.locked { val handler = Handler(topic, callback).apply { handlers.add(this) } val pending = ArrayList() - database.transaction { - pending.addAll(pendingRedelivery) - pendingRedelivery.clear() - } + pending.addAll(pendingRedelivery) + pendingRedelivery.clear() 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 // least sometimes. log.warn("Message to ${transfer.message.topic} could not be delivered") - database.transaction { - pendingRedelivery.add(transfer) - } + pendingRedelivery.add(transfer) null } else { matchingHandlers @@ -516,19 +516,17 @@ class InMemoryMessagingNetwork private constructor( val (transfer, deliverTo) = getNextQueue(q, block) ?: return null if (transfer.message.uniqueMessageId !in processedMessages) { executor.execute { - database.transaction { - for (handler in deliverTo) { - try { - val receivedMessage = transfer.toReceivedMessage() - state.locked { pendingRedelivery.add(transfer) } - handler.callback(receivedMessage, handler, InMemoryDeduplicationHandler(receivedMessage, transfer)) - } catch (e: Exception) { - log.error("Caught exception in handler for $this/${handler.topicSession}", e) - } + for (handler in deliverTo) { + try { + val receivedMessage = transfer.toReceivedMessage() + state.locked { pendingRedelivery.add(transfer) } + handler.callback(receivedMessage, handler, InMemoryDeduplicationHandler(receivedMessage, transfer)) + } catch (e: Exception) { + log.error("Caught exception in handler for $this/${handler.topicSession}", e) } - _receivedMessages.onNext(transfer) - messagesInFlight.countDown() } + _receivedMessages.onNext(transfer) + messagesInFlight.countDown() } } else { log.info("Drop duplicate message ${transfer.message.uniqueMessageId}") diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 8b0e4cf35c..3295bd63c7 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -111,16 +111,17 @@ open class MockServices private constructor( val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation, SecureHash.randomSHA256().toString()) 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 { 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) { ServiceHubInternal.recordTransactions(statesToRecord, txs, validatedTransactions as WritableTransactionStorage, mockStateMachineRecordedTransactionMappingStorage, - vaultService as VaultServiceInternal) + vaultService as VaultServiceInternal, + database) } 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) - internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal { - val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig) + internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService, database: CordaPersistence): VaultServiceInternal { + val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, servicesForResolution, hibernateConfig, database) HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService) return vaultService } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 94acec8715..f2115e32a1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -18,6 +18,7 @@ import net.corda.core.DoNotImplement import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.random63BitValue +import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -281,15 +282,14 @@ open class InternalMockNetwork(private val cordappPackages: List, id, serverThread, myNotaryIdentity, - configuration.myLegalName, - database).also { runOnStop += it::stop } + configuration.myLegalName).also { runOnStop += it::stop } } fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { network = messagingServiceSpy } - override fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set): KeyManagementService { + override fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set, database: CordaPersistence): KeyManagementService { return E2ETestKeyManagementService(identityService, keyPairs) } @@ -326,8 +326,10 @@ open class InternalMockNetwork(private val cordappPackages: List, override val serializationWhitelists: List get() = _serializationWhitelists private var dbCloser: (() -> Any?)? = null - override fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService): CordaPersistence { - return super.initialiseDatabasePersistence(schemaService, identityService).also { dbCloser = it::close } + override fun initialiseDatabasePersistence(schemaService: SchemaService, + wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, + wellKnownPartyFromAnonymous: (AbstractParty) -> Party?): CordaPersistence { + return super.initialiseDatabasePersistence(schemaService, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous).also { dbCloser = it::close } } fun disableDBCloseOnStop() { diff --git a/testing/test-common/src/main/resources/log4j2-test.xml b/testing/test-common/src/main/resources/log4j2-test.xml index 910ad4e00c..55e4799ff2 100644 --- a/testing/test-common/src/main/resources/log4j2-test.xml +++ b/testing/test-common/src/main/resources/log4j2-test.xml @@ -15,7 +15,22 @@ - + + + + + + + diff --git a/tools/explorer/src/main/resources/log4j2.xml b/tools/explorer/src/main/resources/log4j2.xml index 2511e86540..c29a4f7eab 100644 --- a/tools/explorer/src/main/resources/log4j2.xml +++ b/tools/explorer/src/main/resources/log4j2.xml @@ -18,7 +18,22 @@ - + + + + + + +