Skip to content

Commit 1c012f6

Browse files
authored
Back porting clean up of FlowFrameworkTests.kt made in ENT (corda#4218)
1 parent 369f23e commit 1c012f6

File tree

4 files changed

+475
-433
lines changed

4 files changed

+475
-433
lines changed

node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt

+6-7
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import net.corda.confidential.SwapIdentitiesHandler
99
import net.corda.core.CordaException
1010
import net.corda.core.concurrent.CordaFuture
1111
import net.corda.core.context.InvocationContext
12-
import net.corda.core.crypto.internal.AliasPrivateKey
1312
import net.corda.core.crypto.DigitalSignature
1413
import net.corda.core.crypto.SecureHash
14+
import net.corda.core.crypto.internal.AliasPrivateKey
1515
import net.corda.core.crypto.newSecureRandom
1616
import net.corda.core.flows.*
1717
import net.corda.core.identity.AbstractParty
@@ -122,14 +122,14 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
122122
cacheFactoryPrototype: BindableNamedCacheFactory,
123123
protected val versionInfo: VersionInfo,
124124
protected val flowManager: FlowManager,
125-
protected val serverThread: AffinityExecutor.ServiceAffinityExecutor,
126-
private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
125+
val serverThread: AffinityExecutor.ServiceAffinityExecutor,
126+
val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
127127

128128
protected abstract val log: Logger
129129
@Suppress("LeakingThis")
130130
private var tokenizableServices: MutableList<Any>? = mutableListOf(platformClock, this)
131131

132-
protected val metricRegistry = MetricRegistry()
132+
val metricRegistry = MetricRegistry()
133133
protected val cacheFactory = cacheFactoryPrototype.bindWithConfig(configuration).bindWithMetrics(metricRegistry).tokenize()
134134
val monitoringService = MonitoringService(metricRegistry).tokenize()
135135

@@ -146,7 +146,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
146146
}
147147
}
148148

149-
protected val cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo)
149+
val cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo)
150150
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas).tokenize()
151151
val identityService = PersistentIdentityService(cacheFactory).tokenize()
152152
val database: CordaPersistence = createCordaPersistence(
@@ -777,7 +777,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
777777
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
778778
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
779779
// the identity key. But the infrastructure to make that easy isn't here yet.
780-
return BasicHSMKeyManagementService(cacheFactory,identityService, database, cryptoService)
780+
return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService)
781781
}
782782

