mirror of
https://github.com/corda/corda.git
synced 2025-06-17 14:48:16 +00:00
Made FlowCheckpointVersionNodeStartupCheckTest leaner (#4640)
* Removed `restart node successfully with suspended flow` as it duplicates `TraderDemoTest#Test restart node during flow works properly` * Removed the need for a notary
This commit is contained in:
22
.idea/compiler.xml
generated
22
.idea/compiler.xml
generated
@ -4,6 +4,11 @@
|
|||||||
<bytecodeTargetLevel target="1.8">
|
<bytecodeTargetLevel target="1.8">
|
||||||
<module name="api-scanner_main" target="1.8" />
|
<module name="api-scanner_main" target="1.8" />
|
||||||
<module name="api-scanner_test" target="1.8" />
|
<module name="api-scanner_test" target="1.8" />
|
||||||
|
<module name="attachment-demo-contracts_main" target="1.8" />
|
||||||
|
<module name="attachment-demo-contracts_test" target="1.8" />
|
||||||
|
<module name="attachment-demo-workflows_integrationTest" target="1.8" />
|
||||||
|
<module name="attachment-demo-workflows_main" target="1.8" />
|
||||||
|
<module name="attachment-demo-workflows_test" target="1.8" />
|
||||||
<module name="attachment-demo_integrationTest" target="1.8" />
|
<module name="attachment-demo_integrationTest" target="1.8" />
|
||||||
<module name="attachment-demo_main" target="1.8" />
|
<module name="attachment-demo_main" target="1.8" />
|
||||||
<module name="attachment-demo_test" target="1.8" />
|
<module name="attachment-demo_test" target="1.8" />
|
||||||
@ -36,6 +41,8 @@
|
|||||||
<module name="common-validation_test" target="1.8" />
|
<module name="common-validation_test" target="1.8" />
|
||||||
<module name="confidential-identities_main" target="1.8" />
|
<module name="confidential-identities_main" target="1.8" />
|
||||||
<module name="confidential-identities_test" target="1.8" />
|
<module name="confidential-identities_test" target="1.8" />
|
||||||
|
<module name="contracts-irs_main" target="1.8" />
|
||||||
|
<module name="contracts-irs_test" target="1.8" />
|
||||||
<module name="contracts-states_integrationTest" target="1.8" />
|
<module name="contracts-states_integrationTest" target="1.8" />
|
||||||
<module name="contracts-states_main" target="1.8" />
|
<module name="contracts-states_main" target="1.8" />
|
||||||
<module name="contracts-states_test" target="1.8" />
|
<module name="contracts-states_test" target="1.8" />
|
||||||
@ -64,6 +71,8 @@
|
|||||||
<module name="corda-webserver_integrationTest" target="1.8" />
|
<module name="corda-webserver_integrationTest" target="1.8" />
|
||||||
<module name="corda-webserver_main" target="1.8" />
|
<module name="corda-webserver_main" target="1.8" />
|
||||||
<module name="corda-webserver_test" target="1.8" />
|
<module name="corda-webserver_test" target="1.8" />
|
||||||
|
<module name="cordapp-configuration-workflows_main" target="1.8" />
|
||||||
|
<module name="cordapp-configuration-workflows_test" target="1.8" />
|
||||||
<module name="cordapp-configuration_main" target="1.8" />
|
<module name="cordapp-configuration_main" target="1.8" />
|
||||||
<module name="cordapp-configuration_test" target="1.8" />
|
<module name="cordapp-configuration_test" target="1.8" />
|
||||||
<module name="cordapp_integrationTest" target="1.8" />
|
<module name="cordapp_integrationTest" target="1.8" />
|
||||||
@ -170,6 +179,10 @@
|
|||||||
<module name="net.corda_canonicalizer_test" target="1.8" />
|
<module name="net.corda_canonicalizer_test" target="1.8" />
|
||||||
<module name="network-bootstrapper_main" target="1.8" />
|
<module name="network-bootstrapper_main" target="1.8" />
|
||||||
<module name="network-bootstrapper_test" target="1.8" />
|
<module name="network-bootstrapper_test" target="1.8" />
|
||||||
|
<module name="network-verifier-contracts_main" target="1.8" />
|
||||||
|
<module name="network-verifier-contracts_test" target="1.8" />
|
||||||
|
<module name="network-verifier-workflows_main" target="1.8" />
|
||||||
|
<module name="network-verifier-workflows_test" target="1.8" />
|
||||||
<module name="network-verifier_main" target="1.8" />
|
<module name="network-verifier_main" target="1.8" />
|
||||||
<module name="network-verifier_test" target="1.8" />
|
<module name="network-verifier_test" target="1.8" />
|
||||||
<module name="network-visualiser_main" target="1.8" />
|
<module name="network-visualiser_main" target="1.8" />
|
||||||
@ -189,6 +202,10 @@
|
|||||||
<module name="node_test" target="1.8" />
|
<module name="node_test" target="1.8" />
|
||||||
<module name="notary-bft-smart_main" target="1.8" />
|
<module name="notary-bft-smart_main" target="1.8" />
|
||||||
<module name="notary-bft-smart_test" target="1.8" />
|
<module name="notary-bft-smart_test" target="1.8" />
|
||||||
|
<module name="notary-demo-contracts_main" target="1.8" />
|
||||||
|
<module name="notary-demo-contracts_test" target="1.8" />
|
||||||
|
<module name="notary-demo-workflows_main" target="1.8" />
|
||||||
|
<module name="notary-demo-workflows_test" target="1.8" />
|
||||||
<module name="notary-demo_main" target="1.8" />
|
<module name="notary-demo_main" target="1.8" />
|
||||||
<module name="notary-demo_test" target="1.8" />
|
<module name="notary-demo_test" target="1.8" />
|
||||||
<module name="notary-raft_main" target="1.8" />
|
<module name="notary-raft_main" target="1.8" />
|
||||||
@ -266,6 +283,11 @@
|
|||||||
<module name="webserver_integrationTest" target="1.8" />
|
<module name="webserver_integrationTest" target="1.8" />
|
||||||
<module name="webserver_main" target="1.8" />
|
<module name="webserver_main" target="1.8" />
|
||||||
<module name="webserver_test" target="1.8" />
|
<module name="webserver_test" target="1.8" />
|
||||||
|
<module name="workflows-irs_main" target="1.8" />
|
||||||
|
<module name="workflows-irs_test" target="1.8" />
|
||||||
|
<module name="workflows-trader_integrationTest" target="1.8" />
|
||||||
|
<module name="workflows-trader_main" target="1.8" />
|
||||||
|
<module name="workflows-trader_test" target="1.8" />
|
||||||
<module name="workflows_integrationTest" target="1.8" />
|
<module name="workflows_integrationTest" target="1.8" />
|
||||||
<module name="workflows_main" target="1.8" />
|
<module name="workflows_main" target="1.8" />
|
||||||
<module name="workflows_test" target="1.8" />
|
<module name="workflows_test" target="1.8" />
|
||||||
|
@ -63,6 +63,9 @@ fun Path.copyTo(target: Path, vararg options: CopyOption): Path = Files.copy(thi
|
|||||||
/** @see Files.move */
|
/** @see Files.move */
|
||||||
fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options)
|
fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options)
|
||||||
|
|
||||||
|
/** @see Files.move */
|
||||||
|
fun Path.renameTo(fileName: String, vararg options: CopyOption): Path = moveTo(parent / fileName, *options)
|
||||||
|
|
||||||
/** See overload of [Files.copy] which takes in an [InputStream]. */
|
/** See overload of [Files.copy] which takes in an [InputStream]. */
|
||||||
fun Path.copyTo(out: OutputStream): Long = Files.copy(this, out)
|
fun Path.copyTo(out: OutputStream): Long = Files.copy(this, out)
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package net.corda.node.flows
|
package net.corda.node.flows
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.core.flows.*
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.messaging.startTrackedFlow
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.internal.CheckpointIncompatibleException
|
import net.corda.node.internal.CheckpointIncompatibleException
|
||||||
import net.corda.node.internal.NodeStartup
|
|
||||||
import net.corda.testMessage.*
|
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
@ -14,65 +15,30 @@ import net.corda.testing.driver.DriverDSL
|
|||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.NodeParameters
|
import net.corda.testing.driver.NodeParameters
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.node.TestCordapp
|
import net.corda.testing.node.internal.CustomCordapp
|
||||||
import net.corda.testing.node.internal.*
|
import net.corda.testing.node.internal.ListenProcessDeathException
|
||||||
import net.test.cordapp.v1.Record
|
import net.corda.testing.node.internal.assertCheckpoints
|
||||||
import net.test.cordapp.v1.SendMessageFlow
|
import net.corda.testing.node.internal.enclosedCordapp
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
|
|
||||||
|
// TraderDemoTest already has a test which checks the node can resume a flow from a checkpoint
|
||||||
class FlowCheckpointVersionNodeStartupCheckTest {
|
class FlowCheckpointVersionNodeStartupCheckTest {
|
||||||
companion object {
|
companion object {
|
||||||
val message = Message("Hello world!")
|
val defaultCordapp = enclosedCordapp()
|
||||||
val defaultCordapp = cordappWithPackages(
|
|
||||||
MessageState::class.packageName, SendMessageFlow::class.packageName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `restart node successfully with suspended flow`() {
|
|
||||||
return driver(parametersForRestartingNodes()) {
|
|
||||||
createSuspendedFlowInBob(setOf(defaultCordapp))
|
|
||||||
// Bob will resume the flow
|
|
||||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
|
||||||
startNode(providedName = BOB_NAME).getOrThrow()
|
|
||||||
val page = alice.rpc.vaultTrack(MessageState::class.java)
|
|
||||||
val result = if (page.snapshot.states.isNotEmpty()) {
|
|
||||||
page.snapshot.states.first()
|
|
||||||
} else {
|
|
||||||
val r = page.updates.timeout(30, TimeUnit.SECONDS).take(1).toBlocking().single()
|
|
||||||
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
|
|
||||||
}
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals(message, result.state.data.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `restart node with incompatible version of suspended flow due to different jar name`() {
|
fun `restart node with incompatible version of suspended flow due to different jar name`() {
|
||||||
driver(parametersForRestartingNodes()) {
|
driver(parametersForRestartingNodes()) {
|
||||||
val uniqueName = "different-jar-name-test-${UUID.randomUUID()}"
|
val defaultCordappJar = createSuspendedFlowInBob()
|
||||||
val cordapp = defaultCordapp.copy(name = uniqueName)
|
defaultCordappJar.renameTo("renamed-${defaultCordappJar.fileName}")
|
||||||
|
|
||||||
val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp))
|
|
||||||
|
|
||||||
val cordappsDir = bobBaseDir / "cordapps"
|
|
||||||
val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") }
|
|
||||||
// Make sure we're dealing with right jar
|
|
||||||
assertThat(cordappJar.fileName.toString()).contains(uniqueName)
|
|
||||||
// Rename the jar file.
|
|
||||||
cordappJar.moveTo(cordappsDir / "renamed-${cordappJar.fileName}")
|
|
||||||
|
|
||||||
assertBobFailsToStartWithLogMessage(
|
assertBobFailsToStartWithLogMessage(
|
||||||
emptyList(),
|
CheckpointIncompatibleException.FlowNotInstalledException(ReceiverFlow::class.java).message
|
||||||
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,54 +46,42 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
|
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
|
||||||
driver(parametersForRestartingNodes()) {
|
driver(parametersForRestartingNodes()) {
|
||||||
val uniqueWorkflowJarName = "different-jar-hash-test-${UUID.randomUUID()}"
|
val defaultCordappJar = createSuspendedFlowInBob()
|
||||||
val uniqueContractJarName = "contract-$uniqueWorkflowJarName"
|
|
||||||
val defaultWorkflowJar = cordappWithPackages(SendMessageFlow::class.packageName)
|
|
||||||
val defaultContractJar = cordappWithPackages(MessageState::class.packageName)
|
|
||||||
val contractJar = defaultContractJar.copy(name = uniqueContractJarName)
|
|
||||||
val workflowJar = defaultWorkflowJar.copy(name = uniqueWorkflowJarName)
|
|
||||||
|
|
||||||
val bobBaseDir = createSuspendedFlowInBob(setOf(workflowJar, contractJar))
|
|
||||||
|
|
||||||
val cordappsDir = bobBaseDir / "cordapps"
|
|
||||||
val cordappJar = cordappsDir.list().single {
|
|
||||||
! it.toString().contains(uniqueContractJarName) && it.toString().endsWith(".jar")
|
|
||||||
}
|
|
||||||
// Make sure we're dealing with right jar
|
|
||||||
assertThat(cordappJar.fileName.toString()).contains(uniqueWorkflowJarName)
|
|
||||||
|
|
||||||
// The name is part of the MANIFEST so changing it is sufficient to change the jar hash
|
// The name is part of the MANIFEST so changing it is sufficient to change the jar hash
|
||||||
val modifiedCordapp = workflowJar.copy(name = "${workflowJar.name}-modified")
|
val modifiedCordapp = defaultCordapp.copy(name = "${defaultCordapp.name}-modified")
|
||||||
val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp)
|
val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp)
|
||||||
modifiedCordappJar.moveTo(cordappJar, REPLACE_EXISTING)
|
modifiedCordappJar.moveTo(defaultCordappJar, REPLACE_EXISTING)
|
||||||
|
|
||||||
assertBobFailsToStartWithLogMessage(
|
assertBobFailsToStartWithLogMessage(
|
||||||
emptyList(),
|
|
||||||
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
|
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
|
||||||
"that is incompatible with the current installed version of"
|
"that is incompatible with the current installed version of"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set<TestCordapp>): Path {
|
private fun DriverDSL.createSuspendedFlowInBob(): Path {
|
||||||
val (alice, bob) = listOf(ALICE_NAME, BOB_NAME)
|
val (alice, bob) = listOf(
|
||||||
.map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) }
|
startNode(providedName = ALICE_NAME),
|
||||||
.transpose()
|
startNode(NodeParameters(providedName = BOB_NAME, additionalCordapps = listOf(defaultCordapp)))
|
||||||
.getOrThrow()
|
).map { it.getOrThrow() }
|
||||||
alice.stop()
|
|
||||||
val flowTracker = bob.rpc.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
|
alice.stop() // Stop Alice so that Bob never receives the message
|
||||||
// Wait until Bob progresses as far as possible because Alice node is offline
|
|
||||||
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
|
bob.rpc.startFlow(::ReceiverFlow, alice.nodeInfo.singleIdentity())
|
||||||
|
// Wait until Bob's flow has started
|
||||||
|
bob.rpc.stateMachinesFeed().let { it.updates.map { it.id }.startWith(it.snapshot.map { it.id }) }.toBlocking().first()
|
||||||
bob.stop()
|
bob.stop()
|
||||||
return bob.baseDirectory
|
assertCheckpoints(BOB_NAME, 1)
|
||||||
|
|
||||||
|
return (bob.baseDirectory / "cordapps").list().single { it.toString().endsWith(".jar") }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) {
|
private fun DriverDSL.assertBobFailsToStartWithLogMessage(logMessage: String) {
|
||||||
assertFailsWith(ListenProcessDeathException::class) {
|
assertFailsWith(ListenProcessDeathException::class) {
|
||||||
startNode(NodeParameters(
|
startNode(NodeParameters(
|
||||||
providedName = BOB_NAME,
|
providedName = BOB_NAME,
|
||||||
customOverrides = mapOf("devMode" to false),
|
customOverrides = mapOf("devMode" to false)
|
||||||
additionalCordapps = cordapps
|
|
||||||
)).getOrThrow()
|
)).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +95,21 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
|||||||
return DriverParameters(
|
return DriverParameters(
|
||||||
startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts
|
startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts
|
||||||
inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
|
inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
|
||||||
cordappsForAllNodes = emptyList()
|
cordappsForAllNodes = emptyList(),
|
||||||
|
notarySpecs = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InitiatingFlow
|
||||||
|
@StartableByRPC
|
||||||
|
class ReceiverFlow(private val otherParty: Party) : FlowLogic<String>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatedBy(ReceiverFlow::class)
|
||||||
|
class SenderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() = otherSide.send("Hello!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,33 @@
|
|||||||
package net.corda.traderdemo
|
package net.corda.traderdemo
|
||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.millis
|
import net.corda.core.utilities.millis
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.USD
|
||||||
|
import net.corda.finance.contracts.getCashBalance
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
import net.corda.finance.flows.CashPaymentFlow
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.BOC_NAME
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
import net.corda.testing.driver.InProcess
|
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||||
import net.corda.testing.driver.OutOfProcess
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.*
|
||||||
import net.corda.testing.node.TestCordapp
|
import net.corda.testing.node.TestCordapp
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||||
|
import net.corda.testing.node.internal.assertCheckpoints
|
||||||
import net.corda.testing.node.internal.poll
|
import net.corda.testing.node.internal.poll
|
||||||
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
|
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
|
||||||
import net.corda.traderdemo.flow.SellerFlow
|
import net.corda.traderdemo.flow.SellerFlow
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.sql.DriverManager
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class TraderDemoTest {
|
class TraderDemoTest {
|
||||||
@ -85,27 +91,20 @@ class TraderDemoTest {
|
|||||||
inMemoryDB = false,
|
inMemoryDB = false,
|
||||||
cordappsForAllNodes = FINANCE_CORDAPPS + TestCordapp.findCordapp("net.corda.traderdemo")
|
cordappsForAllNodes = FINANCE_CORDAPPS + TestCordapp.findCordapp("net.corda.traderdemo")
|
||||||
)) {
|
)) {
|
||||||
val demoUser = User("demo", "demo", setOf(startFlow<SellerFlow>(), all()))
|
val (buyer, seller, bank) = listOf(
|
||||||
val bankUser = User("user1", "test", permissions = setOf(all()))
|
startNode(providedName = DUMMY_BANK_A_NAME),
|
||||||
val (nodeA, nodeB, bankNode) = listOf(
|
startNode(providedName = DUMMY_BANK_B_NAME),
|
||||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser)),
|
startNode(providedName = BOC_NAME)
|
||||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)),
|
).map { it.getOrThrow() }
|
||||||
startNode(providedName = BOC_NAME, rpcUsers = listOf(bankUser))
|
TraderDemoClientApi(bank.rpc).runIssuer(amount = 100.DOLLARS, buyerName = DUMMY_BANK_A_NAME, sellerName = DUMMY_BANK_B_NAME)
|
||||||
).map { (it.getOrThrow() as OutOfProcess) }
|
val saleFuture = seller.rpc.startFlow(::SellerFlow, buyer.nodeInfo.singleIdentity(), 5.DOLLARS).returnValue
|
||||||
|
buyer.rpc.stateMachinesFeed().updates.toBlocking().first() // wait until initiated flow starts
|
||||||
val nodeBRpc = CordaRPCClient(nodeB.rpcAddress).start(demoUser.username, demoUser.password).proxy
|
buyer.stop()
|
||||||
val nodeARpc = CordaRPCClient(nodeA.rpcAddress).start(demoUser.username, demoUser.password).proxy
|
assertCheckpoints(DUMMY_BANK_A_NAME, 1)
|
||||||
val nodeBankRpc = let {
|
val buyer2 = startNode(providedName = DUMMY_BANK_A_NAME, customOverrides = mapOf("p2pAddress" to buyer.p2pAddress.toString())).getOrThrow()
|
||||||
val client = CordaRPCClient(bankNode.rpcAddress)
|
saleFuture.getOrThrow()
|
||||||
client.start(bankUser.username, bankUser.password).proxy
|
assertThat(buyer2.rpc.getCashBalance(USD)).isEqualTo(95.DOLLARS)
|
||||||
}
|
assertThat(seller.rpc.getCashBalance(USD)).isEqualTo(5.DOLLARS)
|
||||||
|
|
||||||
TraderDemoClientApi(nodeBankRpc).runIssuer(amount = 100.DOLLARS, buyerName = nodeA.nodeInfo.singleIdentity().name, sellerName = nodeB.nodeInfo.singleIdentity().name)
|
|
||||||
val stxFuture = nodeBRpc.startFlow(::SellerFlow, nodeA.nodeInfo.singleIdentity(), 5.DOLLARS).returnValue
|
|
||||||
nodeARpc.stateMachinesFeed().updates.toBlocking().first() // wait until initiated flow starts
|
|
||||||
nodeA.stop()
|
|
||||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to nodeA.p2pAddress.toString()))
|
|
||||||
stxFuture.getOrThrow()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,11 @@ import net.corda.core.CordaException
|
|||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.context.InvocationContext
|
import net.corda.core.context.InvocationContext
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.FlowStateMachine
|
import net.corda.core.internal.FlowStateMachine
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.times
|
import net.corda.core.internal.times
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||||
@ -20,6 +22,7 @@ import net.corda.core.utilities.millis
|
|||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.api.StartedNodeServices
|
import net.corda.node.services.api.StartedNodeServices
|
||||||
import net.corda.node.services.messaging.Message
|
import net.corda.node.services.messaging.Message
|
||||||
|
import net.corda.testing.driver.DriverDSL
|
||||||
import net.corda.testing.driver.NodeHandle
|
import net.corda.testing.driver.NodeHandle
|
||||||
import net.corda.testing.internal.chooseIdentity
|
import net.corda.testing.internal.chooseIdentity
|
||||||
import net.corda.testing.internal.createTestSerializationEnv
|
import net.corda.testing.internal.createTestSerializationEnv
|
||||||
@ -29,12 +32,14 @@ import net.corda.testing.node.TestCordapp
|
|||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.node.testContext
|
import net.corda.testing.node.testContext
|
||||||
import org.apache.commons.lang.ClassUtils
|
import org.apache.commons.lang.ClassUtils
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.AsyncSubject
|
import rx.subjects.AsyncSubject
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
import java.sql.DriverManager
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -239,6 +244,15 @@ fun CordaRPCOps.waitForShutdown(): Observable<Unit> {
|
|||||||
return completable
|
return completable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun DriverDSL.assertCheckpoints(name: CordaX500Name, expected: Long) {
|
||||||
|
DriverManager.getConnection("jdbc:h2:file:${baseDirectory(name) / "persistence"}", "sa", "").use { connection ->
|
||||||
|
connection.createStatement().executeQuery("select count(*) from NODE_CHECKPOINTS").use { rs ->
|
||||||
|
rs.next()
|
||||||
|
assertThat(rs.getLong(1)).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should only be used by Driver and MockNode.
|
* Should only be used by Driver and MockNode.
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user