mirror of
https://github.com/corda/corda.git
synced 2024-12-25 07:31:10 +00:00
Merge pull request #3285 from corda/CORDA-1191/aslemmer-fix-smm-bugs
CORDA-1191 SMM bugs
This commit is contained in:
commit
c66228adf4
@ -0,0 +1,240 @@
|
|||||||
|
package net.corda.node.services.statemachine
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.core.flows.*
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.concurrent.fork
|
||||||
|
import net.corda.core.internal.concurrent.transpose
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import net.corda.core.messaging.startFlow
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.core.utilities.unwrap
|
||||||
|
import net.corda.node.services.Permissions
|
||||||
|
import net.corda.testing.common.internal.ProjectStructure
|
||||||
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
|
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||||
|
import net.corda.testing.core.singleIdentity
|
||||||
|
import net.corda.testing.driver.DriverParameters
|
||||||
|
import net.corda.testing.driver.OutOfProcess
|
||||||
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.testing.node.User
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class HardRestartTest {
|
||||||
|
@StartableByRPC
|
||||||
|
@InitiatingFlow
|
||||||
|
class Ping(val pongParty: Party, val times: Int) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() {
|
||||||
|
val pongSession = initiateFlow(pongParty)
|
||||||
|
pongSession.sendAndReceive<Unit>(times)
|
||||||
|
for (i in 1 .. times) {
|
||||||
|
val j = pongSession.sendAndReceive<Int>(i).unwrap { it }
|
||||||
|
assertEquals(i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatedBy(Ping::class)
|
||||||
|
class Pong(val pingSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() {
|
||||||
|
val times = pingSession.sendAndReceive<Int>(Unit).unwrap { it }
|
||||||
|
for (i in 1 .. times) {
|
||||||
|
val j = pingSession.sendAndReceive<Int>(i).unwrap { it }
|
||||||
|
assertEquals(i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val logConfigFile = ProjectStructure.projectRootDir / "config" / "dev" / "log4j2.xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun restartShortPingPongFlowRandomly() {
|
||||||
|
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||||
|
val (a, b) = listOf(
|
||||||
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
).transpose().getOrThrow()
|
||||||
|
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
// We kill -9 and restart the Pong node after a random sleep
|
||||||
|
val pongRestartThread = thread {
|
||||||
|
latch.await()
|
||||||
|
val ms = Random().nextInt(1000)
|
||||||
|
println("Sleeping $ms ms before kill")
|
||||||
|
Thread.sleep(ms.toLong())
|
||||||
|
(b as OutOfProcess).process.destroyForcibly()
|
||||||
|
b.stop()
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
}
|
||||||
|
CordaRPCClient(a.rpcAddress).use(demoUser.username, demoUser.password) {
|
||||||
|
val returnValue = it.proxy.startFlow(::Ping, b.nodeInfo.singleIdentity(), 1).returnValue
|
||||||
|
latch.countDown()
|
||||||
|
// No matter the kill
|
||||||
|
returnValue.getOrThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pongRestartThread.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun restartLongPingPongFlowRandomly() {
|
||||||
|
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||||
|
val (a, b) = listOf(
|
||||||
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
).transpose().getOrThrow()
|
||||||
|
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
// We kill -9 and restart the Pong node after a random sleep
|
||||||
|
val pongRestartThread = thread {
|
||||||
|
latch.await()
|
||||||
|
val ms = Random().nextInt(1000)
|
||||||
|
println("Sleeping $ms ms before kill")
|
||||||
|
Thread.sleep(ms.toLong())
|
||||||
|
(b as OutOfProcess).process.destroyForcibly()
|
||||||
|
b.stop()
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
}
|
||||||
|
CordaRPCClient(a.rpcAddress).use(demoUser.username, demoUser.password) {
|
||||||
|
val returnValue = it.proxy.startFlow(::Ping, b.nodeInfo.singleIdentity(), 100).returnValue
|
||||||
|
latch.countDown()
|
||||||
|
// No matter the kill
|
||||||
|
returnValue.getOrThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pongRestartThread.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun softRestartLongPingPongFlowRandomly() {
|
||||||
|
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||||
|
val (a, b) = listOf(
|
||||||
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
).transpose().getOrThrow()
|
||||||
|
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
// We kill -9 and restart the Pong node after a random sleep
|
||||||
|
val pongRestartThread = thread {
|
||||||
|
latch.await()
|
||||||
|
val ms = Random().nextInt(1000)
|
||||||
|
println("Sleeping $ms ms before kill")
|
||||||
|
Thread.sleep(ms.toLong())
|
||||||
|
b.stop()
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
}
|
||||||
|
CordaRPCClient(a.rpcAddress).use(demoUser.username, demoUser.password) {
|
||||||
|
val returnValue = it.proxy.startFlow(::Ping, b.nodeInfo.singleIdentity(), 100).returnValue
|
||||||
|
latch.countDown()
|
||||||
|
// No matter the kill
|
||||||
|
returnValue.getOrThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
pongRestartThread.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class RecursiveMode {
|
||||||
|
data class Top(val otherParty: Party, val initialDepth: Int) : RecursiveMode()
|
||||||
|
data class Recursive(val otherSession: FlowSession) : RecursiveMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
@InitiatingFlow
|
||||||
|
@InitiatedBy(RecursiveB::class)
|
||||||
|
class RecursiveA(val mode: RecursiveMode) : FlowLogic<String>() {
|
||||||
|
constructor(otherSession: FlowSession) : this(RecursiveMode.Recursive(otherSession))
|
||||||
|
constructor(otherParty: Party, initialDepth: Int) : this(RecursiveMode.Top(otherParty, initialDepth))
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): String {
|
||||||
|
return when (mode) {
|
||||||
|
is HardRestartTest.RecursiveMode.Top -> {
|
||||||
|
val session = initiateFlow(mode.otherParty)
|
||||||
|
session.sendAndReceive<String>(mode.initialDepth).unwrap { it }
|
||||||
|
}
|
||||||
|
is HardRestartTest.RecursiveMode.Recursive -> {
|
||||||
|
val depth = mode.otherSession.receive<Int>().unwrap { it }
|
||||||
|
val string = if (depth > 0) {
|
||||||
|
val newSession = initiateFlow(mode.otherSession.counterparty)
|
||||||
|
newSession.sendAndReceive<String>(depth).unwrap { it }
|
||||||
|
} else {
|
||||||
|
"-"
|
||||||
|
}
|
||||||
|
mode.otherSession.send(string)
|
||||||
|
string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitiatingFlow
|
||||||
|
@InitiatedBy(RecursiveA::class)
|
||||||
|
class RecursiveB(val otherSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call() {
|
||||||
|
val depth = otherSession.receive<Int>().unwrap { it }
|
||||||
|
val newSession = initiateFlow(otherSession.counterparty)
|
||||||
|
val string = newSession.sendAndReceive<String>(depth - 1).unwrap { it }
|
||||||
|
otherSession.send(string + ":" + depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun restartRecursiveFlowRandomly() {
|
||||||
|
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<RecursiveA>(), Permissions.all()))
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) {
|
||||||
|
val (a, b) = listOf(
|
||||||
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")),
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
).transpose().getOrThrow()
|
||||||
|
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
// We kill -9 and restart the node B after a random sleep
|
||||||
|
val bRestartThread = thread {
|
||||||
|
latch.await()
|
||||||
|
val ms = Random().nextInt(1000)
|
||||||
|
println("Sleeping $ms ms before kill")
|
||||||
|
Thread.sleep(ms.toLong())
|
||||||
|
(b as OutOfProcess).process.destroyForcibly()
|
||||||
|
b.stop()
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000"))
|
||||||
|
}
|
||||||
|
val executor = Executors.newFixedThreadPool(8)
|
||||||
|
try {
|
||||||
|
val tlRpc = ThreadLocal<CordaRPCOps>()
|
||||||
|
(1 .. 10).map { num ->
|
||||||
|
executor.fork {
|
||||||
|
val rpc = tlRpc.get() ?: CordaRPCClient(a.rpcAddress).start(demoUser.username, demoUser.password).proxy.also { tlRpc.set(it) }
|
||||||
|
val string = rpc.startFlow(::RecursiveA, b.nodeInfo.singleIdentity(), 10).returnValue.getOrThrow()
|
||||||
|
latch.countDown()
|
||||||
|
println("$num: $string")
|
||||||
|
}
|
||||||
|
}.transpose().getOrThrow()
|
||||||
|
|
||||||
|
bRestartThread.join()
|
||||||
|
} finally {
|
||||||
|
executor.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package net.corda.node.services.messaging
|
|||||||
|
|
||||||
import net.corda.core.messaging.MessageRecipients
|
import net.corda.core.messaging.MessageRecipients
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.services.statemachine.FlowMessagingImpl
|
import net.corda.node.services.statemachine.FlowMessagingImpl
|
||||||
@ -41,6 +42,7 @@ class MessagingExecutor(
|
|||||||
val amqDelayMillis = System.getProperty("amq.delivery.delay.ms", "0").toInt()
|
val amqDelayMillis = System.getProperty("amq.delivery.delay.ms", "0").toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun send(message: Message, target: MessageRecipients) {
|
fun send(message: Message, target: MessageRecipients) {
|
||||||
val mqAddress = resolver.resolveTargetToArtemisQueue(target)
|
val mqAddress = resolver.resolveTargetToArtemisQueue(target)
|
||||||
val artemisMessage = cordaToArtemisMessage(message)
|
val artemisMessage = cordaToArtemisMessage(message)
|
||||||
@ -51,7 +53,12 @@ class MessagingExecutor(
|
|||||||
producer.send(SimpleString(mqAddress), artemisMessage)
|
producer.send(SimpleString(mqAddress), artemisMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun acknowledge(message: ClientMessage) {
|
fun acknowledge(message: ClientMessage) {
|
||||||
|
log.debug {
|
||||||
|
val id = message.getStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID)
|
||||||
|
"Acking $id"
|
||||||
|
}
|
||||||
message.individualAcknowledge()
|
message.individualAcknowledge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,12 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
val eventQueue = getTransientField(TransientValues::eventQueue)
|
val eventQueue = getTransientField(TransientValues::eventQueue)
|
||||||
try {
|
try {
|
||||||
eventLoop@ while (true) {
|
eventLoop@ while (true) {
|
||||||
val nextEvent = eventQueue.receive()
|
val nextEvent = try {
|
||||||
|
eventQueue.receive()
|
||||||
|
} catch (interrupted: InterruptedException) {
|
||||||
|
log.error("Flow interrupted while waiting for events, aborting immediately")
|
||||||
|
abortFiber()
|
||||||
|
}
|
||||||
val continuation = processEvent(transitionExecutor, nextEvent)
|
val continuation = processEvent(transitionExecutor, nextEvent)
|
||||||
when (continuation) {
|
when (continuation) {
|
||||||
is FlowContinuation.Resume -> return continuation.result
|
is FlowContinuation.Resume -> return continuation.result
|
||||||
@ -166,7 +171,10 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
* processing finished. Purely used for internal invariant checks.
|
* processing finished. Purely used for internal invariant checks.
|
||||||
*/
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun processEventImmediately(event: Event, isDbTransactionOpenOnEntry: Boolean, isDbTransactionOpenOnExit: Boolean): FlowContinuation {
|
private fun processEventImmediately(
|
||||||
|
event: Event,
|
||||||
|
isDbTransactionOpenOnEntry: Boolean,
|
||||||
|
isDbTransactionOpenOnExit: Boolean): FlowContinuation {
|
||||||
checkDbTransaction(isDbTransactionOpenOnEntry)
|
checkDbTransaction(isDbTransactionOpenOnEntry)
|
||||||
val transitionExecutor = getTransientField(TransientValues::transitionExecutor)
|
val transitionExecutor = getTransientField(TransientValues::transitionExecutor)
|
||||||
val continuation = processEvent(transitionExecutor, event)
|
val continuation = processEvent(transitionExecutor, event)
|
||||||
@ -246,7 +254,9 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
processEventImmediately(
|
processEventImmediately(
|
||||||
Event.EnterSubFlow(subFlow.javaClass,
|
Event.EnterSubFlow(subFlow.javaClass,
|
||||||
createSubFlowVersion(
|
createSubFlowVersion(
|
||||||
serviceHub.cordappProvider.getCordappForFlow(subFlow), serviceHub.myInfo.platformVersion)),
|
serviceHub.cordappProvider.getCordappForFlow(subFlow), serviceHub.myInfo.platformVersion
|
||||||
|
)
|
||||||
|
),
|
||||||
isDbTransactionOpenOnEntry = true,
|
isDbTransactionOpenOnEntry = true,
|
||||||
isDbTransactionOpenOnExit = true
|
isDbTransactionOpenOnExit = true
|
||||||
)
|
)
|
||||||
|
@ -322,6 +322,7 @@ class SingleThreadedStateMachineManager(
|
|||||||
mutex.locked { if (flows.containsKey(id)) return@map null }
|
mutex.locked { if (flows.containsKey(id)) return@map null }
|
||||||
val checkpoint = deserializeCheckpoint(serializedCheckpoint)
|
val checkpoint = deserializeCheckpoint(serializedCheckpoint)
|
||||||
if (checkpoint == null) return@map null
|
if (checkpoint == null) return@map null
|
||||||
|
logger.debug { "Restored $checkpoint" }
|
||||||
createFlowFromCheckpoint(
|
createFlowFromCheckpoint(
|
||||||
id = id,
|
id = id,
|
||||||
checkpoint = checkpoint,
|
checkpoint = checkpoint,
|
||||||
@ -370,7 +371,10 @@ class SingleThreadedStateMachineManager(
|
|||||||
// Just flow initiation message
|
// Just flow initiation message
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
externalEventMutex.withLock {
|
mutex.locked {
|
||||||
|
if (stopping) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Remove any sessions the old flow has.
|
// Remove any sessions the old flow has.
|
||||||
for (sessionId in getFlowSessionIds(currentState.checkpoint)) {
|
for (sessionId in getFlowSessionIds(currentState.checkpoint)) {
|
||||||
sessionToFlow.remove(sessionId)
|
sessionToFlow.remove(sessionId)
|
||||||
@ -391,12 +395,13 @@ class SingleThreadedStateMachineManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val externalEventMutex = ReentrantLock()
|
|
||||||
override fun deliverExternalEvent(event: ExternalEvent) {
|
override fun deliverExternalEvent(event: ExternalEvent) {
|
||||||
externalEventMutex.withLock {
|
mutex.locked {
|
||||||
when (event) {
|
if (!stopping) {
|
||||||
is ExternalEvent.ExternalMessageEvent -> onSessionMessage(event)
|
when (event) {
|
||||||
is ExternalEvent.ExternalStartFlowEvent<*> -> onExternalStartFlow(event)
|
is ExternalEvent.ExternalMessageEvent -> onSessionMessage(event)
|
||||||
|
is ExternalEvent.ExternalStartFlowEvent<*> -> onExternalStartFlow(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ crlCheckSoftFail = true
|
|||||||
lazyBridgeStart = true
|
lazyBridgeStart = true
|
||||||
dataSourceProperties = {
|
dataSourceProperties = {
|
||||||
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
||||||
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100;AUTO_SERVER_PORT="${h2port}
|
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000;AUTO_SERVER_PORT="${h2port}
|
||||||
dataSource.user = sa
|
dataSource.user = sa
|
||||||
dataSource.password = ""
|
dataSource.password = ""
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.traderdemo
|
package net.corda.traderdemo
|
||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
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
|
||||||
@ -14,6 +15,7 @@ import net.corda.testing.core.DUMMY_BANK_B_NAME
|
|||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.InProcess
|
import net.corda.testing.driver.InProcess
|
||||||
|
import net.corda.testing.driver.OutOfProcess
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.node.internal.poll
|
import net.corda.testing.node.internal.poll
|
||||||
@ -71,4 +73,31 @@ class TraderDemoTest {
|
|||||||
assertThat(clientB.dollarCashBalance).isEqualTo(5.DOLLARS)
|
assertThat(clientB.dollarCashBalance).isEqualTo(5.DOLLARS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Tudor test`() {
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||||
|
val demoUser = User("demo", "demo", setOf(startFlow<SellerFlow>(), all()))
|
||||||
|
val bankUser = User("user1", "test", permissions = setOf(all()))
|
||||||
|
val (nodeA, nodeB, bankNode) = listOf(
|
||||||
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser)),
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)),
|
||||||
|
startNode(providedName = BOC_NAME, rpcUsers = listOf(bankUser))
|
||||||
|
).map { (it.getOrThrow() as OutOfProcess) }
|
||||||
|
|
||||||
|
val nodeBRpc = CordaRPCClient(nodeB.rpcAddress).start(demoUser.username, demoUser.password).proxy
|
||||||
|
val nodeARpc = CordaRPCClient(nodeA.rpcAddress).start(demoUser.username, demoUser.password).proxy
|
||||||
|
val nodeBankRpc = let {
|
||||||
|
val client = CordaRPCClient(bankNode.rpcAddress)
|
||||||
|
client.start(bankUser.username, bankUser.password).proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user