783783
open fun stop() {
@@ -1008,7 +1008,6 @@ class FlowStarterImpl(private val smm: StateMachineManager, private val flowLogi
10081008
private val _future = openFuture<FlowStateMachine<T>>()
10091009
override val future: CordaFuture<FlowStateMachine<T>>
10101010
get() = _future
1011-
10121011
}
10131012
return startFlow(startFlowEvent)
10141013
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package net.corda.node.services.statemachine
2+
3+
import net.corda.core.crypto.random63BitValue
4+
import net.corda.core.flows.FlowLogic
5+
import net.corda.core.flows.registerCordappFlowFactory
6+
import net.corda.core.identity.Party
7+
import net.corda.core.utilities.getOrThrow
8+
import net.corda.node.services.persistence.checkpoints
9+
import net.corda.testing.core.ALICE_NAME
10+
import net.corda.testing.core.BOB_NAME
11+
import net.corda.testing.core.CHARLIE_NAME
12+
import net.corda.testing.core.singleIdentity
13+
import net.corda.testing.internal.LogHelper
14+
import net.corda.testing.node.InMemoryMessagingNetwork
15+
import net.corda.testing.node.internal.*
16+
import org.assertj.core.api.Assertions.assertThat
17+
import org.junit.After
18+
import org.junit.Before
19+
import org.junit.Ignore
20+
import org.junit.Test
21+
import rx.Observable
22+
import java.util.*
23+
import kotlin.test.assertEquals
24+
import kotlin.test.assertTrue
25+
26+
class FlowFrameworkPersistenceTests {
27+
companion object {
28+
init {
29+
LogHelper.setLevel("+net.corda.flow")
30+
}
31+
}
32+
33+
private lateinit var mockNet: InternalMockNetwork
34+
private val receivedSessionMessages = ArrayList<SessionTransfer>()
35+
private lateinit var aliceNode: TestStartedNode
36+
private lateinit var bobNode: TestStartedNode
37+
private lateinit var notaryIdentity: Party
38+
private lateinit var alice: Party
39+
private lateinit var bob: Party
40+
private lateinit var aliceFlowManager: MockNodeFlowManager
41+
private lateinit var bobFlowManager: MockNodeFlowManager
42+
43+
@Before
44+
fun start() {
45+
mockNet = InternalMockNetwork(
46+
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
47+
servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()
48+
)
49+
aliceFlowManager = MockNodeFlowManager()
50+
bobFlowManager = MockNodeFlowManager()
51+
52+
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, flowManager = aliceFlowManager))
53+
bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME, flowManager = bobFlowManager))
54+
55+
receivedSessionMessagesObservable().forEach { receivedSessionMessages += it }
56+
57+
// Extract identities
58+
alice = aliceNode.info.singleIdentity()
59+
bob = bobNode.info.singleIdentity()
60+
notaryIdentity = mockNet.defaultNotaryIdentity
61+
}
62+
63+
@After
64+
fun cleanUp() {
65+
mockNet.stopNodes()
66+
receivedSessionMessages.clear()
67+
}
68+
69+
@Test
70+
fun `newly added flow is preserved on restart`() {
71+
aliceNode.services.startFlow(NoOpFlow(nonTerminating = true))
72+
aliceNode.internals.acceptableLiveFiberCountOnStop = 1
73+
val restoredFlow = aliceNode.restartAndGetRestoredFlow<NoOpFlow>()
74+
assertThat(restoredFlow.flowStarted).isTrue()
75+
}
76+
77+
@Test
78+
fun `flow restarted just after receiving payload`() {
79+
bobNode.registerCordappFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it)
80+
.nonTerminating() }
81+
aliceNode.services.startFlow(SendFlow("Hello", bob))
82+
83+
// We push through just enough messages to get only the payload sent
84+
bobNode.pumpReceive()
85+
bobNode.internals.disableDBCloseOnStop()
86+
bobNode.internals.acceptableLiveFiberCountOnStop = 1
87+
bobNode.dispose()
88+
mockNet.runNetwork()
89+
val restoredFlow = bobNode.restartAndGetRestoredFlow<InitiatedReceiveFlow>()
90+
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello")
91+
}
92+
93+
@Test
94+
fun `flow loaded from checkpoint will respond to messages from before start`() {
95+
aliceNode.registerCordappFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) }
96+
bobNode.services.startFlow(ReceiveFlow(alice).nonTerminating()) // Prepare checkpointed receive flow
97+
val restoredFlow = bobNode.restartAndGetRestoredFlow<ReceiveFlow>()
98+
assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello")
99+
}
100+
101+
@Ignore("Some changes in startup order make this test's assumptions fail.")
102+
@Test
103+
fun `flow with send will resend on interrupted restart`() {
104+
val payload = random63BitValue()
105+
val payload2 = random63BitValue()
106+
107+
var sentCount = 0
108+
mockNet.messagingNetwork.sentMessages.toSessionTransfers().filter { it.isPayloadTransfer }.forEach { sentCount++ }
109+
val charlieNode = mockNet.createNode(InternalMockNodeParameters(legalName = CHARLIE_NAME))
110+
val secondFlow = charlieNode.registerCordappFlowFactory(PingPongFlow::class) { PingPongFlow(it, payload2) }
111+
mockNet.runNetwork()
112+
val charlie = charlieNode.info.singleIdentity()
113+
114+
// Kick off first send and receive
115+
bobNode.services.startFlow(PingPongFlow(charlie, payload))
116+
bobNode.database.transaction {
117+
assertEquals(1, bobNode.internals.checkpointStorage.checkpoints().size)
118+
}
119+
// Make sure the add() has finished initial processing.
120+
bobNode.internals.disableDBCloseOnStop()
121+
// Restart node and thus reload the checkpoint and resend the message with same UUID
122+
bobNode.dispose()
123+
bobNode.database.transaction {
124+
assertEquals(1, bobNode.internals.checkpointStorage.checkpoints().size) // confirm checkpoint
125+
bobNode.services.networkMapCache.clearNetworkMapCache()
126+
}
127+
val node2b = mockNet.createNode(InternalMockNodeParameters(bobNode.internals.id))
128+
bobNode.internals.manuallyCloseDB()
129+
val (firstAgain, fut1) = node2b.getSingleFlow<PingPongFlow>()
130+
// Run the network which will also fire up the second flow. First message should get deduped. So message data stays in sync.
131+
mockNet.runNetwork()
132+
fut1.getOrThrow()
133+
134+
val receivedCount = receivedSessionMessages.count { it.isPayloadTransfer }
135+
// Check flows completed cleanly and didn't get out of phase
136+
assertEquals(4, receivedCount, "Flow should have exchanged 4 unique messages")// Two messages each way
137+
// can't give a precise value as every addMessageHandler re-runs the undelivered messages
138+
assertTrue(sentCount > receivedCount, "Node restart should have retransmitted messages")
139+
node2b.database.transaction {
140+
assertEquals(0, node2b.internals.checkpointStorage.checkpoints().size, "Checkpoints left after restored flow should have ended")
141+
}
142+
charlieNode.database.transaction {
143+
assertEquals(0, charlieNode.internals.checkpointStorage.checkpoints().size, "Checkpoints left after restored flow should have ended")
144+
}
145+
assertEquals(payload2, firstAgain.receivedPayload, "Received payload does not match the first value on Node 3")
146+
assertEquals(payload2 + 1, firstAgain.receivedPayload2, "Received payload does not match the expected second value on Node 3")
147+
assertEquals(payload, secondFlow.getOrThrow().receivedPayload, "Received payload does not match the (restarted) first value on Node 2")
148+
assertEquals(payload + 1, secondFlow.getOrThrow().receivedPayload2, "Received payload does not match the expected second value on Node 2")
149+
}
150+
151+
////////////////////////////////////////////////////////////////////////////////////////////////////////////
152+
//region Helpers
153+
154+
private inline fun <reified P : FlowLogic<*>> TestStartedNode.restartAndGetRestoredFlow(): P {
155+
val newNode = mockNet.restartNode(this)
156+
newNode.internals.acceptableLiveFiberCountOnStop = 1
157+
mockNet.runNetwork()
158+
return newNode.getSingleFlow<P>().first
159+
}
160+
161+
private fun receivedSessionMessagesObservable(): Observable<SessionTransfer> {
162+
return mockNet.messagingNetwork.receivedMessages.toSessionTransfers()
163+
}
164+
165+
//endregion Helpers
166+
}

0 commit comments

Comments
 (0)