diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 0fe1b48557..519b668cc6 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -52,7 +52,7 @@ class NodeMonitorModelTest : DriverBasedTest() { lateinit var networkMapUpdates: Observable lateinit var newNode: (CordaX500Name) -> NodeInfo - override fun setup() = driver { + override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) { val cashUser = User("user1", "test", permissions = setOf( startFlowPermission(), startFlowPermission(), diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt index 127e93bc3c..e5f68de31f 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt @@ -3,8 +3,8 @@ package net.corda.confidential import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.ContractState import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.identity.AbstractParty -import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.ProgressTracker @@ -22,10 +22,10 @@ object IdentitySyncFlow { * @return a mapping of well known identities to the confidential identities used in the transaction. */ // TODO: Can this be triggered automatically from [SendTransactionFlow] - class Send(val otherSides: Set, + class Send(val otherSideSessions: Set, val tx: WireTransaction, override val progressTracker: ProgressTracker) : FlowLogic() { - constructor(otherSide: Party, tx: WireTransaction) : this(setOf(otherSide), tx, tracker()) + constructor(otherSide: FlowSession, tx: WireTransaction) : this(setOf(otherSide), tx, tracker()) companion object { object SYNCING_IDENTITIES : ProgressTracker.Step("Syncing identities") @@ -45,9 +45,9 @@ object IdentitySyncFlow { val identityCertificates: Map = identities .map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap() - otherSides.forEach { otherSide -> - val requestedIdentities: List = sendAndReceive>(otherSide, confidentialIdentities).unwrap { req -> - require(req.all { it in identityCertificates.keys }) { "${otherSide} requested a confidential identity not part of transaction: ${tx.id}" } + otherSideSessions.forEach { otherSideSession -> + val requestedIdentities: List = otherSideSession.sendAndReceive>(confidentialIdentities).unwrap { req -> + require(req.all { it in identityCertificates.keys }) { "${otherSideSession.counterparty} requested a confidential identity not part of transaction: ${tx.id}" } req } val sendIdentities: List = requestedIdentities.map { @@ -57,7 +57,7 @@ object IdentitySyncFlow { else throw IllegalStateException("Counterparty requested a confidential identity for which we do not have the certificate path: ${tx.id}") } - send(otherSide, sendIdentities) + otherSideSession.send(sendIdentities) } } @@ -67,7 +67,7 @@ object IdentitySyncFlow { * Handle an offer to provide proof of identity (in the form of certificate paths) for confidential identities which * we do not yet know about. */ - class Receive(val otherSide: Party) : FlowLogic() { + class Receive(val otherSideSession: FlowSession) : FlowLogic() { companion object { object RECEIVING_IDENTITIES : ProgressTracker.Step("Receiving confidential identities") object RECEIVING_CERTIFICATES : ProgressTracker.Step("Receiving certificates for unknown identities") @@ -78,10 +78,10 @@ object IdentitySyncFlow { @Suspendable override fun call(): Unit { progressTracker.currentStep = RECEIVING_IDENTITIES - val allIdentities = receive>(otherSide).unwrap { it } + val allIdentities = otherSideSession.receive>().unwrap { it } val unknownIdentities = allIdentities.filter { serviceHub.identityService.partyFromAnonymous(it) == null } progressTracker.currentStep = RECEIVING_CERTIFICATES - val missingIdentities = sendAndReceive>(otherSide, unknownIdentities) + val missingIdentities = otherSideSession.sendAndReceive>(unknownIdentities) // Batch verify the identities we've received, so we know they're all correct before we start storing them in // the identity service diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt index 58ab4ea99e..7257620d19 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesFlow.kt @@ -18,10 +18,10 @@ import net.corda.core.utilities.unwrap */ @StartableByRPC @InitiatingFlow -class SwapIdentitiesFlow(private val otherSide: Party, +class SwapIdentitiesFlow(private val otherParty: Party, private val revocationEnabled: Boolean, override val progressTracker: ProgressTracker) : FlowLogic>() { - constructor(otherSide: Party) : this(otherSide, false, tracker()) + constructor(otherParty: Party) : this(otherParty, false, tracker()) companion object { object AWAITING_KEY : ProgressTracker.Step("Awaiting key") @@ -43,14 +43,15 @@ class SwapIdentitiesFlow(private val otherSide: Party, // Special case that if we're both parties, a single identity is generated val identities = LinkedHashMap() - if (serviceHub.myInfo.isLegalIdentity(otherSide)) { - identities.put(otherSide, legalIdentityAnonymous.party.anonymise()) + if (serviceHub.myInfo.isLegalIdentity(otherParty)) { + identities.put(otherParty, legalIdentityAnonymous.party.anonymise()) } else { - val anonymousOtherSide = sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity -> - validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity) + val otherSession = initiateFlow(otherParty) + val anonymousOtherSide = otherSession.sendAndReceive(legalIdentityAnonymous).unwrap { confidentialIdentity -> + validateAndRegisterIdentity(serviceHub.identityService, otherSession.counterparty, confidentialIdentity) } identities.put(ourIdentity, legalIdentityAnonymous.party.anonymise()) - identities.put(otherSide, anonymousOtherSide.party.anonymise()) + identities.put(otherSession.counterparty, anonymousOtherSide.party.anonymise()) } return identities } diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt index 67d9e0b7b6..753d9a3927 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/SwapIdentitiesHandler.kt @@ -2,13 +2,14 @@ package net.corda.confidential import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party +import net.corda.core.flows.FlowSession import net.corda.core.identity.PartyAndCertificate import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap -class SwapIdentitiesHandler(val otherSide: Party, val revocationEnabled: Boolean) : FlowLogic() { - constructor(otherSide: Party) : this(otherSide, false) +class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEnabled: Boolean) : FlowLogic() { + constructor(otherSideSession: FlowSession) : this(otherSideSession, false) + companion object { object SENDING_KEY : ProgressTracker.Step("Sending key") } @@ -20,8 +21,8 @@ class SwapIdentitiesHandler(val otherSide: Party, val revocationEnabled: Boolean val revocationEnabled = false progressTracker.currentStep = SENDING_KEY val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) - sendAndReceive(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity -> - SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity) + otherSideSession.sendAndReceive(legalIdentityAnonymous).unwrap { confidentialIdentity -> + SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, confidentialIdentity) } } } \ No newline at end of file diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index 5d8ca6c4f6..867931718b 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -2,6 +2,7 @@ package net.corda.confidential import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party @@ -12,11 +13,7 @@ import net.corda.core.utilities.unwrap import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueAndPaymentFlow -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.chooseIdentity -import net.corda.testing.getDefaultNotary +import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before @@ -75,19 +72,20 @@ class IdentitySyncFlowTests { class Initiator(val otherSide: Party, val tx: WireTransaction): FlowLogic() { @Suspendable override fun call(): Boolean { - subFlow(IdentitySyncFlow.Send(otherSide, tx)) + val session = initiateFlow(otherSide) + subFlow(IdentitySyncFlow.Send(session, tx)) // Wait for the counterparty to indicate they're done - return receive(otherSide).unwrap { it } + return session.receive().unwrap { it } } } @InitiatedBy(IdentitySyncFlowTests.Initiator::class) - class Receive(val otherSide: Party): FlowLogic() { + class Receive(val otherSideSession: FlowSession): FlowLogic() { @Suspendable override fun call() { - subFlow(IdentitySyncFlow.Receive(otherSide)) + subFlow(IdentitySyncFlow.Receive(otherSideSession)) // Notify the initiator that we've finished syncing - send(otherSide, true) + otherSideSession.send(true) } } } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt index 323669cffc..44e3dfe982 100644 --- a/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/AbstractStateReplacementFlow.kt @@ -6,13 +6,12 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy -import net.corda.core.identity.Party +import net.corda.core.identity.AbstractParty import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.unwrap -import java.security.PublicKey /** * Abstract flow to be used for replacing one state with another, for example when changing the notary of a state. @@ -33,10 +32,8 @@ abstract class AbstractStateReplacementFlow { * The assembled transaction for upgrading a contract. * * @param stx signed transaction to do the upgrade. - * @param participants the parties involved in the upgrade transaction. - * @param myKey key */ - data class UpgradeTx(val stx: SignedTransaction, val participants: Iterable, val myKey: PublicKey) + data class UpgradeTx(val stx: SignedTransaction) /** * The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants @@ -62,15 +59,11 @@ abstract class AbstractStateReplacementFlow { @Suspendable @Throws(StateReplacementException::class) override fun call(): StateAndRef { - val (stx, participantKeys, myKey) = assembleTx() - + val (stx) = assembleTx() + val participantSessions = getParticipantSessions() progressTracker.currentStep = SIGNING - val signatures = if (participantKeys.singleOrNull() == myKey) { - getNotarySignatures(stx) - } else { - collectSignatures(participantKeys - myKey, stx) - } + val signatures = collectSignatures(participantSessions, stx) val finalTx = stx + signatures serviceHub.recordTransactions(finalTx) @@ -89,35 +82,38 @@ abstract class AbstractStateReplacementFlow { /** * Build the upgrade transaction. * - * @return a triple of the transaction, the public keys of all participants, and the participating public key of - * this node. + * @return the transaction */ abstract protected fun assembleTx(): UpgradeTx - @Suspendable - private fun collectSignatures(participants: Iterable, stx: SignedTransaction): List { - // In identity service we record all identities we know about from network map. - val parties = participants.map { - serviceHub.identityService.partyFromKey(it) ?: - throw IllegalStateException("Participant $it to state $originalState not found on the network") - } + /** + * Initiate sessions with parties we want signatures from. + */ + open fun getParticipantSessions(): List>> { + return serviceHub.excludeMe(serviceHub.groupAbstractPartyByWellKnownParty(originalState.state.data.participants)).map { initiateFlow(it.key) to it.value } + } - val participantSignatures = parties.map { getParticipantSignature(it, stx) } + @Suspendable + private fun collectSignatures(sessions: List>>, stx: SignedTransaction): List { + val participantSignatures = sessions.map { getParticipantSignature(it.first, it.second, stx) } val allPartySignedTx = stx + participantSignatures val allSignatures = participantSignatures + getNotarySignatures(allPartySignedTx) - parties.forEach { send(it, allSignatures) } + sessions.forEach { it.first.send(allSignatures) } return allSignatures } @Suspendable - private fun getParticipantSignature(party: Party, stx: SignedTransaction): TransactionSignature { + private fun getParticipantSignature(session: FlowSession, party: List, stx: SignedTransaction): TransactionSignature { + require(party.size == 1) { + "We do not currently support multiple signatures from the same party ${session.counterparty}: $party" + } val proposal = Proposal(originalState.ref, modification) - subFlow(SendTransactionFlow(party, stx)) - return sendAndReceive(party, proposal).unwrap { - check(party.owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" } + subFlow(SendTransactionFlow(session, stx)) + return session.sendAndReceive(proposal).unwrap { + check(party.single().owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" } it.verify(stx.id) it } @@ -136,9 +132,9 @@ abstract class AbstractStateReplacementFlow { // Type parameter should ideally be Unit but that prevents Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964). // We use Void? instead of Unit? as that's what you'd use in Java. - abstract class Acceptor(val otherSide: Party, + abstract class Acceptor(val initiatingSession: FlowSession, override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic() { - constructor(otherSide: Party) : this(otherSide, Acceptor.tracker()) + constructor(initiatingSession: FlowSession) : this(initiatingSession, Acceptor.tracker()) companion object { object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal") object APPROVING : ProgressTracker.Step("State replacement approved") @@ -151,9 +147,9 @@ abstract class AbstractStateReplacementFlow { override fun call(): Void? { progressTracker.currentStep = VERIFYING // We expect stx to have insufficient signatures here - val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false)) + val stx = subFlow(ReceiveTransactionFlow(initiatingSession, checkSufficientSignatures = false)) checkMySignatureRequired(stx) - val maybeProposal: UntrustworthyData> = receive(otherSide) + val maybeProposal: UntrustworthyData> = initiatingSession.receive() maybeProposal.unwrap { verifyProposal(stx, it) } @@ -166,7 +162,7 @@ abstract class AbstractStateReplacementFlow { progressTracker.currentStep = APPROVING val mySignature = sign(stx) - val swapSignatures = sendAndReceive>(otherSide, mySignature) + val swapSignatures = initiatingSession.sendAndReceive>(mySignature) // TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures. val allSignatures = swapSignatures.unwrap { signatures -> diff --git a/core/src/main/kotlin/net/corda/core/flows/BroadcastTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/BroadcastTransactionFlow.kt deleted file mode 100644 index 4aada740a4..0000000000 --- a/core/src/main/kotlin/net/corda/core/flows/BroadcastTransactionFlow.kt +++ /dev/null @@ -1,28 +0,0 @@ -package net.corda.core.flows - -import co.paralleluniverse.fibers.Suspendable -import net.corda.core.identity.Party -import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.NonEmptySet - -/** - * Notify the specified parties about a transaction. The remote peers will download this transaction and its - * dependency graph, verifying them all. The flow returns when all peers have acknowledged the transactions - * as valid. Normally you wouldn't use this directly, it would be called via [FinalityFlow]. - * - * @param notarisedTransaction transaction which has been notarised (if needed) and is ready to notify nodes about. - * @param participants a list of participants involved in the transaction. - * @return a list of participants who were successfully notified of the transaction. - */ -@InitiatingFlow -class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction, - val participants: NonEmptySet) : FlowLogic() { - @Suspendable - override fun call() { - // TODO: Messaging layer should handle this broadcast for us - participants.filter { !serviceHub.myInfo.isLegalIdentity(it) }.forEach { participant -> - // SendTransactionFlow allows otherParty to access our data to resolve the transaction. - subFlow(SendTransactionFlow(participant, notarisedTransaction)) - } - } -} diff --git a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt index 559b55807b..aee41e3b4b 100644 --- a/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/CollectSignaturesFlow.kt @@ -3,8 +3,6 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy -import net.corda.core.utilities.toBase58String -import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction @@ -57,21 +55,25 @@ import java.security.PublicKey * val stx = subFlow(CollectSignaturesFlow(ptx)) * * @param partiallySignedTx Transaction to collect the remaining signatures for + * @param sessionsToCollectFrom A session for every party we need to collect a signature from. Must be an exact match. * @param myOptionalKeys set of keys in the transaction which are owned by this node. This includes keys used on commands, not * just in the states. If null, the default well known identity of the node is used. */ // TODO: AbstractStateReplacementFlow needs updating to use this flow. class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: SignedTransaction, + val sessionsToCollectFrom: Collection, val myOptionalKeys: Iterable?, override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic() { - @JvmOverloads constructor(partiallySignedTx: SignedTransaction, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, null, progressTracker) + @JvmOverloads constructor(partiallySignedTx: SignedTransaction, sessionsToCollectFrom: Collection, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, sessionsToCollectFrom, null, progressTracker) companion object { object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.") object VERIFYING : ProgressTracker.Step("Verifying collected signatures.") + @JvmStatic fun tracker() = ProgressTracker(COLLECTING, VERIFYING) // TODO: Make the progress tracker adapt to the number of counter-parties to collect from. + } @Suspendable override fun call(): SignedTransaction { @@ -100,8 +102,15 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si // If the unsigned counter-parties list is empty then we don't need to collect any more signatures here. if (unsigned.isEmpty()) return partiallySignedTx + val partyToKeysMap = serviceHub.groupPublicKeysByWellKnownParty(unsigned) + // Check that we have a session for all parties. No more, no less. + require(sessionsToCollectFrom.map { it.counterparty }.toSet() == partyToKeysMap.keys) { + "The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction." + } // Collect signatures from all counter-parties and append them to the partially signed transaction. - val counterpartySignatures = keysToParties(unsigned).map { collectSignature(it.first, it.second) } + val counterpartySignatures = sessionsToCollectFrom.flatMap { session -> + subFlow(CollectSignatureFlow(partiallySignedTx, session, partyToKeysMap[session.counterparty]!!)) + } val stx = partiallySignedTx + counterpartySignatures // Verify all but the notary's signature if the transaction requires a notary, otherwise verify all signatures. @@ -110,40 +119,38 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si return stx } +} - /** - * Lookup the [Party] object for each [PublicKey] using the [ServiceHub.identityService]. - * - * @return a pair of the well known identity to contact for a signature, and the public key that party should sign - * with (this may belong to a confidential identity). - */ - @Suspendable private fun keysToParties(keys: Collection): List> = keys.map { - val party = serviceHub.identityService.partyFromAnonymous(AnonymousParty(it)) - ?: throw IllegalStateException("Party ${it.toBase58String()} not found on the network.") - Pair(party, it) - } - - // DOCSTART 1 - /** - * Get and check the required signature. - * - * @param counterparty the party to request a signature from. - * @param signingKey the key the party should use to sign the transaction. - */ - @Suspendable private fun collectSignature(counterparty: Party, signingKey: PublicKey): TransactionSignature { +// DOCSTART 1 +/** + * Get and check the required signature. + * + * @param partiallySignedTx the transaction to sign. + * @param session the [FlowSession] to connect to to get the signature. + * @param signingKeys the list of keys the party should use to sign the transaction. + */ +@Suspendable +class CollectSignatureFlow(val partiallySignedTx: SignedTransaction, val session: FlowSession, val signingKeys: List) : FlowLogic>() { + constructor(partiallySignedTx: SignedTransaction, session: FlowSession, vararg signingKeys: PublicKey) : + this(partiallySignedTx, session, listOf(*signingKeys)) + @Suspendable + override fun call(): List { // SendTransactionFlow allows counterparty to access our data to resolve the transaction. - subFlow(SendTransactionFlow(counterparty, partiallySignedTx)) + subFlow(SendTransactionFlow(session, partiallySignedTx)) // Send the key we expect the counterparty to sign with - this is important where they may have several // keys to sign with, as it makes it faster for them to identify the key to sign with, and more straight forward // for us to check we have the expected signature returned. - send(counterparty, signingKey) - return receive(counterparty).unwrap { - require(signingKey.isFulfilledBy(it.by)) { "Not signed by the required signing key." } - it + session.send(signingKeys) + return session.receive>().unwrap { signatures -> + require(signatures.size == signingKeys.size) { "Need signature for each signing key" } + signatures.forEachIndexed { index, signature -> + require(signingKeys[index].isFulfilledBy(signature.by)) { "Not signed by the required signing key." } + } + signatures } } - // DOCEND 1 } +// DOCEND 1 /** * The [SignTransactionFlow] should be called in response to the [CollectSignaturesFlow]. It automates the signing of @@ -159,15 +166,15 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si * - Subclass [SignTransactionFlow] - this can be done inside an existing flow (as shown below) * - Override the [checkTransaction] method to add some custom verification logic * - Call the flow via [FlowLogic.subFlow] - * - The flow returns the fully signed transaction once it has been committed to the ledger + * - The flow returns the transaction signed with the additional signature. * * Example - checking and signing a transaction involving a [net.corda.core.contracts.DummyContract], see * CollectSignaturesFlowTests.kt for further examples: * - * class Responder(val otherParty: Party): FlowLogic() { + * class Responder(val otherPartySession: FlowSession): FlowLogic() { * @Suspendable override fun call(): SignedTransaction { * // [SignTransactionFlow] sub-classed as a singleton object. - * val flow = object : SignTransactionFlow(otherParty) { + * val flow = object : SignTransactionFlow(otherPartySession) { * @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat { * val tx = stx.tx * val magicNumberState = tx.outputs.single().data as DummyContract.MultiOwnerState @@ -182,9 +189,9 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si * } * } * - * @param otherParty The counter-party which is providing you a transaction to sign. + * @param otherSideSession The session which is providing you a transaction to sign. */ -abstract class SignTransactionFlow(val otherParty: Party, +abstract class SignTransactionFlow(val otherSideSession: FlowSession, override val progressTracker: ProgressTracker = SignTransactionFlow.tracker()) : FlowLogic() { companion object { @@ -192,23 +199,24 @@ abstract class SignTransactionFlow(val otherParty: Party, object VERIFYING : ProgressTracker.Step("Verifying transaction proposal.") object SIGNING : ProgressTracker.Step("Signing transaction proposal.") + @JvmStatic fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING) } @Suspendable override fun call(): SignedTransaction { progressTracker.currentStep = RECEIVING // Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures. - val stx = subFlow(ReceiveTransactionFlow(otherParty, checkSufficientSignatures = false)) + val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false)) // Receive the signing key that the party requesting the signature expects us to sign with. Having this provided // means we only have to check we own that one key, rather than matching all keys in the transaction against all // keys we own. - val signingKey = receive(otherParty).unwrap { + val signingKeys = otherSideSession.receive>().unwrap { keys -> // TODO: We should have a faster way of verifying we own a single key - serviceHub.keyManagementService.filterMyKeys(listOf(it)).single() + serviceHub.keyManagementService.filterMyKeys(keys) } progressTracker.currentStep = VERIFYING // Check that the Responder actually needs to sign. - checkMySignatureRequired(stx, signingKey) + checkMySignaturesRequired(stx, signingKeys) // Check the signatures which have already been provided. Usually the Initiators and possibly an Oracle's. checkSignatures(stx) stx.tx.toLedgerTransaction(serviceHub).verify() @@ -223,18 +231,19 @@ abstract class SignTransactionFlow(val otherParty: Party, } // Sign and send back our signature to the Initiator. progressTracker.currentStep = SIGNING - val mySignature = serviceHub.createSignature(stx, signingKey) - send(otherParty, mySignature) + val mySignatures = signingKeys.map { key -> + serviceHub.createSignature(stx, key) + } + otherSideSession.send(mySignatures) - // Return the fully signed transaction once it has been committed. - return waitForLedgerCommit(stx.id) + // Return the additionally signed transaction. + return stx + mySignatures } @Suspendable private fun checkSignatures(stx: SignedTransaction) { - val signingIdentities = stx.sigs.map(TransactionSignature::by).mapNotNull(serviceHub.identityService::partyFromKey) - val signingWellKnownIdentities = signingIdentities.mapNotNull(serviceHub.identityService::partyFromAnonymous) - require(otherParty in signingWellKnownIdentities) { - "The Initiator of CollectSignaturesFlow must have signed the transaction. Found ${signingWellKnownIdentities}, expected ${otherParty}" + val signingWellKnownIdentities = serviceHub.groupPublicKeysByWellKnownParty(stx.sigs.map(TransactionSignature::by)) + require(otherSideSession.counterparty in signingWellKnownIdentities) { + "The Initiator of CollectSignaturesFlow must have signed the transaction. Found ${signingWellKnownIdentities}, expected ${otherSideSession}" } val signed = stx.sigs.map { it.by } val allSigners = stx.tx.requiredSigningKeys @@ -266,9 +275,9 @@ abstract class SignTransactionFlow(val otherParty: Party, @Throws(FlowException::class) abstract protected fun checkTransaction(stx: SignedTransaction) - @Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) { - require(signingKey in stx.tx.requiredSigningKeys) { - "Party is not a participant for any of the input states of transaction ${stx.id}" + @Suspendable private fun checkMySignaturesRequired(stx: SignedTransaction, signingKeys: Iterable) { + require(signingKeys.all { it in stx.tx.requiredSigningKeys }) { + "A signature was requested for a key that isn't part of the required signing keys for transaction ${stx.id}" } } } diff --git a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt index b1e32b4be4..ec1c6a307c 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ContractUpgradeFlow.kt @@ -2,7 +2,6 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* -import net.corda.core.identity.Party import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder @@ -87,13 +86,13 @@ object ContractUpgradeFlow { // TODO: We need a much faster way of finding our key in the transaction val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single() val stx = serviceHub.signInitialTransaction(baseTx, myKey) - return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey) + return AbstractStateReplacementFlow.UpgradeTx(stx) } } @StartableByRPC @InitiatedBy(ContractUpgradeFlow.Initiator::class) - class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor>>(otherSide) { + class Acceptor(otherSide: FlowSession) : AbstractStateReplacementFlow.Acceptor>>(otherSide) { companion object { @JvmStatic @@ -133,7 +132,7 @@ object ContractUpgradeFlow { val proposedTx = stx.tx val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction() requireThat { - "The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants) + "The instigator is one of the participants" using (initiatingSession.counterparty in oldStateAndRef.state.data.participants) "The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade) "The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx) } diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt index 847475ae75..cc5c1962d6 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt @@ -1,46 +1,35 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState import net.corda.core.crypto.isFulfilledBy -import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.internal.ResolveTransactionsFlow -import net.corda.core.node.ServiceHub import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.ProgressTracker -import net.corda.core.utilities.toNonEmptySet /** - * Verifies the given transactions, then sends them to the named notary. If the notary agrees that the transactions - * are acceptable then they are from that point onwards committed to the ledger, and will be written through to the - * vault. Additionally they will be distributed to the parties reflected in the participants list of the states. + * Verifies the given transaction, then sends it to the named notary. If the notary agrees that the transaction + * is acceptable then it is from that point onwards committed to the ledger, and will be written through to the + * vault. Additionally it will be distributed to the parties reflected in the participants list of the states. * - * The transactions will be topologically sorted before commitment to ensure that dependencies are committed before - * dependers, so you don't need to do this yourself. + * The transaction is expected to have already been resolved: if its dependencies are not available in local + * storage, verification will fail. It must have signatures from all necessary parties other than the notary. * - * The transactions are expected to have already been resolved: if their dependencies are not available in local - * storage or within the given set, verification will fail. They must have signatures from all necessary parties - * other than the notary. + * If specified, the extra recipients are sent the given transaction. The base set of parties to inform are calculated + * from the contract-given set of participants. * - * If specified, the extra recipients are sent all the given transactions. The base set of parties to inform of each - * transaction are calculated on a per transaction basis from the contract-given set of participants. + * The flow returns the same transaction but with the additional signatures from the notary. * - * The flow returns the same transactions, in the same order, with the additional signatures. - * - * @param transactions What to commit. + * @param transaction What to commit. * @param extraRecipients A list of additional participants to inform of the transaction. */ -open class FinalityFlow(val transactions: Iterable, +@InitiatingFlow +class FinalityFlow(val transaction: SignedTransaction, private val extraRecipients: Set, - override val progressTracker: ProgressTracker) : FlowLogic>() { - constructor(transaction: SignedTransaction, extraParticipants: Set) : this(listOf(transaction), extraParticipants, tracker()) - constructor(transaction: SignedTransaction) : this(listOf(transaction), emptySet(), tracker()) - constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(listOf(transaction), emptySet(), progressTracker) + override val progressTracker: ProgressTracker) : FlowLogic() { + constructor(transaction: SignedTransaction, extraParticipants: Set) : this(transaction, extraParticipants, tracker()) + constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker()) + constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker) companion object { object NOTARISING : ProgressTracker.Step("Requesting signature by notary service") { @@ -49,52 +38,41 @@ open class FinalityFlow(val transactions: Iterable, object BROADCASTING : ProgressTracker.Step("Broadcasting transaction to participants") - // TODO: Make all tracker() methods @JvmStatic + @JvmStatic fun tracker() = ProgressTracker(NOTARISING, BROADCASTING) } @Suspendable @Throws(NotaryException::class) - override fun call(): List { + override fun call(): SignedTransaction { // Note: this method is carefully broken up to minimize the amount of data reachable from the stack at // the point where subFlow is invoked, as that minimizes the checkpointing work to be done. // // Lookup the resolved transactions and use them to map each signed transaction to the list of participants. // Then send to the notary if needed, record locally and distribute. + val parties = getPartiesToSend(verifyTx()) progressTracker.currentStep = NOTARISING - val notarisedTxns: List>> = resolveDependenciesOf(transactions) - .map { (stx, ltx) -> Pair(notariseAndRecord(stx), lookupParties(ltx)) } + val notarised = notariseAndRecord() // Each transaction has its own set of recipients, but extra recipients get them all. progressTracker.currentStep = BROADCASTING - for ((stx, parties) in notarisedTxns) { - val participants = (parties + extraRecipients).filter { !serviceHub.myInfo.isLegalIdentity(it) }.toSet() - if (participants.isNotEmpty()) { - broadcastTransaction(stx, participants.toNonEmptySet()) + for (party in parties) { + if (!serviceHub.myInfo.isLegalIdentity(party)) { + val session = initiateFlow(party) + subFlow(SendTransactionFlow(session, notarised)) } } - return notarisedTxns.map { it.first } - } - /** - * Broadcast a transaction to the participants. By default calls [BroadcastTransactionFlow], however can be - * overridden for more complex transaction delivery protocols (for example where not all parties know each other). - * - * @param participants the participants to send the transaction to. This is expected to include extra participants - * and exclude the local node. - */ - @Suspendable - open protected fun broadcastTransaction(stx: SignedTransaction, participants: NonEmptySet) { - subFlow(BroadcastTransactionFlow(stx, participants)) + return notarised } @Suspendable - private fun notariseAndRecord(stx: SignedTransaction): SignedTransaction { - val notarised = if (needsNotarySignature(stx)) { - val notarySignatures = subFlow(NotaryFlow.Client(stx)) - stx + notarySignatures + private fun notariseAndRecord(): SignedTransaction { + val notarised = if (needsNotarySignature(transaction)) { + val notarySignatures = subFlow(NotaryFlow.Client(transaction)) + transaction + notarySignatures } else { - stx + transaction } serviceHub.recordTransactions(notarised) return notarised @@ -112,47 +90,17 @@ open class FinalityFlow(val transactions: Iterable, return !(notaryKey?.isFulfilledBy(signers) ?: false) } - /** - * Resolve the parties involved in a transaction. - * - * The default implementation throws an exception if an unknown party is encountered. - */ - open protected fun lookupParties(ltx: LedgerTransaction): Set { - // Calculate who is meant to see the results based on the participants involved. - return extractParticipants(ltx).map { - serviceHub.identityService.partyFromAnonymous(it) - ?: throw IllegalArgumentException("Could not resolve well known identity of participant $it") - }.toSet() + private fun getPartiesToSend(ltx: LedgerTransaction): Set { + val participants = ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants } + return serviceHub.groupAbstractPartyByWellKnownParty(participants).keys + extraRecipients } - /** - * Helper function to extract all participants from a ledger transaction. Intended to help implement [lookupParties] - * overriding functions. - */ - protected fun extractParticipants(ltx: LedgerTransaction): List { - return ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants } - } - - private fun resolveDependenciesOf(signedTransactions: Iterable): List> { - // Make sure the dependencies come before the dependers. - val sorted = ResolveTransactionsFlow.topologicalSort(signedTransactions.toList()) - // Build a ServiceHub that consults the argument list as well as what's in local tx storage so uncommitted - // transactions can depend on each other. - val augmentedLookup = object : ServiceHub by serviceHub { - val hashToTx = sorted.associateBy { it.id } - override fun loadState(stateRef: StateRef): TransactionState<*> { - val provided: TransactionState? = hashToTx[stateRef.txhash]?.let { it.tx.outputs[stateRef.index] } - return provided ?: super.loadState(stateRef) - } - } - // Load and verify each transaction. - return sorted.map { stx -> - val notary = stx.tx.notary - // The notary signature(s) are allowed to be missing but no others. - if (notary != null) stx.verifySignaturesExcept(notary.owningKey) else stx.verifyRequiredSignatures() - val ltx = stx.toLedgerTransaction(augmentedLookup, false) - ltx.verify() - stx to ltx - } + private fun verifyTx(): LedgerTransaction { + val notary = transaction.tx.notary + // The notary signature(s) are allowed to be missing but no others. + if (notary != null) transaction.verifySignaturesExcept(notary.owningKey) else transaction.verifyRequiredSignatures() + val ltx = transaction.toLedgerTransaction(serviceHub, false) + ltx.verify() + return ltx } } diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowException.kt b/core/src/main/kotlin/net/corda/core/flows/FlowException.kt index 4e20320883..33251020f8 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowException.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowException.kt @@ -7,7 +7,7 @@ import net.corda.core.CordaRuntimeException /** * Exception which can be thrown by a [FlowLogic] at any point in its logic to unexpectedly bring it to a permanent end. * The exception will propagate to all counterparty flows and will be thrown on their end the next time they wait on a - * [FlowLogic.receive] or [FlowLogic.sendAndReceive]. Any flow which no longer needs to do a receive, or has already ended, + * [FlowSession.receive] or [FlowSession.sendAndReceive]. Any flow which no longer needs to do a receive, or has already ended, * will not receive the exception (if this is required then have them wait for a confirmation message). * * [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service. diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 91269be2d3..2137b5ee03 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -137,11 +137,17 @@ abstract class FlowLogic { internal inline fun sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData { return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true) } + @Suspendable internal fun FlowSession.sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true) } + @Suspendable + internal inline fun FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData { + return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true) + } + /** * Suspends until the specified [otherParty] sends us a message of type [R]. * diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt index e3423bd302..9c2e5425d6 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt @@ -23,12 +23,12 @@ import net.corda.core.utilities.UntrustworthyData * * If it's an InitiatedBy flow: * - * Change the constructor to take an initiatingSession: FlowSession instead of a counterparty: Party + * Change the constructor to take an otherSideSession: FlowSession instead of a counterparty: Party * Then look for usages of the deprecated functions and change them to use the FlowSession * For example: * send(counterparty, something) * will become - * initiatingSession.send(something) + * otherSideSession.send(something) */ abstract class FlowSession { abstract val counterparty: Party diff --git a/core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt deleted file mode 100644 index 16c792266c..0000000000 --- a/core/src/main/kotlin/net/corda/core/flows/ManualFinalityFlow.kt +++ /dev/null @@ -1,20 +0,0 @@ -package net.corda.core.flows - -import net.corda.core.identity.Party -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.ProgressTracker - -/** - * Alternative finality flow which only does not attempt to take participants from the transaction, but instead all - * participating parties must be provided manually. - * - * @param transactions What to commit. - * @param recipients List of participants to inform of the transaction. - */ -class ManualFinalityFlow(transactions: Iterable, - recipients: Set, - progressTracker: ProgressTracker) : FinalityFlow(transactions, recipients, progressTracker) { - constructor(transaction: SignedTransaction, extraParticipants: Set) : this(listOf(transaction), extraParticipants, tracker()) - override fun lookupParties(ltx: LedgerTransaction): Set = emptySet() -} diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt index a861ad8f84..26525204aa 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt @@ -43,7 +43,7 @@ class NotaryChangeFlow( val mySignature = serviceHub.keyManagementService.sign(signableData, myKey) val stx = SignedTransaction(tx, listOf(mySignature)) - return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey) + return AbstractStateReplacementFlow.UpgradeTx(stx) } /** Resolves the encumbrance state chain for the given [state] */ diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index e2a2ba1f62..f738df62be 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -65,16 +65,17 @@ class NotaryFlow { } val response = try { + val session = initiateFlow(notaryParty) if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) { - subFlow(SendTransactionWithRetry(notaryParty, stx)) - receive>(notaryParty) + subFlow(SendTransactionWithRetry(session, stx)) + session.receive>() } else { val tx: Any = if (stx.isNotaryChangeTransaction()) { stx.notaryChangeTx } else { stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty }) } - sendAndReceiveWithRetry(notaryParty, tx) + session.sendAndReceiveWithRetry(tx) } } catch (e: NotaryException) { if (e.error is NotaryError.Conflict) { @@ -99,10 +100,10 @@ class NotaryFlow { * The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry] * instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only. */ - private class SendTransactionWithRetry(otherSide: Party, stx: SignedTransaction) : SendTransactionFlow(otherSide, stx) { + private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) { @Suspendable - override fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any): UntrustworthyData { - return sendAndReceiveWithRetry(otherSide, payload) + override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData { + return otherSideSession.sendAndReceiveWithRetry(payload) } } @@ -115,14 +116,14 @@ class NotaryFlow { * Additional transaction validation logic can be added when implementing [receiveAndVerifyTx]. */ // See AbstractStateReplacementFlow.Acceptor for why it's Void? - abstract class Service(val otherSide: Party, val service: TrustedAuthorityNotaryService) : FlowLogic() { + abstract class Service(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic() { @Suspendable override fun call(): Void? { val (id, inputs, timeWindow, notary) = receiveAndVerifyTx() checkNotary(notary) service.validateTimeWindow(timeWindow) - service.commitInputStates(inputs, id, otherSide) + service.commitInputStates(inputs, id, otherSideSession.counterparty) signAndSendResponse(id) return null } @@ -144,7 +145,7 @@ class NotaryFlow { @Suspendable private fun signAndSendResponse(txId: SecureHash) { val signature = service.sign(txId) - send(otherSide, listOf(signature)) + otherSideSession.send(listOf(signature)) } } } diff --git a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt index 48504034d7..35f17c64ea 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt @@ -2,7 +2,6 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* -import net.corda.core.identity.Party import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap @@ -11,18 +10,26 @@ import java.security.SignatureException /** * The [ReceiveTransactionFlow] should be called in response to the [SendTransactionFlow]. * - * This flow is a combination of [receive], resolve and [SignedTransaction.verify]. This flow will receive the [SignedTransaction] - * and perform the resolution back-and-forth required to check the dependencies and download any missing attachments. - * The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify]. + * This flow is a combination of [FlowSession.receive], resolve and [SignedTransaction.verify]. This flow will receive the + * [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing + * attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify]. + * + * @param otherSideSession session to the other side which is calling [SendTransactionFlow]. + * @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify]. */ -class ReceiveTransactionFlow -@JvmOverloads -constructor(private val otherParty: Party, private val checkSufficientSignatures: Boolean = true) : FlowLogic() { +class ReceiveTransactionFlow(private val otherSideSession: FlowSession, + private val checkSufficientSignatures: Boolean) : FlowLogic() { + /** Receives a [SignedTransaction] from [otherSideSession], verifies it and then records it in the vault. */ + constructor(otherSideSession: FlowSession) : this(otherSideSession, true) + @Suspendable - @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) + @Throws(SignatureException::class, + AttachmentResolutionException::class, + TransactionResolutionException::class, + TransactionVerificationException::class) override fun call(): SignedTransaction { - return receive(otherParty).unwrap { - subFlow(ResolveTransactionsFlow(it, otherParty)) + return otherSideSession.receive().unwrap { + subFlow(ResolveTransactionsFlow(it, otherSideSession)) it.verify(serviceHub, checkSufficientSignatures) it } @@ -32,16 +39,16 @@ constructor(private val otherParty: Party, private val checkSufficientSignatures /** * The [ReceiveStateAndRefFlow] should be called in response to the [SendStateAndRefFlow]. * - * This flow is a combination of [receive] and resolve. This flow will receive a list of [StateAndRef] + * This flow is a combination of [FlowSession.receive] and resolve. This flow will receive a list of [StateAndRef] * and perform the resolution back-and-forth required to check the dependencies. * The flow will return the list of [StateAndRef] after it is resolved. */ // @JvmSuppressWildcards is used to suppress wildcards in return type when calling `subFlow(new ReceiveStateAndRef(otherParty))` in java. -class ReceiveStateAndRefFlow(private val otherParty: Party) : FlowLogic<@JvmSuppressWildcards List>>() { +class ReceiveStateAndRefFlow(private val otherSideSession: FlowSession) : FlowLogic<@JvmSuppressWildcards List>>() { @Suspendable override fun call(): List> { - return receive>>(otherParty).unwrap { - subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherParty)) + return otherSideSession.receive>>().unwrap { + subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherSideSession)) it } } diff --git a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt index 12cc8592d0..9352d3e178 100644 --- a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt @@ -2,7 +2,6 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.StateAndRef -import net.corda.core.identity.Party import net.corda.core.internal.FetchDataFlow import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap @@ -14,9 +13,9 @@ import net.corda.core.utilities.unwrap * to check the dependencies and download any missing attachments. * * @param otherSide the target party. - * @param stx the [SignedTransaction] being sent to the [otherSide]. + * @param stx the [SignedTransaction] being sent to the [otherSideSession]. */ -open class SendTransactionFlow(otherSide: Party, stx: SignedTransaction) : DataVendingFlow(otherSide, stx) +open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : DataVendingFlow(otherSide, stx) /** * The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify @@ -24,14 +23,14 @@ open class SendTransactionFlow(otherSide: Party, stx: SignedTransaction) : DataV * at the right point in the conversation to receive the input state and ref and perform the resolution back-and-forth * required to check the dependencies. * - * @param otherSide the target party. - * @param stateAndRefs the list of [StateAndRef] being sent to the [otherSide]. + * @param otherSideSession the target session. + * @param stateAndRefs the list of [StateAndRef] being sent to the [otherSideSession]. */ -open class SendStateAndRefFlow(otherSide: Party, stateAndRefs: List>) : DataVendingFlow(otherSide, stateAndRefs) +open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List>) : DataVendingFlow(otherSideSession, stateAndRefs) -sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic() { +sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic() { @Suspendable - protected open fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any) = sendAndReceive(otherSide, payload) + protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive(payload) @Suspendable protected open fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) { @@ -42,11 +41,11 @@ sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic override fun call(): Void? { // The first payload will be the transaction data, subsequent payload will be the transaction/attachment data. var payload = payload - // This loop will receive [FetchDataFlow.Request] continuously until the `otherSide` has all the data they need - // to resolve the transaction, a [FetchDataFlow.EndRequest] will be sent from the `otherSide` to indicate end of + // This loop will receive [FetchDataFlow.Request] continuously until the `otherSideSession` has all the data they need + // to resolve the transaction, a [FetchDataFlow.EndRequest] will be sent from the `otherSideSession` to indicate end of // data request. while (true) { - val dataRequest = sendPayloadAndReceiveDataRequest(otherSide, payload).unwrap { request -> + val dataRequest = sendPayloadAndReceiveDataRequest(otherSideSession, payload).unwrap { request -> when (request) { is FetchDataFlow.Request.Data -> { // Security TODO: Check for abnormally large or malformed data requests diff --git a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt index ce95ee9838..0789c535ba 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt @@ -7,7 +7,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party +import net.corda.core.flows.FlowSession import net.corda.core.internal.FetchDataFlow.DownloadedVsRequestedDataMismatch import net.corda.core.internal.FetchDataFlow.HashNotFound import net.corda.core.serialization.CordaSerializable @@ -38,7 +38,7 @@ import java.util.* */ sealed class FetchDataFlow( protected val requests: Set, - protected val otherSide: Party, + protected val otherSideSession: FlowSession, protected val dataType: DataType) : FlowLogic>() { @CordaSerializable @@ -72,7 +72,7 @@ sealed class FetchDataFlow( return if (toFetch.isEmpty()) { Result(fromDisk, emptyList()) } else { - logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSide.name}") + logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}") // TODO: Support "large message" response streaming so response sizes are not limited by RAM. // We can then switch to requesting items in large batches to minimise the latency penalty. @@ -85,11 +85,11 @@ sealed class FetchDataFlow( for (hash in toFetch) { // We skip the validation here (with unwrap { it }) because we will do it below in validateFetchResponse. // The only thing checked is the object type. It is a protocol violation to send results out of order. - maybeItems += sendAndReceive>(otherSide, Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it } + maybeItems += otherSideSession.sendAndReceive>(Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it } } // Check for a buggy/malicious peer answering with something that we didn't ask for. val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch) - logger.info("Fetched ${downloaded.size} elements from ${otherSide.name}") + logger.info("Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}") maybeWriteToDisk(downloaded) Result(fromDisk, downloaded) } @@ -140,7 +140,7 @@ sealed class FetchDataFlow( * attachments are saved to local storage automatically. */ class FetchAttachmentsFlow(requests: Set, - otherSide: Party) : FetchDataFlow(requests, otherSide, DataType.ATTACHMENT) { + otherSide: FlowSession) : FetchDataFlow(requests, otherSide, DataType.ATTACHMENT) { override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid) @@ -171,7 +171,7 @@ class FetchAttachmentsFlow(requests: Set, * results in a [FetchDataFlow.HashNotFound] exception. Note that returned transactions are not inserted into * the database, because it's up to the caller to actually verify the transactions are valid. */ -class FetchTransactionsFlow(requests: Set, otherSide: Party) : +class FetchTransactionsFlow(requests: Set, otherSide: FlowSession) : FetchDataFlow(requests, otherSide, DataType.TRANSACTION) { override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index d9761906fe..cbb80eb2ae 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -272,3 +272,5 @@ annotation class VisibleForTesting @Suppress("UNCHECKED_CAST") fun uncheckedCast(obj: T) = obj as U + +fun Iterable>.toMultiMap(): Map> = this.groupBy({ it.first }) { it.second } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt index e776b0efce..f297fc7c5b 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt @@ -3,7 +3,7 @@ package net.corda.core.internal import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party +import net.corda.core.flows.FlowSession import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.exactAdd @@ -17,14 +17,14 @@ import java.util.* * @return a list of verified [SignedTransaction] objects, in a depth-first order. */ class ResolveTransactionsFlow(private val txHashes: Set, - private val otherSide: Party) : FlowLogic>() { + private val otherSide: FlowSession) : FlowLogic>() { /** * Resolves and validates the dependencies of the specified [signedTransaction]. Fetches the attachments, but does * *not* validate or store the [signedTransaction] itself. * * @return a list of verified [SignedTransaction] objects, in a depth-first order. */ - constructor(signedTransaction: SignedTransaction, otherSide: Party) : this(dependencyIDs(signedTransaction), otherSide) { + constructor(signedTransaction: SignedTransaction, otherSide: FlowSession) : this(dependencyIDs(signedTransaction), otherSide) { this.signedTransaction = signedTransaction } companion object { @@ -82,7 +82,7 @@ class ResolveTransactionsFlow(private val txHashes: Set, // Start fetching data. val newTxns = downloadDependencies(txHashes) fetchMissingAttachments(signedTransaction?.let { newTxns + it } ?: newTxns) - send(otherSide, FetchDataFlow.Request.End) + otherSide.send(FetchDataFlow.Request.End) // Finish fetching data. val result = topologicalSort(newTxns) diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 725e7ce44a..7860c687e0 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -5,8 +5,10 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party -import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.toMultiMap import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.transactions.FilteredTransaction @@ -269,4 +271,75 @@ interface ServiceHub : ServicesForResolution { * @return A new [Connection] */ fun jdbcSession(): Connection + + /** + * Group each [PublicKey] by the well known party using the [ServiceHub.identityService], in preparation for + * creating [FlowSession]s, for example. + * + * @param publicKeys the [PublicKey]s to group. + * @param ignoreUnrecognisedParties if this is false, throw an exception if some of the [PublicKey]s cannot be mapped + * to a [Party]. + * @return a map of well known [Party] to associated [PublicKey]s. + */ + @Throws(IllegalArgumentException::class) + fun groupPublicKeysByWellKnownParty(publicKeys: Collection, ignoreUnrecognisedParties: Boolean): Map> = + groupAbstractPartyByWellKnownParty(publicKeys.map { AnonymousParty(it) }, ignoreUnrecognisedParties).mapValues { it.value.map { it.owningKey } } + + /** + * Group each [PublicKey] by the well known party using the [ServiceHub.identityService], in preparation for + * creating [FlowSession]s, for example. Throw an exception if some of the [PublicKey]s cannot be mapped + * to a [Party]. + * + * @param publicKeys the [PublicKey]s to group. + * @return a map of well known [Party] to associated [PublicKey]s. + */ + // Cannot use @JvmOverloads in interface + @Throws(IllegalArgumentException::class) + fun groupPublicKeysByWellKnownParty(publicKeys: Collection): Map> = groupPublicKeysByWellKnownParty(publicKeys, false) + + /** + * Group each [AbstractParty] by the well known party using the [ServiceHub.identityService], in preparation for + * creating [FlowSession]s, for example. + * + * @param parties the [AbstractParty]s to group. + * @param ignoreUnrecognisedParties if this is false, throw an exception if some of the [AbstractParty]s cannot be mapped + * to a [Party]. + * @return a map of well known [Party] to associated [AbstractParty]s. + */ + @Throws(IllegalArgumentException::class) + fun groupAbstractPartyByWellKnownParty(parties: Collection, ignoreUnrecognisedParties: Boolean): Map> { + val partyToPublicKey: Iterable> = parties.mapNotNull { + (identityService.partyFromAnonymous(it) ?: if (ignoreUnrecognisedParties) return@mapNotNull null else throw IllegalArgumentException("Could not find Party for $it")) to it + } + return partyToPublicKey.toMultiMap() + } + + /** + * Group each [AbstractParty] by the well known party using the [ServiceHub.identityService], in preparation for + * creating [FlowSession]s, for example. Throw an exception if some of the [AbstractParty]s cannot be mapped + * to a [Party]. + * + * @param parties the [AbstractParty]s to group. + * @return a map of well known [Party] to associated [AbstractParty]s. + */ + // Cannot use @JvmOverloads in interface + @Throws(IllegalArgumentException::class) + fun groupAbstractPartyByWellKnownParty(parties: Collection): Map> { + return groupAbstractPartyByWellKnownParty(parties, false) + } + + /** + * Remove this node from a map of well known [Party]s. + * + * @return a new copy of the map, with the well known [Party] for this node removed. + */ + fun excludeMe(map: Map): Map = map.filterKeys { !myInfo.isLegalIdentity(it) } + + /** + * Remove the [Party] associated with the notary of a [SignedTransaction] from the a map of [Party]s. It is a no-op + * if the notary is null. + * + * @return a new copy of the map, with the well known [Party] for the notary removed. + */ + fun excludeNotary(map: Map, stx: SignedTransaction): Map = map.filterKeys { it != stx.notary } } diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index c5a4b6449e..71e24c506d 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -3,10 +3,7 @@ package net.corda.core.node.services import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.* -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.NotaryError -import net.corda.core.flows.NotaryException -import net.corda.core.flows.NotaryFlow +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken @@ -24,9 +21,9 @@ abstract class NotaryService : SingletonSerializeAsToken() { /** * Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client]. - * @param otherParty client [Party] making the request + * @param otherPartySession client [Party] making the request */ - abstract fun createServiceFlow(otherParty: Party): FlowLogic + abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt index 5bc5f3ce0c..ab25d68064 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -42,7 +42,7 @@ interface TransactionWithSignatures : NamedByHash { fun verifySignaturesExcept(vararg allowedToBeMissing: PublicKey) { checkSignaturesAreValid() - val needed = getMissingSignatures() - allowedToBeMissing + val needed = getMissingSigners() - allowedToBeMissing if (needed.isNotEmpty()) throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id) } @@ -71,7 +71,10 @@ interface TransactionWithSignatures : NamedByHash { */ fun getKeyDescriptions(keys: Set): List - private fun getMissingSignatures(): Set { + /** + * Return the [PublicKey]s for which we still need signatures. + */ + fun getMissingSigners(): Set { val sigKeys = sigs.map { it.by }.toSet() // TODO Problem is that we can get single PublicKey wrapped as CompositeKey in allowedToBeMissing/mustSign // equals on CompositeKey won't catch this case (do we want to single PublicKey be equal to the same key wrapped in CompositeKey with threshold 1?) diff --git a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index 406cbc30ec..01855fb5e3 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -80,8 +80,9 @@ public class FlowsInJavaTest { @Suspendable @Override public String call() throws FlowException { - return receive(String.class, otherParty).unwrap(data -> { - send(otherParty, "Something"); + FlowSession session = initiateFlow(otherParty); + return session.receive(String.class).unwrap(data -> { + session.send("Something"); return data; }); } @@ -89,16 +90,16 @@ public class FlowsInJavaTest { @InitiatedBy(SendInUnwrapFlow.class) private static class SendHelloAndThenReceive extends FlowLogic { - private final Party otherParty; + private final FlowSession otherSide; - private SendHelloAndThenReceive(Party otherParty) { - this.otherParty = otherParty; + private SendHelloAndThenReceive(FlowSession otherParty) { + this.otherSide = otherParty; } @Suspendable @Override public String call() throws FlowException { - return sendAndReceive(String.class, otherParty, "Hello").unwrap(data -> data); + return otherSide.sendAndReceive(String.class, "Hello").unwrap(data -> data); } } @@ -115,7 +116,8 @@ public class FlowsInJavaTest { @Suspendable @Override public Void call() throws FlowException { - receive(Primitives.unwrap(receiveType), otherParty); + FlowSession session = initiateFlow(otherParty); + session.receive(Primitives.unwrap(receiveType)); return null; } } diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index e1511eaa77..735798118c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -162,12 +162,15 @@ class AttachmentTests { @InitiatingFlow private class InitiatingFetchAttachmentsFlow(val otherSide: Party, val hashes: Set) : FlowLogic>() { @Suspendable - override fun call(): FetchDataFlow.Result = subFlow(FetchAttachmentsFlow(hashes, otherSide)) + override fun call(): FetchDataFlow.Result { + val session = initiateFlow(otherSide) + return subFlow(FetchAttachmentsFlow(hashes, session)) + } } @InitiatedBy(InitiatingFetchAttachmentsFlow::class) - private class FetchAttachmentsResponse(val otherSide: Party) : FlowLogic() { + private class FetchAttachmentsResponse(val otherSideSession: FlowSession) : FlowLogic() { @Suspendable - override fun call() = subFlow(TestDataVendingFlow(otherSide)) + override fun call() = subFlow(TestDataVendingFlow(otherSideSession)) } } diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 540a8a3546..34a7bf5887 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Command import net.corda.core.contracts.StateAndContract import net.corda.core.contracts.requireThat -import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder @@ -31,7 +30,6 @@ class CollectSignaturesFlowTests { lateinit var b: StartedNode lateinit var c: StartedNode lateinit var notary: Party - val services = MockServices() @Before fun setup() { @@ -61,12 +59,13 @@ class CollectSignaturesFlowTests { // "collectSignaturesFlow" and "SignTransactionFlow" can be used in practise. object TestFlow { @InitiatingFlow - class Initiator(val state: DummyContract.MultiOwnerState, val otherParty: Party) : FlowLogic() { + class Initiator(private val state: DummyContract.MultiOwnerState, private val otherParty: Party) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - send(otherParty, state) + val session = initiateFlow(otherParty) + session.send(state) - val flow = object : SignTransactionFlow(otherParty) { + val flow = object : SignTransactionFlow(session) { @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat { val tx = stx.tx val ltx = tx.toLedgerTransaction(serviceHub) @@ -83,19 +82,19 @@ class CollectSignaturesFlowTests { } @InitiatedBy(TestFlow.Initiator::class) - class Responder(val otherParty: Party, val identities: Map) : FlowLogic() { + class Responder(private val initiatingSession: FlowSession) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - val state = receive(otherParty).unwrap { it } + val state = initiatingSession.receive().unwrap { it } val notary = serviceHub.getDefaultNotary() val myInputKeys = state.participants.map { it.owningKey } - val myKeys = myInputKeys + (identities[ourIdentity] ?: ourIdentity).owningKey val command = Command(DummyContract.Commands.Create(), myInputKeys) val builder = TransactionBuilder(notary).withItems(StateAndContract(state, DUMMY_PROGRAM_ID), command) val ptx = serviceHub.signInitialTransaction(builder) - val stx = subFlow(CollectSignaturesFlow(ptx, myKeys)) - return subFlow(FinalityFlow(stx)).single() + val signature = subFlow(CollectSignatureFlow(ptx, initiatingSession, initiatingSession.counterparty.owningKey)) + val stx = ptx + signature + return subFlow(FinalityFlow(stx)) } } } @@ -105,7 +104,7 @@ class CollectSignaturesFlowTests { // receiving off the wire. object TestFlowTwo { @InitiatingFlow - class Initiator(val state: DummyContract.MultiOwnerState) : FlowLogic() { + class Initiator(private val state: DummyContract.MultiOwnerState) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { val notary = serviceHub.getDefaultNotary() @@ -113,15 +112,16 @@ class CollectSignaturesFlowTests { val command = Command(DummyContract.Commands.Create(), myInputKeys) val builder = TransactionBuilder(notary).withItems(StateAndContract(state, DUMMY_PROGRAM_ID), command) val ptx = serviceHub.signInitialTransaction(builder) - val stx = subFlow(CollectSignaturesFlow(ptx, myInputKeys)) - return subFlow(FinalityFlow(stx)).single() + val sessions = serviceHub.excludeMe(serviceHub.groupAbstractPartyByWellKnownParty(state.owners)).map { initiateFlow(it.key) } + val stx = subFlow(CollectSignaturesFlow(ptx, sessions, myInputKeys)) + return subFlow(FinalityFlow(stx)) } } @InitiatedBy(TestFlowTwo.Initiator::class) - class Responder(val otherParty: Party) : FlowLogic() { - @Suspendable override fun call(): SignedTransaction { - val flow = object : SignTransactionFlow(otherParty) { + class Responder(private val otherSideSession: FlowSession) : FlowLogic() { + @Suspendable override fun call() { + val signFlow = object : SignTransactionFlow(otherSideSession) { @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat { val tx = stx.tx val ltx = tx.toLedgerTransaction(serviceHub) @@ -132,9 +132,8 @@ class CollectSignaturesFlowTests { } } - val stx = subFlow(flow) - - return waitForLedgerCommit(stx.id) + val stx = subFlow(signFlow) + waitForLedgerCommit(stx.id) } } } @@ -164,7 +163,7 @@ class CollectSignaturesFlowTests { fun `no need to collect any signatures`() { val onePartyDummyContract = DummyContract.generateInitial(1337, notary, a.info.chooseIdentity().ref(1)) val ptx = a.services.signInitialTransaction(onePartyDummyContract) - val flow = a.services.startFlow(CollectSignaturesFlow(ptx)) + val flow = a.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) mockNet.runNetwork() val result = flow.resultFuture.getOrThrow() result.verifyRequiredSignatures() @@ -177,7 +176,7 @@ class CollectSignaturesFlowTests { val onePartyDummyContract = DummyContract.generateInitial(1337, notary, a.info.chooseIdentity().ref(1)) val miniCorpServices = MockServices(MINI_CORP_KEY) val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract) - val flow = a.services.startFlow(CollectSignaturesFlow(ptx)) + val flow = a.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) mockNet.runNetwork() assertFailsWith("The Initiator of CollectSignaturesFlow must have signed the transaction.") { flow.resultFuture.getOrThrow() @@ -192,7 +191,7 @@ class CollectSignaturesFlowTests { b.info.chooseIdentity().ref(3)) val signedByA = a.services.signInitialTransaction(twoPartyDummyContract) val signedByBoth = b.services.addSignature(signedByA) - val flow = a.services.startFlow(CollectSignaturesFlow(signedByBoth)) + val flow = a.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet())) mockNet.runNetwork() val result = flow.resultFuture.getOrThrow() println(result.tx) diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 69a2d06d79..edc9dd0df0 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -21,15 +21,10 @@ import net.corda.node.internal.CordaRPCOpsImpl import net.corda.node.internal.StartedNode import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.nodeapi.User -import net.corda.testing.RPCDriverExposedDSLInterface -import net.corda.testing.chooseIdentity +import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContractV2 -import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork -import net.corda.testing.rpcDriver -import net.corda.testing.rpcTestUser -import net.corda.testing.startRpcClient import org.junit.After import org.junit.Before import org.junit.Test @@ -63,7 +58,6 @@ class ContractUpgradeFlowTest { b.database.transaction { b.services.identityService.verifyAndRegisterIdentity(nodeIdentity) } - } @After @@ -78,7 +72,7 @@ class ContractUpgradeFlowTest { val signedByA = a.services.signInitialTransaction(twoPartyDummyContract) val stx = b.services.addSignature(signedByA) - a.services.startFlow(FinalityFlow(stx, setOf(a.info.chooseIdentity(), b.info.chooseIdentity()))) + a.services.startFlow(FinalityFlow(stx, setOf(b.info.chooseIdentity()))) mockNet.runNetwork() val atx = a.database.transaction { a.services.validatedTransactions.getTransaction(stx.id) } @@ -157,7 +151,7 @@ class ContractUpgradeFlowTest { )) val rpcA = startProxy(a, user) val rpcB = startProxy(b, user) - val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(a.info.chooseIdentity(), b.info.chooseIdentity())) + val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(b.info.chooseIdentity())) mockNet.runNetwork() handle.returnValue.getOrThrow() @@ -257,9 +251,9 @@ class ContractUpgradeFlowTest { } @StartableByRPC - class FinalityInvoker(val transaction: SignedTransaction, - val extraRecipients: Set) : FlowLogic>() { + class FinalityInvoker(private val transaction: SignedTransaction, + private val extraRecipients: Set) : FlowLogic() { @Suspendable - override fun call(): List = subFlow(FinalityFlow(transaction, extraRecipients)) + override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients)) } } diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 48e7df3537..929a65fb90 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -1,14 +1,13 @@ package net.corda.core.flows -import net.corda.core.contracts.Amount -import net.corda.core.contracts.Issued import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow -import net.corda.finance.GBP +import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash -import net.corda.testing.ALICE +import net.corda.finance.issuedBy import net.corda.node.internal.StartedNode +import net.corda.testing.ALICE import net.corda.testing.chooseIdentity import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork @@ -44,14 +43,13 @@ class FinalityFlowTests { @Test fun `finalise a simple transaction`() { - val amount = Amount(1000, Issued(nodeA.info.chooseIdentity().ref(0), GBP)) + val amount = 1000.POUNDS.issuedBy(nodeA.info.chooseIdentity().ref(0)) val builder = TransactionBuilder(notary) Cash().generateIssue(builder, amount, nodeB.info.chooseIdentity(), notary) val stx = nodeA.services.signInitialTransaction(builder) val flow = nodeA.services.startFlow(FinalityFlow(stx)) mockNet.runNetwork() - val result = flow.resultFuture.getOrThrow() - val notarisedTx = result.single() + val notarisedTx = flow.resultFuture.getOrThrow() notarisedTx.verifyRequiredSignatures() val transactionSeenByB = nodeB.services.database.transaction { nodeB.services.validatedTransactions.getTransaction(notarisedTx.id) @@ -61,7 +59,7 @@ class FinalityFlowTests { @Test fun `reject a transaction with unknown parties`() { - val amount = Amount(1000, Issued(nodeA.info.chooseIdentity().ref(0), GBP)) + val amount = 1000.POUNDS.issuedBy(nodeA.info.chooseIdentity().ref(0)) val fakeIdentity = ALICE // Alice isn't part of this network, so node A won't recognise them val builder = TransactionBuilder(notary) Cash().generateIssue(builder, amount, fakeIdentity, notary) diff --git a/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt deleted file mode 100644 index 88505ebbfc..0000000000 --- a/core/src/test/kotlin/net/corda/core/flows/ManualFinalityFlowTests.kt +++ /dev/null @@ -1,67 +0,0 @@ -package net.corda.core.flows - -import net.corda.core.contracts.Amount -import net.corda.core.contracts.Issued -import net.corda.core.identity.Party -import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.getOrThrow -import net.corda.finance.GBP -import net.corda.finance.contracts.asset.Cash -import net.corda.node.internal.StartedNode -import net.corda.testing.chooseIdentity -import net.corda.testing.getDefaultNotary -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockServices -import org.junit.After -import org.junit.Before -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class ManualFinalityFlowTests { - lateinit var mockNet: MockNetwork - lateinit var nodeA: StartedNode - lateinit var nodeB: StartedNode - lateinit var nodeC: StartedNode - lateinit var notary: Party - val services = MockServices() - - @Before - fun setup() { - mockNet = MockNetwork() - val nodes = mockNet.createSomeNodes(3) - nodeA = nodes.partyNodes[0] - nodeB = nodes.partyNodes[1] - nodeC = nodes.partyNodes[2] - mockNet.runNetwork() - nodeA.internals.ensureRegistered() - notary = nodeA.services.getDefaultNotary() - } - - @After - fun tearDown() { - mockNet.stopNodes() - } - - @Test - fun `finalise a simple transaction`() { - val amount = Amount(1000, Issued(nodeA.info.chooseIdentity().ref(0), GBP)) - val builder = TransactionBuilder(notary) - Cash().generateIssue(builder, amount, nodeB.info.chooseIdentity(), notary) - val stx = nodeA.services.signInitialTransaction(builder) - val flow = nodeA.services.startFlow(ManualFinalityFlow(stx, setOf(nodeC.info.chooseIdentity()))) - mockNet.runNetwork() - val result = flow.resultFuture.getOrThrow() - val notarisedTx = result.single() - notarisedTx.verifyRequiredSignatures() - // We override the participants, so node C will get a copy despite not being involved, and B won't - val transactionSeenByB = nodeB.services.database.transaction { - nodeB.services.validatedTransactions.getTransaction(notarisedTx.id) - } - assertNull(transactionSeenByB) - val transactionSeenByC = nodeC.services.database.transaction { - nodeC.services.validatedTransactions.getTransaction(notarisedTx.id) - } - assertEquals(notarisedTx, transactionSeenByC) - } -} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/TestDataVendingFlow.kt b/core/src/test/kotlin/net/corda/core/flows/TestDataVendingFlow.kt index 5c53890038..aa2f698c89 100644 --- a/core/src/test/kotlin/net/corda/core/flows/TestDataVendingFlow.kt +++ b/core/src/test/kotlin/net/corda/core/flows/TestDataVendingFlow.kt @@ -1,19 +1,18 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.identity.Party import net.corda.core.internal.FetchDataFlow import net.corda.core.utilities.UntrustworthyData // Flow to start data vending without sending transaction. For testing only. -class TestDataVendingFlow(otherSide: Party) : SendStateAndRefFlow(otherSide, emptyList()) { +class TestDataVendingFlow(otherSideSession: FlowSession) : SendStateAndRefFlow(otherSideSession, emptyList()) { @Suspendable - override fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any): UntrustworthyData { + override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData { return if (payload is List<*> && payload.isEmpty()) { // Hack to not send the first message. - receive(otherSide) + otherSideSession.receive() } else { - super.sendPayloadAndReceiveDataRequest(otherSide, payload) + super.sendPayloadAndReceiveDataRequest(this.otherSideSession, payload) } } } \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index dc752b1da0..5a3594b6de 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -2,10 +2,7 @@ package net.corda.core.internal import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.TestDataVendingFlow +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.getOrThrow @@ -195,20 +192,22 @@ class ResolveTransactionsFlowTest { // DOCEND 2 @InitiatingFlow - private class TestFlow(private val resolveTransactionsFlow: ResolveTransactionsFlow, private val txCountLimit: Int? = null) : FlowLogic>() { - constructor(txHashes: Set, otherSide: Party, txCountLimit: Int? = null) : this(ResolveTransactionsFlow(txHashes, otherSide), txCountLimit = txCountLimit) - constructor(stx: SignedTransaction, otherSide: Party) : this(ResolveTransactionsFlow(stx, otherSide)) + private class TestFlow(val otherSide: Party, private val resolveTransactionsFlowFactory: (FlowSession) -> ResolveTransactionsFlow, private val txCountLimit: Int? = null) : FlowLogic>() { + constructor(txHashes: Set, otherSide: Party, txCountLimit: Int? = null) : this(otherSide, { ResolveTransactionsFlow(txHashes, it) }, txCountLimit = txCountLimit) + constructor(stx: SignedTransaction, otherSide: Party) : this(otherSide, { ResolveTransactionsFlow(stx, it) }) @Suspendable override fun call(): List { + val session = initiateFlow(otherSide) + val resolveTransactionsFlow = resolveTransactionsFlowFactory(session) txCountLimit?.let { resolveTransactionsFlow.transactionCountLimit = it } return subFlow(resolveTransactionsFlow) } } @InitiatedBy(TestFlow::class) - private class TestResponseFlow(val otherSide: Party) : FlowLogic() { + private class TestResponseFlow(val otherSideSession: FlowSession) : FlowLogic() { @Suspendable - override fun call() = subFlow(TestDataVendingFlow(otherSide)) + override fun call() = subFlow(TestDataVendingFlow(otherSideSession)) } } diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index f935671300..0dc98505dc 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.TestDataVendingFlow import net.corda.core.identity.Party @@ -82,14 +83,14 @@ class AttachmentSerializationTest { mockNet.stopNodes() } - private class ServerLogic(private val client: Party, private val sendData: Boolean) : FlowLogic() { + private class ServerLogic(private val clientSession: FlowSession, private val sendData: Boolean) : FlowLogic() { @Suspendable override fun call() { if (sendData) { - subFlow(TestDataVendingFlow(client)) + subFlow(TestDataVendingFlow(clientSession)) } - receive(client).unwrap { assertEquals("ping one", it) } - sendAndReceive(client, "pong").unwrap { assertEquals("ping two", it) } + clientSession.receive().unwrap { assertEquals("ping one", it) } + clientSession.sendAndReceive("pong").unwrap { assertEquals("ping two", it) } } } @@ -100,9 +101,9 @@ class AttachmentSerializationTest { internal val server = server.info.chooseIdentity() @Suspendable - internal fun communicate() { - sendAndReceive(server, "ping one").unwrap { assertEquals("pong", it) } - send(server, "ping two") + internal fun communicate(serverSession: FlowSession) { + serverSession.sendAndReceive("ping one").unwrap { assertEquals("pong", it) } + serverSession.send("ping two") } @Suspendable @@ -121,7 +122,8 @@ class AttachmentSerializationTest { @Suspendable override fun getAttachmentContent(): String { val customAttachment = CustomAttachment(attachmentId, customContent) - communicate() + val session = initiateFlow(server) + communicate(session) return customAttachment.customContent } } @@ -130,7 +132,8 @@ class AttachmentSerializationTest { @Suspendable override fun getAttachmentContent(): String { val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!! - communicate() + val session = initiateFlow(server) + communicate(session) return localAttachment.extractContent() } } @@ -138,9 +141,10 @@ class AttachmentSerializationTest { private class FetchAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) { @Suspendable override fun getAttachmentContent(): String { - val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), server)).downloaded - send(server, FetchDataFlow.Request.End) - communicate() + val serverSession = initiateFlow(server) + val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), serverSession)).downloaded + serverSession.send(FetchDataFlow.Request.End) + communicate(serverSession) return downloadedAttachment.extractContent() } } @@ -148,7 +152,7 @@ class AttachmentSerializationTest { private fun launchFlow(clientLogic: ClientLogic, rounds: Int, sendData: Boolean = false) { server.internals.internalRegisterFlowFactory( ClientLogic::class.java, - InitiatedFlowFactory.Core { ServerLogic(it.counterparty, sendData) }, + InitiatedFlowFactory.Core { ServerLogic(it, sendData) }, ServerLogic::class.java, track = false) client.services.startFlow(clientLogic) diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index 8838e86368..a5cbf809dc 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -23,7 +23,7 @@ class IntegrationTestingTutorial { @Test fun `alice bob cash exchange example`() { // START 1 - driver { + driver(extraCordappPackagesToScan = listOf("net.corda.finance")) { val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( startFlowPermission(), startFlowPermission() diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index a4e0228066..1b84610ca9 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -2,7 +2,6 @@ package net.corda.docs; import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import net.corda.core.contracts.*; import net.corda.core.crypto.SecureHash; import net.corda.core.crypto.TransactionSignature; @@ -29,6 +28,7 @@ import java.security.GeneralSecurityException; import java.security.PublicKey; import java.time.Duration; import java.time.Instant; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -37,6 +37,7 @@ import static net.corda.testing.TestConstants.getALICE_KEY; // We group our two flows inside a singleton object to indicate that they work // together. +@SuppressWarnings("unused") public class FlowCookbookJava { // ``InitiatorFlow`` is our first flow, and will communicate with // ``ResponderFlow``, below. @@ -80,14 +81,14 @@ public class FlowCookbookJava { // subflow's progress steps in our flow's progress tracker. @Override public ProgressTracker childProgressTracker() { - return CollectSignaturesFlow.Companion.tracker(); + return CollectSignaturesFlow.tracker(); } }; private static final Step VERIFYING_SIGS = new Step("Verifying a transaction's signatures."); private static final Step FINALISATION = new Step("Finalising a transaction.") { @Override public ProgressTracker childProgressTracker() { - return FinalityFlow.Companion.tracker(); + return FinalityFlow.tracker(); } }; @@ -154,7 +155,8 @@ public class FlowCookbookJava { // registered to respond to this flow, and has a corresponding // ``receive`` call. // DOCSTART 4 - send(counterparty, new Object()); + FlowSession counterpartySession = initiateFlow(counterparty); + counterpartySession.send(new Object()); // DOCEND 4 // We can wait to receive arbitrary data of a specific type from a @@ -177,7 +179,7 @@ public class FlowCookbookJava { // be what it appears to be! We must unwrap the // ``UntrustworthyData`` using a lambda. // DOCSTART 5 - UntrustworthyData packet1 = receive(Integer.class, counterparty); + UntrustworthyData packet1 = counterpartySession.receive(Integer.class); Integer integer = packet1.unwrap(data -> { // Perform checking on the object received. // T O D O: Check the received object. @@ -191,7 +193,7 @@ public class FlowCookbookJava { // data sent doesn't need to match the type of the data received // back. // DOCSTART 7 - UntrustworthyData packet2 = sendAndReceive(Boolean.class, counterparty, "You can send and receive any class!"); + UntrustworthyData packet2 = counterpartySession.sendAndReceive(Boolean.class, "You can send and receive any class!"); Boolean bool = packet2.unwrap(data -> { // Perform checking on the object received. // T O D O: Check the received object. @@ -204,8 +206,9 @@ public class FlowCookbookJava { // counterparty. A flow can send messages to as many parties as it // likes, and each party can invoke a different response flow. // DOCSTART 6 - send(regulator, new Object()); - UntrustworthyData packet3 = receive(Object.class, regulator); + FlowSession regulatorSession = initiateFlow(regulator); + regulatorSession.send(new Object()); + UntrustworthyData packet3 = regulatorSession.receive(Object.class); // DOCEND 6 /*------------------------------------ @@ -395,10 +398,10 @@ public class FlowCookbookJava { // for data request until the transaction is resolved and verified // on the other side: // DOCSTART 12 - subFlow(new SendTransactionFlow(counterparty, twiceSignedTx)); + subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx)); // Optional request verification to further restrict data access. - subFlow(new SendTransactionFlow(counterparty, twiceSignedTx) { + subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx) { @Override protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) { // Extra request verification. @@ -408,17 +411,17 @@ public class FlowCookbookJava { // We can receive the transaction using ``ReceiveTransactionFlow``, // which will automatically download all the dependencies and verify - // the transaction + // the transaction and then record in our vault // DOCSTART 13 - SignedTransaction verifiedTransaction = subFlow(new ReceiveTransactionFlow(counterparty)); + SignedTransaction verifiedTransaction = subFlow(new ReceiveTransactionFlow(counterpartySession)); // DOCEND 13 // We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies. // DOCSTART 14 - subFlow(new SendStateAndRefFlow(counterparty, dummyStates)); + subFlow(new SendStateAndRefFlow(counterpartySession, dummyStates)); // On the receive side ... - List> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow(counterparty)); + List> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow(counterpartySession)); // DOCEND 14 try { @@ -475,7 +478,7 @@ public class FlowCookbookJava { // other required signers using ``CollectSignaturesFlow``. // The responder flow will need to call ``SignTransactionFlow``. // DOCSTART 15 - SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker())); + SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, Collections.emptySet(), SIGS_GATHERING.childProgressTracker())); // DOCEND 15 /*------------------------ @@ -517,13 +520,13 @@ public class FlowCookbookJava { // We notarise the transaction and get it recorded in the vault of // the participants of all the transaction's states. // DOCSTART 9 - SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).get(0); + SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())); // DOCEND 9 // We can also choose to send it to additional parties who aren't one // of the state's participants. // DOCSTART 10 - Set additionalParties = ImmutableSet.of(regulator); - SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(ImmutableList.of(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).get(0); + Set additionalParties = Collections.singleton(regulator); + SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker())); // DOCEND 10 return null; @@ -540,10 +543,10 @@ public class FlowCookbookJava { @InitiatedBy(InitiatorFlow.class) public static class ResponderFlow extends FlowLogic { - private final Party counterparty; + private final FlowSession counterpartySession; - public ResponderFlow(Party counterparty) { - this.counterparty = counterparty; + public ResponderFlow(FlowSession counterpartySession) { + this.counterpartySession = counterpartySession; } private static final Step RECEIVING_AND_SENDING_DATA = new Step("Sending data between parties."); @@ -575,9 +578,9 @@ public class FlowCookbookJava { // ``Boolean`` instance back // Our side of the flow must mirror these calls. // DOCSTART 8 - Object obj = receive(Object.class, counterparty).unwrap(data -> data); - String string = sendAndReceive(String.class, counterparty, 99).unwrap(data -> data); - send(counterparty, true); + Object obj = counterpartySession.receive(Object.class).unwrap(data -> data); + String string = counterpartySession.sendAndReceive(String.class, 99).unwrap(data -> data); + counterpartySession.send(true); // DOCEND 8 /*----------------------------------------- @@ -590,8 +593,8 @@ public class FlowCookbookJava { // ``SignTransactionFlow`` subclass. // DOCSTART 16 class SignTxFlow extends SignTransactionFlow { - private SignTxFlow(Party otherParty, ProgressTracker progressTracker) { - super(otherParty, progressTracker); + private SignTxFlow(FlowSession otherSession, ProgressTracker progressTracker) { + super(otherSession, progressTracker); } @Override @@ -605,7 +608,7 @@ public class FlowCookbookJava { } } - subFlow(new SignTxFlow(counterparty, SignTransactionFlow.Companion.tracker())); + subFlow(new SignTxFlow(counterpartySession, SignTransactionFlow.tracker())); // DOCEND 16 /*------------------------------ diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt index 170b945d10..3f56cb0a56 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomNotaryTutorial.kt @@ -3,7 +3,6 @@ package net.corda.docs import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.* -import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.CordaService import net.corda.core.node.services.TimeWindowChecker @@ -19,7 +18,7 @@ class MyCustomValidatingNotaryService(override val services: ServiceHub, overrid override val timeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider = PersistentUniquenessProvider() - override fun createServiceFlow(otherParty: Party): FlowLogic = MyValidatingNotaryFlow(otherParty, this) + override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = MyValidatingNotaryFlow(otherPartySession, this) override fun start() {} override fun stop() {} @@ -27,7 +26,7 @@ class MyCustomValidatingNotaryService(override val services: ServiceHub, overrid // END 1 // START 2 -class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotaryService) : NotaryFlow.Service(otherSide, service) { +class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryFlow.Service(otherSide, service) { /** * The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole * transaction dependency chain. @@ -35,7 +34,7 @@ class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotary @Suspendable override fun receiveAndVerifyTx(): TransactionParts { try { - val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false)) + val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false)) checkNotary(stx.notary) checkSignatures(stx) val wtx = stx.tx diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt index 0a5c060d21..47a5ca00a0 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt @@ -2,10 +2,7 @@ package net.corda.docs import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.CordaService @@ -85,12 +82,12 @@ object TopupIssuerFlow { @Throws(CashException::class) override fun call(): List { val topupRequest = TopupRequest(issueToParty, issueToPartyRef, notaryParty) - return sendAndReceive>(issuerBankParty, topupRequest).unwrap { it } + return initiateFlow(issuerBankParty).sendAndReceive>(topupRequest).unwrap { it } } } @InitiatedBy(TopupIssuanceRequester::class) - class TopupIssuer(val otherParty: Party) : FlowLogic>() { + class TopupIssuer(val otherPartySession: FlowSession) : FlowLogic>() { companion object { object AWAITING_REQUEST : ProgressTracker.Step("Awaiting issuance request") object ISSUING : ProgressTracker.Step("Issuing asset") @@ -107,7 +104,7 @@ object TopupIssuerFlow { @Throws(CashException::class) override fun call(): List { progressTracker.currentStep = AWAITING_REQUEST - val topupRequest = receive(otherParty).unwrap { + val topupRequest = otherPartySession.receive().unwrap { it } @@ -122,7 +119,7 @@ object TopupIssuerFlow { return@map txn.stx } - send(otherParty, txns) + otherPartySession.send(txns) return txns } // DOCEND TopupIssuer diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index ac13f8ac66..f16983a6c8 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -16,8 +16,11 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.* +import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Step +import net.corda.core.utilities.UntrustworthyData +import net.corda.core.utilities.seconds +import net.corda.core.utilities.unwrap import net.corda.finance.contracts.asset.Cash import net.corda.testing.ALICE_PUBKEY import net.corda.testing.contracts.DUMMY_PROGRAM_ID @@ -39,7 +42,7 @@ object FlowCookbook { @StartableByRPC // Every flow must subclass ``FlowLogic``. The generic indicates the // flow's return type. - class InitiatorFlow(val arg1: Boolean, val arg2: Int, val counterparty: Party, val regulator: Party) : FlowLogic() { + class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty: Party, val regulator: Party) : FlowLogic() { /**--------------------------------- * WIRING UP THE PROGRESS TRACKER * @@ -135,7 +138,8 @@ object FlowCookbook { // registered to respond to this flow, and has a corresponding // ``receive`` call. // DOCSTART 4 - send(counterparty, Any()) + val counterpartySession = initiateFlow(counterparty) + counterpartySession.send(Any()) // DOCEND 4 // We can wait to receive arbitrary data of a specific type from a @@ -158,7 +162,7 @@ object FlowCookbook { // be what it appears to be! We must unwrap the // ``UntrustworthyData`` using a lambda. // DOCSTART 5 - val packet1: UntrustworthyData = receive(counterparty) + val packet1: UntrustworthyData = counterpartySession.receive() val int: Int = packet1.unwrap { data -> // Perform checking on the object received. // T O D O: Check the received object. @@ -172,7 +176,7 @@ object FlowCookbook { // data sent doesn't need to match the type of the data received // back. // DOCSTART 7 - val packet2: UntrustworthyData = sendAndReceive(counterparty, "You can send and receive any class!") + val packet2: UntrustworthyData = counterpartySession.sendAndReceive("You can send and receive any class!") val boolean: Boolean = packet2.unwrap { data -> // Perform checking on the object received. // T O D O: Check the received object. @@ -185,8 +189,9 @@ object FlowCookbook { // counterparty. A flow can send messages to as many parties as it // likes, and each party can invoke a different response flow. // DOCSTART 6 - send(regulator, Any()) - val packet3: UntrustworthyData = receive(regulator) + val regulatorSession = initiateFlow(regulator) + regulatorSession.send(Any()) + val packet3: UntrustworthyData = regulatorSession.receive() // DOCEND 6 /**----------------------------------- @@ -378,10 +383,10 @@ object FlowCookbook { // for data request until the transaction is resolved and verified // on the other side: // DOCSTART 12 - subFlow(SendTransactionFlow(counterparty, twiceSignedTx)) + subFlow(SendTransactionFlow(counterpartySession, twiceSignedTx)) // Optional request verification to further restrict data access. - subFlow(object : SendTransactionFlow(counterparty, twiceSignedTx) { + subFlow(object : SendTransactionFlow(counterpartySession, twiceSignedTx) { override fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) { // Extra request verification. } @@ -392,16 +397,16 @@ object FlowCookbook { // which will automatically download all the dependencies and verify // the transaction // DOCSTART 13 - val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterparty)) + val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterpartySession)) // DOCEND 13 // We can also send and receive a `StateAndRef` dependency chain // and automatically resolve its dependencies. // DOCSTART 14 - subFlow(SendStateAndRefFlow(counterparty, dummyStates)) + subFlow(SendStateAndRefFlow(counterpartySession, dummyStates)) // On the receive side ... - val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow(counterparty)) + val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow(counterpartySession)) // DOCEND 14 // We can now verify the transaction to ensure that it satisfies @@ -452,7 +457,7 @@ object FlowCookbook { // other required signers using ``CollectSignaturesFlow``. // The responder flow will need to call ``SignTransactionFlow``. // DOCSTART 15 - val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker())) + val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, emptySet(), SIGS_GATHERING.childProgressTracker())) // DOCEND 15 /**----------------------- @@ -488,13 +493,13 @@ object FlowCookbook { // We notarise the transaction and get it recorded in the vault of // the participants of all the transaction's states. // DOCSTART 9 - val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).single() + val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())) // DOCEND 9 // We can also choose to send it to additional parties who aren't one // of the state's participants. // DOCSTART 10 val additionalParties: Set = setOf(regulator) - val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(listOf(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).single() + val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker())) // DOCEND 10 } } @@ -507,7 +512,7 @@ object FlowCookbook { // Each node also has several flow pairs registered by default - see // ``AbstractNode.installCoreFlows``. @InitiatedBy(InitiatorFlow::class) - class ResponderFlow(val counterparty: Party) : FlowLogic() { + class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic() { companion object { object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.") @@ -541,9 +546,9 @@ object FlowCookbook { // ``Boolean`` instance back // Our side of the flow must mirror these calls. // DOCSTART 8 - val any: Any = receive(counterparty).unwrap { data -> data } - val string: String = sendAndReceive(counterparty, 99).unwrap { data -> data } - send(counterparty, true) + val any: Any = counterpartySession.receive().unwrap { data -> data } + val string: String = counterpartySession.sendAndReceive(99).unwrap { data -> data } + counterpartySession.send(true) // DOCEND 8 /**---------------------------------------- @@ -555,7 +560,7 @@ object FlowCookbook { // ``CollectSignaturesFlow``. It does so my invoking its own // ``SignTransactionFlow`` subclass. // DOCSTART 16 - val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterparty) { + val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterpartySession) { override fun checkTransaction(stx: SignedTransaction) = requireThat { // Any additional checking we see fit... val outputState = stx.tx.outputsOfType().single() diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt index aa0fa394fa..6ccc807d6a 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt @@ -50,7 +50,7 @@ private fun gatherOurInputs(serviceHub: ServiceHub, val eligibleStates = serviceHub.vaultService.tryLockFungibleStatesForSpending(lockId, fullCriteria, amountRequired.withoutIssuer(), Cash.State::class.java) check(eligibleStates.isNotEmpty()) { "Insufficient funds" } - val amount = eligibleStates.fold(0L) { tot, x -> tot + x.state.data.amount.quantity } + val amount = eligibleStates.fold(0L) { tot, (state) -> tot + state.data.amount.quantity } val change = amount - amountRequired.quantity return Pair(eligibleStates, change) @@ -87,25 +87,25 @@ private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, lockId: UUID, req // A flow representing creating a transaction that // carries out exchange of cash assets. @InitiatingFlow -class ForeignExchangeFlow(val tradeId: String, - val baseCurrencyAmount: Amount>, - val quoteCurrencyAmount: Amount>, - val baseCurrencyBuyer: Party, - val baseCurrencySeller: Party) : FlowLogic() { +class ForeignExchangeFlow(private val tradeId: String, + private val baseCurrencyAmount: Amount>, + private val quoteCurrencyAmount: Amount>, + private val counterparty: Party, + private val weAreBaseCurrencySeller: Boolean) : FlowLogic() { @Suspendable override fun call(): SecureHash { // Select correct sides of the Fx exchange to query for. // Specifically we own the assets we wish to sell. // Also prepare the other side query - val (localRequest, remoteRequest) = if (serviceHub.myInfo.isLegalIdentity(baseCurrencySeller)) { - val local = FxRequest(tradeId, baseCurrencyAmount, baseCurrencySeller, baseCurrencyBuyer) - val remote = FxRequest(tradeId, quoteCurrencyAmount, baseCurrencyBuyer, baseCurrencySeller) + val (localRequest, remoteRequest) = if (weAreBaseCurrencySeller) { + val local = FxRequest(tradeId, baseCurrencyAmount, ourIdentity, counterparty) + val remote = FxRequest(tradeId, quoteCurrencyAmount, counterparty, ourIdentity) Pair(local, remote) - } else if (serviceHub.myInfo.isLegalIdentity(baseCurrencyBuyer)) { - val local = FxRequest(tradeId, quoteCurrencyAmount, baseCurrencyBuyer, baseCurrencySeller) - val remote = FxRequest(tradeId, baseCurrencyAmount, baseCurrencySeller, baseCurrencyBuyer) + } else { + val local = FxRequest(tradeId, quoteCurrencyAmount, ourIdentity, counterparty) + val remote = FxRequest(tradeId, baseCurrencyAmount, counterparty, ourIdentity) Pair(local, remote) - } else throw IllegalArgumentException("Our identity must be one of the parties in the trade.") + } // Call the helper method to identify suitable inputs and make the outputs val (ourInputStates, ourOutputStates) = prepareOurInputsAndOutputs(serviceHub, runId.uuid, localRequest) @@ -117,9 +117,10 @@ class ForeignExchangeFlow(val tradeId: String, // Send the request to the counterparty to verify and call their version of prepareOurInputsAndOutputs // Then they can return their candidate states - send(remoteRequestWithNotary.owner, remoteRequestWithNotary) - val theirInputStates = subFlow(ReceiveStateAndRefFlow(remoteRequestWithNotary.owner)) - val theirOutputStates = receive>(remoteRequestWithNotary.owner).unwrap { + val counterpartySession = initiateFlow(counterparty) + counterpartySession.send(remoteRequestWithNotary) + val theirInputStates = subFlow(ReceiveStateAndRefFlow(counterpartySession)) + val theirOutputStates = counterpartySession.receive>().unwrap { require(theirInputStates.all { it.state.notary == notary }) { "notary of remote states must be same as for our states" } @@ -144,9 +145,9 @@ class ForeignExchangeFlow(val tradeId: String, val signedTransaction = buildTradeProposal(ourInputStates, ourOutputStates, theirInputStates, theirOutputStates) // pass transaction details to the counterparty to revalidate and confirm with a signature - // Allow otherParty to access our data to resolve the transaction. - subFlow(SendTransactionFlow(remoteRequestWithNotary.owner, signedTransaction)) - val allPartySignedTx = receive(remoteRequestWithNotary.owner).unwrap { + // Allow counterparty to access our data to resolve the transaction. + subFlow(SendTransactionFlow(counterpartySession, signedTransaction)) + val allPartySignedTx = counterpartySession.receive().unwrap { val withNewSignature = signedTransaction + it // check all signatures are present except the notary withNewSignature.verifySignaturesExcept(withNewSignature.tx.notary!!.owningKey) @@ -160,7 +161,7 @@ class ForeignExchangeFlow(val tradeId: String, } // Initiate the standard protocol to notarise and distribute to the involved parties. - subFlow(FinalityFlow(allPartySignedTx, setOf(baseCurrencyBuyer, baseCurrencySeller))) + subFlow(FinalityFlow(allPartySignedTx, setOf(counterparty))) return allPartySignedTx.id } @@ -195,11 +196,11 @@ class ForeignExchangeFlow(val tradeId: String, } @InitiatedBy(ForeignExchangeFlow::class) -class ForeignExchangeRemoteFlow(private val source: Party) : FlowLogic() { +class ForeignExchangeRemoteFlow(private val source: FlowSession) : FlowLogic() { @Suspendable override fun call() { // Initial receive from remote party - val request = receive(source).unwrap { + val request = source.receive().unwrap { // We would need to check that this is a known trade ID here! // Also that the amounts and source are correct with the trade details. // In a production system there would be other Corda contracts tracking @@ -209,7 +210,7 @@ class ForeignExchangeRemoteFlow(private val source: Party) : FlowLogic() { require(serviceHub.myInfo.isLegalIdentity(it.owner)) { "Request does not include the correct counterparty" } - require(source == it.counterparty) { + require(source.counterparty == it.counterparty) { "Request does not include the correct counterparty" } it // return validated request @@ -224,18 +225,13 @@ class ForeignExchangeRemoteFlow(private val source: Party) : FlowLogic() { // Send back our proposed states and await the full transaction to verify val ourKey = serviceHub.keyManagementService.filterMyKeys(ourInputState.flatMap { it.state.data.participants }.map { it.owningKey }).single() - // SendStateAndRefFlow allows otherParty to access our transaction data to resolve the transaction. + // SendStateAndRefFlow allows counterparty to access our transaction data to resolve the transaction. subFlow(SendStateAndRefFlow(source, ourInputState)) - send(source, ourOutputState) + source.send(ourOutputState) val proposedTrade = subFlow(ReceiveTransactionFlow(source, checkSufficientSignatures = false)).let { val wtx = it.tx // check all signatures are present except our own and the notary it.verifySignaturesExcept(ourKey, wtx.notary!!.owningKey) - - // This verifies that the transaction is contract-valid, even though it is missing signatures. - // In a full solution there would be states tracking the trade request which - // would be included in the transaction and enforce the amounts and tradeId - wtx.toLedgerTransaction(serviceHub).verify() it // return the SignedTransaction } @@ -243,7 +239,7 @@ class ForeignExchangeRemoteFlow(private val source: Party) : FlowLogic() { val ourSignature = serviceHub.createSignature(proposedTrade, ourKey) // send the other side our signature. - send(source, ourSignature) + source.send(ourSignature) // N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction // and broadcasting the result to us. } diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt index b95f6a99ac..2532577f9a 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt @@ -3,10 +3,7 @@ package net.corda.docs import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* import net.corda.core.crypto.TransactionSignature -import net.corda.core.flows.FinalityFlow -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.queryBy @@ -110,7 +107,7 @@ class SubmitTradeApprovalFlow(private val tradeId: String, // We can automatically sign as there is no untrusted data. val signedTx = serviceHub.signInitialTransaction(tx) // Notarise and distribute. - subFlow(FinalityFlow(signedTx, setOf(ourIdentity, counterparty))) + subFlow(FinalityFlow(signedTx, setOf(counterparty))) // Return the initial state return signedTx.tx.outRef(0) } @@ -175,7 +172,8 @@ class SubmitCompletionFlow(private val ref: StateRef, private val verdict: Workf val selfSignedTx = serviceHub.signInitialTransaction(tx) //DOCEND 2 // Send the signed transaction to the originator and await their signature to confirm - val allPartySignedTx = sendAndReceive(newState.source, selfSignedTx).unwrap { + val session = initiateFlow(newState.source) + val allPartySignedTx = session.sendAndReceive(selfSignedTx).unwrap { // Add their signature to our unmodified transaction. To check they signed the same tx. val agreedTx = selfSignedTx + it // Receive back their signature and confirm that it is for an unmodified transaction @@ -189,7 +187,7 @@ class SubmitCompletionFlow(private val ref: StateRef, private val verdict: Workf } // DOCSTART 4 // Notarise and distribute the completed transaction. - subFlow(FinalityFlow(allPartySignedTx, setOf(latestRecord.state.data.source, latestRecord.state.data.counterparty))) + subFlow(FinalityFlow(allPartySignedTx, setOf(newState.source))) // DOCEND 4 // Return back the details of the completed state/transaction. return allPartySignedTx.tx.outRef(0) @@ -202,12 +200,12 @@ class SubmitCompletionFlow(private val ref: StateRef, private val verdict: Workf * transaction to the ledger. */ @InitiatedBy(SubmitCompletionFlow::class) -class RecordCompletionFlow(private val source: Party) : FlowLogic() { +class RecordCompletionFlow(private val sourceSession: FlowSession) : FlowLogic() { @Suspendable override fun call() { // DOCSTART 3 // First we receive the verdict transaction signed by their single key - val completeTx = receive(source).unwrap { + val completeTx = sourceSession.receive().unwrap { // Check the transaction is signed apart from our own key and the notary it.verifySignaturesExcept(ourIdentity.owningKey, it.tx.notary!!.owningKey) // Check the transaction data is correctly formed @@ -223,7 +221,7 @@ class RecordCompletionFlow(private val source: Party) : FlowLogic() { require(serviceHub.myInfo.isLegalIdentity(state.state.data.source)) { "Proposal not one of our original proposals" } - require(state.state.data.counterparty == source) { + require(state.state.data.counterparty == sourceSession.counterparty) { "Proposal not for sent from correct source" } it @@ -232,7 +230,7 @@ class RecordCompletionFlow(private val source: Party) : FlowLogic() { // Having verified the SignedTransaction passed to us we can sign it too val ourSignature = serviceHub.createSignature(completeTx) // Send our signature to the other party. - send(source, ourSignature) + sourceSession.send(ourSignature) // N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction // and broadcasting the result to us. } diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index fc84e71509..c3b56f34a1 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -7,11 +7,11 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.* import net.corda.finance.contracts.getCashBalances import net.corda.finance.flows.CashIssueFlow -import net.corda.node.internal.StartedNode import net.corda.finance.schemas.CashSchemaV1 -import net.corda.nodeapi.ServiceInfo +import net.corda.node.internal.StartedNode import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.ValidatingNotaryService +import net.corda.nodeapi.ServiceInfo import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.testing.chooseIdentity @@ -76,8 +76,8 @@ class FxTransactionBuildTutorialTest { val doIt = nodeA.services.startFlow(ForeignExchangeFlow("trade1", POUNDS(100).issuedBy(nodeB.info.chooseIdentity().ref(0x01)), DOLLARS(200).issuedBy(nodeA.info.chooseIdentity().ref(0x01)), - nodeA.info.chooseIdentity(), - nodeB.info.chooseIdentity())) + nodeB.info.chooseIdentity(), + weAreBaseCurrencySeller = false)) // wait for the flow to finish and the vault updates to be done doIt.resultFuture.getOrThrow() // Get the balances when the vault updates diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 604b63f676..85cb11b422 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -9,9 +9,9 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.toFuture import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode -import net.corda.nodeapi.ServiceInfo import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.ValidatingNotaryService +import net.corda.nodeapi.ServiceInfo import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.testing.chooseIdentity diff --git a/finance/src/main/kotlin/net/corda/finance/flows/AbstractCashFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/AbstractCashFlow.kt index f86a3c52d0..e1579fef90 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/AbstractCashFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/AbstractCashFlow.kt @@ -27,9 +27,9 @@ abstract class AbstractCashFlow(override val progressTracker: ProgressTra } @Suspendable - protected fun finaliseTx(participants: Set, tx: SignedTransaction, message: String) { + protected fun finaliseTx(tx: SignedTransaction, extraParticipants: Set, message: String): SignedTransaction { try { - subFlow(FinalityFlow(tx, participants)) + return subFlow(FinalityFlow(tx, extraParticipants)) } catch (e: NotaryException) { throw CashException(message, e) } diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt index 4ba83b2645..63bb087726 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt @@ -44,9 +44,10 @@ class CashExitFlow(private val amount: Amount, @Throws(CashException::class) override fun call(): AbstractCashFlow.Result { progressTracker.currentStep = GENERATING_TX - val builder = TransactionBuilder(notary = null as Party?) + val builder = TransactionBuilder(notary = null) val issuer = ourIdentity.ref(issuerRef) - val exitStates = CashSelection.getInstance { serviceHub.jdbcSession().metaData } + val exitStates = CashSelection + .getInstance { serviceHub.jdbcSession().metaData } .unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference)) val signers = try { Cash().generateExit( @@ -72,8 +73,8 @@ class CashExitFlow(private val amount: Amount, // Commit the transaction progressTracker.currentStep = FINALISING_TX - finaliseTx(participants, tx, "Unable to notarise exit") - return Result(tx, null) + val notarised = finaliseTx(tx, participants, "Unable to notarise exit") + return Result(notarised, null) } @CordaSerializable diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt index 2072a0f214..56f071783a 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt @@ -2,7 +2,6 @@ package net.corda.finance.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount -import net.corda.core.flows.FinalityFlow import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable @@ -43,7 +42,8 @@ class CashIssueFlow(private val amount: Amount, progressTracker.currentStep = SIGNING_TX val tx = serviceHub.signInitialTransaction(builder, signers) progressTracker.currentStep = FINALISING_TX - val notarised = subFlow(FinalityFlow(tx)).single() + // There is no one to send the tx to as we're the only participants + val notarised = finaliseTx(tx, emptySet(), "Unable to notarise issue") return Result(notarised, ourIdentity) } diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt index 38623c23d2..8f6402a799 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt @@ -45,7 +45,7 @@ open class CashPaymentFlow( } val anonymousRecipient = txIdentities[recipient] ?: recipient progressTracker.currentStep = GENERATING_TX - val builder = TransactionBuilder(null as Party?) + val builder = TransactionBuilder(notary = null) // TODO: Have some way of restricting this to states the caller controls val (spendTX, keysForSigning) = try { Cash.generateSpend(serviceHub, @@ -61,10 +61,13 @@ open class CashPaymentFlow( val tx = serviceHub.signInitialTransaction(spendTX, keysForSigning) progressTracker.currentStep = FINALISING_TX - finaliseTx(setOf(recipient), tx, "Unable to notarise spend") - return Result(tx, anonymousRecipient) + val notarised = finaliseTx(tx, setOf(recipient), "Unable to notarise spend") + return Result(notarised, anonymousRecipient) } @CordaSerializable - class PaymentRequest(amount: Amount, val recipient: Party, val anonymous: Boolean, val issuerConstraint: Set = emptySet()) : AbstractRequest(amount) -} \ No newline at end of file + class PaymentRequest(amount: Amount, + val recipient: Party, + val anonymous: Boolean, + val issuerConstraint: Set = emptySet()) : AbstractRequest(amount) +} diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt index 9aeb2bd755..8398bb37c5 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyDealFlow.kt @@ -3,12 +3,8 @@ package net.corda.finance.flows import co.paralleluniverse.fibers.Suspendable import net.corda.confidential.SwapIdentitiesFlow import net.corda.core.contracts.requireThat -import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature -import net.corda.core.flows.CollectSignaturesFlow -import net.corda.core.flows.FinalityFlow -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.SignTransactionFlow +import net.corda.core.flows.* import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable @@ -50,38 +46,38 @@ object TwoPartyDealFlow { abstract val payload: Any abstract val notaryParty: Party - abstract val otherParty: Party + abstract val otherSideSession: FlowSession - @Suspendable override fun call(): SignedTransaction { + @Suspendable + override fun call(): SignedTransaction { progressTracker.currentStep = GENERATING_ID - val txIdentities = subFlow(SwapIdentitiesFlow(otherParty)) + val txIdentities = subFlow(SwapIdentitiesFlow(otherSideSession.counterparty)) val anonymousMe = txIdentities[ourIdentity] ?: ourIdentity.anonymise() - val anonymousCounterparty = txIdentities[otherParty] ?: otherParty.anonymise() + val anonymousCounterparty = txIdentities[otherSideSession.counterparty] ?: otherSideSession.counterparty.anonymise() progressTracker.currentStep = SENDING_PROPOSAL // Make the first message we'll send to kick off the flow. val hello = Handshake(payload, anonymousMe, anonymousCounterparty) // Wait for the FinalityFlow to finish on the other side and return the tx when it's available. - send(otherParty, hello) + otherSideSession.send(hello) - val signTransactionFlow = object : SignTransactionFlow(otherParty) { + val signTransactionFlow = object : SignTransactionFlow(otherSideSession) { override fun checkTransaction(stx: SignedTransaction) = checkProposal(stx) } - subFlow(signTransactionFlow) + val txId = subFlow(signTransactionFlow).id - val txHash = receive(otherParty).unwrap { it } - - return waitForLedgerCommit(txHash) + return waitForLedgerCommit(txId) } - @Suspendable abstract fun checkProposal(stx: SignedTransaction) + @Suspendable + abstract fun checkProposal(stx: SignedTransaction) } /** * Abstracted bilateral deal flow participant that is recipient of initial communication. */ abstract class Secondary(override val progressTracker: ProgressTracker = Secondary.tracker(), - val regulators: List = emptyList()) : FlowLogic() { + val regulators: Set = emptySet()) : FlowLogic() { companion object { object RECEIVING : ProgressTracker.Step("Waiting for deal info.") @@ -89,13 +85,11 @@ object TwoPartyDealFlow { object SIGNING : ProgressTracker.Step("Generating and signing transaction proposal.") object COLLECTING_SIGNATURES : ProgressTracker.Step("Collecting signatures from other parties.") object RECORDING : ProgressTracker.Step("Recording completed transaction.") - object COPYING_TO_REGULATOR : ProgressTracker.Step("Copying regulator.") - object COPYING_TO_COUNTERPARTY : ProgressTracker.Step("Copying counterparty.") - fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING, COPYING_TO_REGULATOR, COPYING_TO_COUNTERPARTY) + fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING, COLLECTING_SIGNATURES, RECORDING) } - abstract val otherParty: Party + abstract val otherSideSession: FlowSession @Suspendable override fun call(): SignedTransaction { @@ -109,31 +103,24 @@ object TwoPartyDealFlow { serviceHub.signInitialTransaction(utx, additionalSigningPubKeys) } - logger.trace { "Signed proposed transaction." } + logger.trace("Signed proposed transaction.") progressTracker.currentStep = COLLECTING_SIGNATURES + // Get signature of initiating side + val ptxSignedByOtherSide = ptx + subFlow(CollectSignatureFlow(ptx, otherSideSession, otherSideSession.counterparty.owningKey)) + // DOCSTART 1 - val stx = subFlow(CollectSignaturesFlow(ptx, additionalSigningPubKeys)) + // Get signatures of other signers + val sessionsForOtherSigners = serviceHub.excludeNotary(serviceHub.groupPublicKeysByWellKnownParty(ptxSignedByOtherSide.getMissingSigners()), ptxSignedByOtherSide).map { initiateFlow(it.key) } + val stx = subFlow(CollectSignaturesFlow(ptxSignedByOtherSide, sessionsForOtherSigners, additionalSigningPubKeys)) // DOCEND 1 - logger.trace { "Got signatures from other party, verifying ... " } + logger.trace("Got signatures from other party, verifying ... ") progressTracker.currentStep = RECORDING - val ftx = subFlow(FinalityFlow(stx, setOf(otherParty, ourIdentity))).single() - - logger.trace { "Recorded transaction." } - - progressTracker.currentStep = COPYING_TO_REGULATOR - - // Copy the transaction to every regulator in the network. This is obviously completely bogus, it's - // just for demo purposes. - regulators.forEach { send(it, ftx) } - - progressTracker.currentStep = COPYING_TO_COUNTERPARTY - // Send the final transaction hash back to the other party. - // We need this so we don't break the IRS demo and the SIMM Demo. - send(otherParty, ftx.id) + val ftx = subFlow(FinalityFlow(stx, regulators + otherSideSession.counterparty)) + logger.trace("Recorded transaction.") return ftx } @@ -142,14 +129,14 @@ object TwoPartyDealFlow { private fun receiveAndValidateHandshake(): Handshake { progressTracker.currentStep = RECEIVING // Wait for a trade request to come in on our pre-provided session ID. - val handshake = receive>(otherParty) + val handshake = otherSideSession.receive>() progressTracker.currentStep = VERIFYING return handshake.unwrap { // Verify the transaction identities represent the correct parties val wellKnownOtherParty = serviceHub.identityService.partyFromAnonymous(it.primaryIdentity) val wellKnownMe = serviceHub.identityService.partyFromAnonymous(it.secondaryIdentity) - require(wellKnownOtherParty == otherParty) + require(wellKnownOtherParty == otherSideSession.counterparty) require(wellKnownMe == ourIdentity) validateHandshake(it) } @@ -167,7 +154,7 @@ object TwoPartyDealFlow { /** * One side of the flow for inserting a pre-agreed deal. */ - open class Instigator(override val otherParty: Party, + open class Instigator(override val otherSideSession: FlowSession, override val payload: AutoOffer, override val progressTracker: ProgressTracker = Primary.tracker()) : Primary() { override val notaryParty: Party get() = payload.notary @@ -180,7 +167,7 @@ object TwoPartyDealFlow { /** * One side of the flow for inserting a pre-agreed deal. */ - open class Acceptor(override val otherParty: Party, + open class Acceptor(override val otherSideSession: FlowSession, override val progressTracker: ProgressTracker = Secondary.tracker()) : Secondary() { override fun validateHandshake(handshake: Handshake): Handshake { diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt index fd83370c6f..57a862041c 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt @@ -55,10 +55,10 @@ object TwoPartyTradeFlow { val payToIdentity: PartyAndCertificate ) - open class Seller(val otherParty: Party, - val assetToSell: StateAndRef, - val price: Amount, - val myParty: PartyAndCertificate, // TODO Left because in tests it's used to pass anonymous party. + open class Seller(private val otherSideSession: FlowSession, + private val assetToSell: StateAndRef, + private val price: Amount, + private val myParty: PartyAndCertificate, // TODO Left because in tests it's used to pass anonymous party. override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic() { companion object { @@ -80,22 +80,22 @@ object TwoPartyTradeFlow { val hello = SellerTradeInfo(price, myParty) // What we get back from the other side is a transaction that *might* be valid and acceptable to us, // but we must check it out thoroughly before we sign! - // SendTransactionFlow allows otherParty to access our data to resolve the transaction. - subFlow(SendStateAndRefFlow(otherParty, listOf(assetToSell))) - send(otherParty, hello) + // SendTransactionFlow allows seller to access our data to resolve the transaction. + subFlow(SendStateAndRefFlow(otherSideSession, listOf(assetToSell))) + otherSideSession.send(hello) // Verify and sign the transaction. progressTracker.currentStep = VERIFYING_AND_SIGNING // Sync identities to ensure we know all of the identities involved in the transaction we're about to // be asked to sign - subFlow(IdentitySyncFlow.Receive(otherParty)) + subFlow(IdentitySyncFlow.Receive(otherSideSession)) // DOCSTART 5 - val signTransactionFlow = object : SignTransactionFlow(otherParty, VERIFYING_AND_SIGNING.childProgressTracker()) { + val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) { override fun checkTransaction(stx: SignedTransaction) { // Verify that we know who all the participants in the transaction are - val states: Iterable = (stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data }) + val states: Iterable = stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data } states.forEach { state -> state.participants.forEach { anon -> require(serviceHub.identityService.partyFromAnonymous(anon) != null) { @@ -108,8 +108,11 @@ object TwoPartyTradeFlow { throw FlowException("Transaction is not sending us the right amount of cash") } } - return subFlow(signTransactionFlow) + + val txId = subFlow(signTransactionFlow).id // DOCEND 5 + + return waitForLedgerCommit(txId) } // DOCEND 4 @@ -126,13 +129,13 @@ object TwoPartyTradeFlow { // express flow state machines on top of the messaging layer. } - open class Buyer(private val otherParty: Party, + open class Buyer(private val sellerSession: FlowSession, private val notary: Party, private val acceptablePrice: Amount, private val typeToBuy: Class, private val anonymous: Boolean) : FlowLogic() { - constructor(otherParty: Party, notary: Party, acceptablePrice: Amount, typeToBuy: Class) : - this(otherParty, notary, acceptablePrice, typeToBuy, true) + constructor(otherSideSession: FlowSession, notary: Party, acceptablePrice: Amount, typeToBuy: Class) : + this(otherSideSession, notary, acceptablePrice, typeToBuy, true) // DOCSTART 2 object RECEIVING : ProgressTracker.Step("Waiting for seller trading info") @@ -170,22 +173,23 @@ object TwoPartyTradeFlow { val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys) // Sync up confidential identities in the transaction with our counterparty - subFlow(IdentitySyncFlow.Send(otherParty, ptx.toWireTransaction())) + subFlow(IdentitySyncFlow.Send(sellerSession, ptx.toWireTransaction())) // Send the signed transaction to the seller, who must then sign it themselves and commit // it to the ledger by sending it to the notary. progressTracker.currentStep = COLLECTING_SIGNATURES - val twiceSignedTx = subFlow(CollectSignaturesFlow(partSignedTx, cashSigningPubKeys, COLLECTING_SIGNATURES.childProgressTracker())) + val sellerSignature = subFlow(CollectSignatureFlow(partSignedTx, sellerSession, sellerSession.counterparty.owningKey)) + val twiceSignedTx = partSignedTx + sellerSignature // Notarise and record the transaction. progressTracker.currentStep = RECORDING - return subFlow(FinalityFlow(twiceSignedTx)).single() + return subFlow(FinalityFlow(twiceSignedTx)) } @Suspendable private fun receiveAndValidateTradeRequest(): Pair, SellerTradeInfo> { - val assetForSale = subFlow(ReceiveStateAndRefFlow(otherParty)).single() - return assetForSale to receive(otherParty).unwrap { + val assetForSale = subFlow(ReceiveStateAndRefFlow(sellerSession)).single() + return assetForSale to sellerSession.receive().unwrap { progressTracker.currentStep = VERIFYING // What is the seller trying to sell us? val asset = assetForSale.state.data @@ -194,12 +198,12 @@ object TwoPartyTradeFlow { // The asset must either be owned by the well known identity of the counterparty, or we must be able to // prove the owner is a confidential identity of the counterparty. val assetForSaleIdentity = serviceHub.identityService.partyFromAnonymous(asset.owner) - require(assetForSaleIdentity == otherParty) + require(assetForSaleIdentity == sellerSession.counterparty) // Register the identity we're about to send payment to. This shouldn't be the same as the asset owner // identity, so that anonymity is enforced. val wellKnownPayToIdentity = serviceHub.identityService.verifyAndRegisterIdentity(it.payToIdentity) - require(wellKnownPayToIdentity?.party == otherParty) { "Well known identity to pay to must match counterparty identity" } + require(wellKnownPayToIdentity?.party == sellerSession.counterparty) { "Well known identity to pay to must match counterparty identity" } if (it.price > acceptablePrice) throw UnacceptablePriceException(it.price) diff --git a/finance/src/test/java/net/corda/finance/flows/AbstractStateReplacementFlowTest.java b/finance/src/test/java/net/corda/finance/flows/AbstractStateReplacementFlowTest.java index 3b78a34881..b753141ac5 100644 --- a/finance/src/test/java/net/corda/finance/flows/AbstractStateReplacementFlowTest.java +++ b/finance/src/test/java/net/corda/finance/flows/AbstractStateReplacementFlowTest.java @@ -1,7 +1,7 @@ package net.corda.finance.flows; import net.corda.core.flows.AbstractStateReplacementFlow; -import net.corda.core.identity.Party; +import net.corda.core.flows.FlowSession; import net.corda.core.transactions.SignedTransaction; import net.corda.core.utilities.ProgressTracker; import org.jetbrains.annotations.NotNull; @@ -11,8 +11,8 @@ public class AbstractStateReplacementFlowTest { // Acceptor used to have a type parameter of Unit which prevented Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964). private static class TestAcceptorCanBeInheritedInJava extends AbstractStateReplacementFlow.Acceptor { - public TestAcceptorCanBeInheritedInJava(@NotNull Party otherSide, @NotNull ProgressTracker progressTracker) { - super(otherSide, progressTracker); + public TestAcceptorCanBeInheritedInJava(@NotNull FlowSession otherSideSession, @NotNull ProgressTracker progressTracker) { + super(otherSideSession, progressTracker); } @Override diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt index ee215040c6..73d5330b12 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt @@ -38,10 +38,9 @@ class CashPaymentFlowTests { notaryNode = nodes.notaryNode bankOfCordaNode = nodes.partyNodes[0] bankOfCorda = bankOfCordaNode.info.chooseIdentity() - - mockNet.runNetwork() - notary = bankOfCordaNode.services.getDefaultNotary() + notary = notaryNode.services.getDefaultNotary() val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture + mockNet.runNetwork() future.getOrThrow() } diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt index d6ae803077..a46856b2ce 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt @@ -1,19 +1,16 @@ package net.corda.node import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow -import net.corda.testing.ALICE -import net.corda.testing.BOB import net.corda.core.utilities.unwrap import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.nodeapi.User +import net.corda.testing.ALICE +import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat @@ -41,15 +38,15 @@ class CordappScanningDriverTest { @InitiatingFlow class ReceiveFlow(val otherParty: Party) :FlowLogic() { @Suspendable - override fun call(): String = receive(otherParty).unwrap { it } + override fun call(): String = initiateFlow(otherParty).receive().unwrap { it } } @InitiatedBy(ReceiveFlow::class) - open class SendClassFlow(val otherParty: Party) : FlowLogic() { + open class SendClassFlow(val otherPartySession: FlowSession) : FlowLogic() { @Suspendable - override fun call() = send(otherParty, javaClass.name) + override fun call() = otherPartySession.send(javaClass.name) } @InitiatedBy(ReceiveFlow::class) - class SendSubClassFlow(otherParty: Party) : SendClassFlow(otherParty) + class SendSubClassFlow(otherPartySession: FlowSession) : SendClassFlow(otherPartySession) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt index 7d56427f5a..a32c1eae59 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt @@ -2,6 +2,7 @@ package net.corda.node.services.statemachine import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.internal.concurrent.transpose @@ -32,17 +33,18 @@ class FlowVersioningTest : NodeBasedTest() { @Suspendable override fun call(): Pair { // Execute receive() outside of the Pair constructor to avoid Kotlin/Quasar instrumentation bug. - val alicePlatformVersionAccordingToBob = receive(initiatedParty).unwrap { it } + val session = initiateFlow(initiatedParty) + val alicePlatformVersionAccordingToBob = session.receive().unwrap { it } return Pair( alicePlatformVersionAccordingToBob, - getFlowInfo(initiatedParty).flowVersion + session.getCounterpartyFlowInfo().flowVersion ) } } - private class PretendInitiatedCoreFlow(val initiatingParty: Party) : FlowLogic() { + private class PretendInitiatedCoreFlow(val otherSideSession: FlowSession) : FlowLogic() { @Suspendable - override fun call() = send(initiatingParty, getFlowInfo(initiatingParty).flowVersion) + override fun call() = otherSideSession.send(otherSideSession.getCounterpartyFlowInfo().flowVersion) } } \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt index b3358408bd..36cd3a6979 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -3,7 +3,6 @@ package net.corda.node.services.statemachine import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.SecureHash import net.corda.core.flows.* -import net.corda.core.identity.Party import net.corda.core.internal.InputStreamAndHash import net.corda.core.messaging.startFlow import net.corda.core.transactions.TransactionBuilder @@ -39,18 +38,19 @@ class LargeTransactionsTest { val stx = serviceHub.signInitialTransaction(tx, ourIdentity.owningKey) // Send to the other side and wait for it to trigger resolution from us. val bob = serviceHub.identityService.partyFromX500Name(BOB.name)!! - subFlow(SendTransactionFlow(bob, stx)) - receive(bob) + val bobSession = initiateFlow(bob) + subFlow(SendTransactionFlow(bobSession, stx)) + bobSession.receive() } } @InitiatedBy(SendLargeTransactionFlow::class) @Suppress("UNUSED") - class ReceiveLargeTransactionFlow(private val counterParty: Party) : FlowLogic() { + class ReceiveLargeTransactionFlow(private val otherSide: FlowSession) : FlowLogic() { @Suspendable override fun call() { - subFlow(ReceiveTransactionFlow(counterParty)) + subFlow(ReceiveTransactionFlow(otherSide)) // Unblock the other side by sending some dummy object (Unit is fine here as it's a singleton). - send(counterParty, Unit) + otherSide.send(Unit) } } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index e5dff89f39..9caff3474d 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -3,17 +3,16 @@ package net.corda.services.messaging import co.paralleluniverse.fibers.Suspendable import net.corda.client.rpc.CordaRPCClient import net.corda.core.crypto.generateKeyPair -import net.corda.core.utilities.toBase58String +import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.crypto.random63BitValue import net.corda.core.utilities.getOrThrow -import net.corda.testing.ALICE -import net.corda.testing.BOB +import net.corda.core.utilities.toBase58String import net.corda.core.utilities.unwrap import net.corda.node.internal.Node import net.corda.node.internal.StartedNode @@ -25,6 +24,8 @@ import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.RPCApi import net.corda.nodeapi.User import net.corda.nodeapi.config.SSLConfiguration +import net.corda.testing.ALICE +import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.configureTestSSL import net.corda.testing.messaging.SimpleMQClient @@ -229,12 +230,12 @@ abstract class MQSecurityTest : NodeBasedTest() { @InitiatingFlow private class SendFlow(val otherParty: Party, val payload: Any) : FlowLogic() { @Suspendable - override fun call() = send(otherParty, payload) + override fun call() = initiateFlow(otherParty).send(payload) } @InitiatedBy(SendFlow::class) - private class ReceiveFlow(val otherParty: Party) : FlowLogic() { + private class ReceiveFlow(val otherPartySession: FlowSession) : FlowLogic() { @Suspendable - override fun call() = receive(otherParty).unwrap { it } + override fun call() = otherPartySession.receive().unwrap { it } } } \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 045ebcf52f..7073632133 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -18,8 +18,8 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getOrThrow import net.corda.node.services.FlowPermissions -import net.corda.nodeapi.ServiceInfo import net.corda.node.services.transactions.SimpleNotaryService +import net.corda.nodeapi.ServiceInfo import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity @@ -151,6 +151,6 @@ class SendMessageFlow(private val message: Message) : FlowLogic>, flowFactory: (Party) -> FlowLogic<*>) { - log.warn(deprecatedFlowConstructorMessage(clientFlowClass.java)) - installCoreFlowExpectingFlowSession(clientFlowClass, { flowSession -> flowFactory(flowSession.counterparty) }) - } - - @VisibleForTesting - fun installCoreFlowExpectingFlowSession(clientFlowClass: KClass>, flowFactory: (FlowSession) -> FlowLogic<*>) { + fun installCoreFlow(clientFlowClass: KClass>, flowFactory: (FlowSession) -> FlowLogic<*>) { require(clientFlowClass.java.flowVersionAndInitiatingClass.first == 1) { "${InitiatingFlow::class.java.name}.version not applicable for core flows; their version is the node's platform version" } @@ -378,7 +371,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, private fun installCoreFlows() { - installCoreFlow(BroadcastTransactionFlow::class, ::NotifyTransactionHandler) + installCoreFlow(FinalityFlow::class, ::FinalityHandler) installCoreFlow(NotaryChangeFlow::class, ::NotaryChangeHandler) installCoreFlow(ContractUpgradeFlow.Initiator::class, ::Acceptor) installCoreFlow(SwapIdentitiesFlow::class, ::SwapIdentitiesHandler) @@ -407,10 +400,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } private fun makeCordappLoader(): CordappLoader { - val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package") - return if (scanPackage != null) { + val scanPackages = System.getProperty("net.corda.node.cordapp.scan.packages") + return if (scanPackages != null) { check(configuration.devMode) { "Package scanning can only occur in dev mode" } - CordappLoader.createDevMode(scanPackage) + CordappLoader.createDevMode(scanPackages) } else { CordappLoader.createDefault(configuration.baseDirectory) } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index d35bbb47a8..77d34308e9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -57,19 +57,22 @@ class CordappLoader private constructor(private val cordappJarPaths: List) * @param scanPackage Resolves the JARs that contain scanPackage and use them as the source for * the classpath scanning. */ - fun createDevMode(scanPackage: String): CordappLoader { - val resource = scanPackage.replace('.', '/') - val paths = this::class.java.classLoader.getResources(resource) - .asSequence() - .map { - val uri = if (it.protocol == "jar") { - (it.openConnection() as JarURLConnection).jarFileURL.toURI() - } else { - URI(it.toExternalForm().removeSuffix(resource)) + fun createDevMode(scanPackages: String): CordappLoader { + val paths = scanPackages.split(",").flatMap { scanPackage -> + val resource = scanPackage.replace('.', '/') + this::class.java.classLoader.getResources(resource) + .asSequence() + .map { + val uri = if (it.protocol == "jar") { + (it.openConnection() as JarURLConnection).jarFileURL.toURI() + } else { + URI(it.toExternalForm().removeSuffix(resource)) + } + uri.toURL() } - uri.toURL() - } - .toList() + .toList() + } + return CordappLoader(paths) } diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index a63985d653..57b6349dc2 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -1,10 +1,7 @@ package net.corda.node.services import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.AbstractStateReplacementFlow -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.ReceiveTransactionFlow -import net.corda.core.flows.StateReplacementException +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction @@ -12,15 +9,15 @@ import net.corda.core.transactions.SignedTransaction // includes us in any outside that list. Potentially just if it includes any outside that list at all. // TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming // cash without from unknown parties? -class NotifyTransactionHandler(val otherParty: Party) : FlowLogic() { +class FinalityHandler(private val sender: FlowSession) : FlowLogic() { @Suspendable override fun call() { - val stx = subFlow(ReceiveTransactionFlow(otherParty)) + val stx = subFlow(ReceiveTransactionFlow(sender)) serviceHub.recordTransactions(stx) } } -class NotaryChangeHandler(otherSide: Party) : AbstractStateReplacementFlow.Acceptor(otherSide) { +class NotaryChangeHandler(otherSideSession: FlowSession) : AbstractStateReplacementFlow.Acceptor(otherSideSession) { /** * Check the notary change proposal. * diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt index 58f08dfa63..4f2d1ba5fc 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt @@ -18,7 +18,7 @@ class FlowSessionInternal( val ourSessionId: Long, val initiatingParty: Party?, var state: FlowSessionState, - val retryable: Boolean = false) { + var retryable: Boolean = false) { val receivedMessages = ConcurrentLinkedQueue>() val fiber: FlowStateMachineImpl<*> get() = flow.stateMachine as FlowStateMachineImpl<*> @@ -30,14 +30,19 @@ class FlowSessionInternal( /** * [FlowSessionState] describes the session's state. * - * [Initiating] is pre-handshake. [Initiating.otherParty] at this point holds a [Party] corresponding to either a - * specific peer or a service. + * [Uninitiated] is pre-handshake, where no communication has happened. [Initiating.otherParty] at this point holds a + * [Party] corresponding to either a specific peer or a service. + * [Initiating] is pre-handshake, where the initiating message has been sent. * [Initiated] is post-handshake. At this point [Initiating.otherParty] will have been resolved to a specific peer * [Initiated.peerParty], and the peer's sessionId has been initialised. */ sealed class FlowSessionState { abstract val sendToParty: Party + data class Uninitiated(val otherParty: Party) : FlowSessionState() { + override val sendToParty: Party get() = otherParty + } + /** [otherParty] may be a specific peer or a service party */ data class Initiating(val otherParty: Party) : FlowSessionState() { override val sendToParty: Party get() = otherParty diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index 0a1299b591..4f6b4c3dca 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -164,6 +164,16 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable override fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession { + val sessionKey = Pair(sessionFlow, otherParty) + if (openSessions.containsKey(sessionKey)) { + throw IllegalStateException( + "Attempted to initiateFlow() twice in the same InitiatingFlow $sessionFlow for the same party " + + "$otherParty. This isn't supported in this version of Corda. Alternatively you may " + + "initiate a new flow by calling initiateFlow() in an " + + "@${InitiatingFlow::class.java.simpleName} sub-flow." + ) + } + createNewSession(otherParty, sessionFlow) val flowSession = FlowSessionImpl(otherParty) flowSession.stateMachine = this flowSession.sessionFlow = sessionFlow @@ -186,7 +196,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) val receivedSessionData: ReceivedSessionMessage = if (session == null) { - val newSession = startNewSession(otherParty, sessionFlow, payload, waitForConfirmation = true, retryable = retrySend) + val newSession = initiateSession(otherParty, sessionFlow, payload, waitForConfirmation = true, retryable = retrySend) // Only do a receive here as the session init has carried the payload receiveInternal(newSession, receiveType) } else { @@ -221,7 +231,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) if (session == null) { // Don't send the payload again if it was already piggy-backed on a session init - startNewSession(otherParty, sessionFlow, payload, waitForConfirmation = false) + initiateSession(otherParty, sessionFlow, payload, waitForConfirmation = false) } else { sendInternal(session, createSessionData(session, payload)) } @@ -308,8 +318,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, private fun createSessionData(session: FlowSessionInternal, payload: Any): SessionData { val sessionState = session.state val peerSessionId = when (sessionState) { - is FlowSessionState.Initiating -> throw IllegalStateException("We've somehow held onto an unconfirmed session: $session") is FlowSessionState.Initiated -> sessionState.peerSessionId + else -> throw IllegalStateException("We've somehow held onto a non-initiated session: $session") } return SessionData(peerSessionId, payload) } @@ -332,37 +342,53 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable private fun getConfirmedSessionIfPresent(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSessionInternal? { - return openSessions[Pair(sessionFlow, otherParty)]?.apply { - if (state is FlowSessionState.Initiating) { - // Session still initiating, wait for the confirmation - waitForConfirmation() + val session = openSessions[Pair(sessionFlow, otherParty)] ?: return null + return when (session.state) { + is FlowSessionState.Uninitiated -> null + is FlowSessionState.Initiating -> { + session.waitForConfirmation() + session } + is FlowSessionState.Initiated -> session } } @Suspendable private fun getConfirmedSession(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSessionInternal { return getConfirmedSessionIfPresent(otherParty, sessionFlow) ?: - startNewSession(otherParty, sessionFlow, null, waitForConfirmation = true) + initiateSession(otherParty, sessionFlow, null, waitForConfirmation = true) } - /** - * Creates a new session. The provided [otherParty] can be an identity of any advertised service on the network, - * and might be advertised by more than one node. Therefore we first choose a single node that advertises it - * and use its *legal identity* for communication. At the moment a single node can compose its legal identity out of - * multiple public keys, but we **don't support multiple nodes advertising the same legal identity**. - */ - @Suspendable - private fun startNewSession(otherParty: Party, - sessionFlow: FlowLogic<*>, - firstPayload: Any?, - waitForConfirmation: Boolean, - retryable: Boolean = false): FlowSessionInternal { - logger.trace { "Initiating a new session with $otherParty" } - val session = FlowSessionInternal(sessionFlow, random63BitValue(), null, FlowSessionState.Initiating(otherParty), retryable) + private fun createNewSession( + otherParty: Party, + sessionFlow: FlowLogic<*> + ) { + logger.trace { "Creating a new session with $otherParty" } + val session = FlowSessionInternal(sessionFlow, random63BitValue(), null, FlowSessionState.Uninitiated(otherParty)) openSessions[Pair(sessionFlow, otherParty)] = session - val (version, initiatingFlowClass) = sessionFlow.javaClass.flowVersionAndInitiatingClass - val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, sessionFlow.javaClass.appName, firstPayload) + } + + @Suspendable + private fun initiateSession( + otherParty: Party, + sessionFlow: FlowLogic<*>, + firstPayload: Any?, + waitForConfirmation: Boolean, + retryable: Boolean = false + ): FlowSessionInternal { + val session = openSessions[Pair(sessionFlow, otherParty)] + if (session == null) { + throw IllegalStateException("Expected an Uninitiated session for $otherParty") + } + val state = session.state + if (state !is FlowSessionState.Uninitiated) { + throw IllegalStateException("Tried to initiate a session $session, but it's already initiating/initiated") + } + logger.trace { "Initiating a new session with ${state.otherParty}" } + session.state = FlowSessionState.Initiating(state.otherParty) + session.retryable = retryable + val (version, initiatingFlowClass) = session.flow.javaClass.flowVersionAndInitiatingClass + val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, session.flow.javaClass.appName, firstPayload) sendInternal(session, sessionInit) if (waitForConfirmation) { session.waitForConfirmation() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index bf98b2ff3a..807bcc9415 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -6,6 +6,7 @@ import net.corda.core.contracts.StateRef import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.identity.CordaX500Name @@ -71,19 +72,19 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, fun commitTransaction(tx: Any, otherSide: Party) = client.commitTransaction(tx, otherSide) - override fun createServiceFlow(otherParty: Party): FlowLogic = ServiceFlow(otherParty, this) + override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = ServiceFlow(otherPartySession, this) - private class ServiceFlow(val otherSide: Party, val service: BFTNonValidatingNotaryService) : FlowLogic() { + private class ServiceFlow(val otherSideSession: FlowSession, val service: BFTNonValidatingNotaryService) : FlowLogic() { @Suspendable override fun call(): Void? { - val stx = receive(otherSide).unwrap { it } + val stx = otherSideSession.receive().unwrap { it } val signatures = commit(stx) - send(otherSide, signatures) + otherSideSession.send(signatures) return null } private fun commit(stx: FilteredTransaction): List { - val response = service.commitTransaction(stx, otherSide) + val response = service.commitTransaction(stx, otherSideSession.counterparty) when (response) { is BFTSMaRt.ClusterResponse.Error -> throw NotaryException(response.error) is BFTSMaRt.ClusterResponse.Signatures -> { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt index 6ebf61de1e..a12d565d52 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt @@ -1,16 +1,16 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.FlowSession import net.corda.core.contracts.ComponentGroupEnum import net.corda.core.flows.NotaryFlow import net.corda.core.flows.TransactionParts -import net.corda.core.identity.Party import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.utilities.unwrap -class NonValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSide, service) { +class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSideSession, service) { /** * The received transaction is not checked for contract-validity, as that would require fully * resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction @@ -21,7 +21,7 @@ class NonValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryS */ @Suspendable override fun receiveAndVerifyTx(): TransactionParts { - val parts = receive(otherSide).unwrap { + val parts = otherSideSession.receive().unwrap { when (it) { is FilteredTransaction -> { it.verify() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt index 850f8182c8..6e0916e3cc 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.transactions +import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow -import net.corda.core.identity.Party import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal @@ -16,7 +16,7 @@ class RaftNonValidatingNotaryService(override val services: ServiceHubInternal, override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider: RaftUniquenessProvider = RaftUniquenessProvider(services) - override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = NonValidatingNotaryFlow(otherParty, this) + override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this) override fun start() { uniquenessProvider.start() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt index 26fbd333b3..10da5581fe 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.transactions +import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow -import net.corda.core.identity.Party import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal @@ -16,7 +16,7 @@ class RaftValidatingNotaryService(override val services: ServiceHubInternal, ove override val timeWindowChecker: TimeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider: RaftUniquenessProvider = RaftUniquenessProvider(services) - override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = ValidatingNotaryFlow(otherParty, this) + override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this) override fun start() { uniquenessProvider.start() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt index 0df32048b3..f27d4e7478 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.transactions +import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow -import net.corda.core.identity.Party import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.nodeapi.ServiceType @@ -17,7 +17,7 @@ class SimpleNotaryService(override val services: ServiceHubInternal, override va override val timeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider = PersistentUniquenessProvider() - override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = NonValidatingNotaryFlow(otherParty, this) + override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this) override fun start() {} override fun stop() {} diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index a7ee9f1228..cf871f7f0f 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -3,7 +3,6 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.* -import net.corda.core.identity.Party import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.transactions.SignedTransaction import java.security.SignatureException @@ -14,7 +13,7 @@ import java.security.SignatureException * has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was * indeed valid. */ -class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSide, service) { +class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSideSession, service) { /** * The received transaction is checked for contract-validity, which requires fully resolving it into a * [TransactionForVerification], for which the caller also has to to reveal the whole transaction @@ -23,7 +22,7 @@ class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryServ @Suspendable override fun receiveAndVerifyTx(): TransactionParts { try { - val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false)) + val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false)) val notary = stx.notary checkNotary(notary) checkSignatures(stx) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt index 76a0c83674..e3f7289393 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt @@ -1,7 +1,7 @@ package net.corda.node.services.transactions +import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow -import net.corda.core.identity.Party import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.nodeapi.ServiceType @@ -17,7 +17,7 @@ class ValidatingNotaryService(override val services: ServiceHubInternal, overrid override val timeWindowChecker = TimeWindowChecker(services.clock) override val uniquenessProvider = PersistentUniquenessProvider() - override fun createServiceFlow(otherParty: Party): NotaryFlow.Service = ValidatingNotaryFlow(otherParty, this) + override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this) override fun start() {} override fun stop() {} diff --git a/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt b/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt index 7a67198558..bd2d072d64 100644 --- a/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt +++ b/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt @@ -4,7 +4,10 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.* +import net.corda.core.internal.copyToDirectory +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.list import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap @@ -68,21 +71,22 @@ class CordappSmokeTest { override fun call(): Pair { // This receive will kick off SendBackInitiatorFlowContext by sending a session-init with our app name. // SendBackInitiatorFlowContext will send back our context using the information from this session-init - val sessionInitContext = receive(otherParty).unwrap { it } + val session = initiateFlow(otherParty) + val sessionInitContext = session.receive().unwrap { it } // This context is taken from the session-confirm message - val sessionConfirmContext = getFlowInfo(otherParty) + val sessionConfirmContext = session.getCounterpartyFlowInfo() return Pair(sessionInitContext, sessionConfirmContext) } } @Suppress("unused") @InitiatedBy(GatherContextsFlow::class) - class SendBackInitiatorFlowContext(private val otherParty: Party) : FlowLogic() { + class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic() { @Suspendable override fun call() { // An initiated flow calling getFlowContext on its initiator will get the context from the session-init - val sessionInitContext = getFlowInfo(otherParty) - send(otherParty, sessionInitContext) + val sessionInitContext = otherPartySession.getCounterpartyFlowInfo() + otherPartySession.send(sessionInitContext) } } } diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt index 1ff641e4d4..b239ac01d6 100644 --- a/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt @@ -1,20 +1,21 @@ package net.corda.node.cordapp import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy -import net.corda.node.internal.cordapp.Cordapp +import net.corda.core.flows.InitiatingFlow import net.corda.node.internal.cordapp.CordappLoader -import org.junit.Assert +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Paths -import org.assertj.core.api.Assertions.assertThat +@InitiatingFlow class DummyFlow : FlowLogic() { override fun call() { } } @InitiatedBy(DummyFlow::class) -class LoaderTestFlow : FlowLogic() { +class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic() { override fun call() { } } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 55afe46acb..74d0d43886 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -4,10 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.* -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StateMachineRunId +import net.corda.core.flows.* import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name @@ -562,9 +559,10 @@ class TwoPartyTradeFlowTests { } else { ourIdentityAndCert } - send(buyer, TestTx(notary, price, anonymous)) + val buyerSession = initiateFlow(buyer) + buyerSession.send(TestTx(notary, price, anonymous)) return subFlow(Seller( - buyer, + buyerSession, assetToSell, price, myPartyAndCert)) @@ -572,14 +570,14 @@ class TwoPartyTradeFlowTests { } @InitiatedBy(SellerInitiator::class) - class BuyerAcceptor(private val seller: Party) : FlowLogic() { + class BuyerAcceptor(private val sellerSession: FlowSession) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - val (notary, price, anonymous) = receive(seller).unwrap { + val (notary, price, anonymous) = sellerSession.receive().unwrap { require(serviceHub.networkMapCache.isNotary(it.notaryIdentity)) { "${it.notaryIdentity} is not a notary" } it } - return subFlow(Buyer(seller, notary, price, CommercialPaper.State::class.java, anonymous)) + return subFlow(Buyer(sellerSession, notary, price, CommercialPaper.State::class.java, anonymous)) } } diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index f4b7d79de1..73b9058561 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.flows.* -import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.VaultQueryService import net.corda.core.node.services.queryBy @@ -16,10 +15,10 @@ import net.corda.core.node.services.vault.SortAttribute import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode -import net.corda.nodeapi.ServiceInfo import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.ValidatingNotaryService +import net.corda.nodeapi.ServiceInfo import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DUMMY_PROGRAM_ID @@ -50,15 +49,15 @@ class ScheduledFlowTests { val processed: Boolean = false, override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState { override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? { - if (!processed) { + return if (!processed) { val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.java, thisStateRef) - return ScheduledActivity(logicRef, creationTime) + ScheduledActivity(logicRef, creationTime) } else { - return null + null } } - override val participants: List = listOf(source, destination) + override val participants: List get() = listOf(source, destination) } class InsertInitialStateFlow(private val destination: Party) : FlowLogic() { @@ -70,7 +69,7 @@ class ScheduledFlowTests { .addOutputState(scheduledState, DUMMY_PROGRAM_ID) .addCommand(dummyCommand(ourIdentity.owningKey)) val tx = serviceHub.signInitialTransaction(builder) - subFlow(FinalityFlow(tx, setOf(ourIdentity))) + subFlow(FinalityFlow(tx)) } } @@ -92,7 +91,7 @@ class ScheduledFlowTests { .addOutputState(newStateOutput, DUMMY_PROGRAM_ID) .addCommand(dummyCommand(ourIdentity.owningKey)) val tx = serviceHub.signInitialTransaction(builder) - subFlow(FinalityFlow(tx, setOf(scheduledState.source, scheduledState.destination))) + subFlow(FinalityFlow(tx, setOf(scheduledState.destination))) } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index 3d2956d069..da94d376aa 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -2,6 +2,7 @@ package net.corda.node.services.network import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.CordaX500Name @@ -170,17 +171,18 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { override fun call(): String { println("SEND FLOW to $otherParty") println("Party key ${otherParty.owningKey.toBase58String()}") - return sendAndReceive(otherParty, "Hi!").unwrap { it } + val session = initiateFlow(otherParty) + return session.sendAndReceive("Hi!").unwrap { it } } } @InitiatedBy(SendFlow::class) - private class SendBackFlow(val otherParty: Party) : FlowLogic() { + private class SendBackFlow(val otherSideSession: FlowSession) : FlowLogic() { @Suspendable override fun call() { - println("SEND BACK FLOW to $otherParty") - println("Party key ${otherParty.owningKey.toBase58String()}") - send(otherParty, "Hello!") + println("SEND BACK FLOW to ${otherSideSession.counterparty}") + println("Party key ${otherSideSession.counterparty.owningKey.toBase58String()}") + otherSideSession.send("Hello!") } } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt deleted file mode 100644 index 84305dab78..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DataVendingServiceTests.kt +++ /dev/null @@ -1,116 +0,0 @@ -package net.corda.node.services.persistence - -import co.paralleluniverse.fibers.Suspendable -import net.corda.core.contracts.Amount -import net.corda.core.contracts.Issued -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.SendTransactionFlow -import net.corda.core.identity.Party -import net.corda.core.node.services.queryBy -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder -import net.corda.finance.USD -import net.corda.finance.contracts.asset.Cash -import net.corda.node.internal.StartedNode -import net.corda.node.services.NotifyTransactionHandler -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MEGA_CORP -import net.corda.testing.chooseIdentity -import net.corda.testing.node.MockNetwork -import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import kotlin.test.assertEquals - -/** - * Tests for the data vending service. - */ -class DataVendingServiceTests { - lateinit var mockNet: MockNetwork - - @Before - fun setup() { - mockNet = MockNetwork() - } - - @After - fun cleanUp() { - mockNet.stopNodes() - } - - @Test - fun `notify of transaction`() { - val nodes = mockNet.createSomeNodes(2) - val vaultServiceNode = nodes.partyNodes[0] - val registerNode = nodes.partyNodes[1] - val beneficiary = vaultServiceNode.info.chooseIdentity() - val deposit = registerNode.info.chooseIdentity().ref(1) - mockNet.runNetwork() - - // Generate an issuance transaction - val ptx = TransactionBuilder(null) - Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY) - - // Complete the cash transaction, and then manually relay it - val tx = registerNode.services.signInitialTransaction(ptx) - vaultServiceNode.database.transaction { - assertThat(vaultServiceNode.services.vaultQueryService.queryBy().states.isEmpty()) - - registerNode.sendNotifyTx(tx, vaultServiceNode) - - // Check the transaction is in the receiving node - val actual = vaultServiceNode.services.vaultQueryService.queryBy().states.singleOrNull() - val expected = tx.tx.outRef(0) - assertEquals(expected, actual) - } - } - - /** - * Test that invalid transactions are rejected. - */ - @Test - fun `notify failure`() { - val nodes = mockNet.createSomeNodes(2) - val vaultServiceNode = nodes.partyNodes[0] - val registerNode = nodes.partyNodes[1] - val beneficiary = vaultServiceNode.info.chooseIdentity() - val deposit = MEGA_CORP.ref(1) - mockNet.runNetwork() - - // Generate an issuance transaction - val ptx = TransactionBuilder(DUMMY_NOTARY) - Cash().generateIssue(ptx, Amount(100, Issued(deposit, USD)), beneficiary, DUMMY_NOTARY) - - // The transaction tries issuing MEGA_CORP cash, but we aren't the issuer, so it's invalid - val tx = registerNode.services.signInitialTransaction(ptx) - vaultServiceNode.database.transaction { - assertThat(vaultServiceNode.services.vaultQueryService.queryBy().states.isEmpty()) - - registerNode.sendNotifyTx(tx, vaultServiceNode) - - // Check the transaction is not in the receiving node - assertThat(vaultServiceNode.services.vaultQueryService.queryBy().states.isEmpty()) - } - } - - private fun StartedNode<*>.sendNotifyTx(tx: SignedTransaction, walletServiceNode: StartedNode<*>) { - walletServiceNode.internals.registerInitiatedFlow(InitiateNotifyTxFlow::class.java) - services.startFlow(NotifyTxFlow(walletServiceNode.info.chooseIdentity(), tx)) - mockNet.runNetwork() - } - - @InitiatingFlow - private class NotifyTxFlow(val otherParty: Party, val stx: SignedTransaction) : FlowLogic() { - @Suspendable - override fun call() = subFlow(SendTransactionFlow(otherParty, stx)) - } - - @InitiatedBy(NotifyTxFlow::class) - private class InitiateNotifyTxFlow(val otherParty: Party) : FlowLogic() { - @Suspendable - override fun call() = subFlow(NotifyTransactionHandler(otherParty)) - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index 5e9698b68b..d7b90b8880 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -37,7 +37,7 @@ class NodeSchemaServiceTest { /** * Note: this test verifies auto-scanning to register identified [MappedSchema] schemas. * By default, Driver uses the caller package for auto-scanning: - * System.setProperty("net.corda.node.cordapp.scan.package", callerPackage) + * System.setProperty("net.corda.node.cordapp.scan.packages", callerPackage) */ @Test fun `auto scanning of custom schemas for testing with Driver`() { diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index a383eb3396..64d6af905e 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -124,7 +124,7 @@ class FlowFrameworkTests { @Test fun `exception while fiber suspended`() { - node2.registerFlowFactory(ReceiveFlow::class) { SendFlow("Hello", it) } + node2.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } val flow = ReceiveFlow(node2.info.chooseIdentity()) val fiber = node1.services.startFlow(flow) as FlowStateMachineImpl // Before the flow runs change the suspend action to throw an exception @@ -143,7 +143,7 @@ class FlowFrameworkTests { @Test fun `flow restarted just after receiving payload`() { - node2.registerFlowFactory(SendFlow::class) { ReceiveFlow(it).nonTerminating() } + node2.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() } node1.services.startFlow(SendFlow("Hello", node2.info.chooseIdentity())) // We push through just enough messages to get only the payload sent @@ -152,7 +152,7 @@ class FlowFrameworkTests { node2.internals.acceptableLiveFiberCountOnStop = 1 node2.dispose() mockNet.runNetwork() - val restoredFlow = node2.restartAndGetRestoredFlow(node1) + val restoredFlow = node2.restartAndGetRestoredFlow(node1) assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello") } @@ -195,7 +195,7 @@ class FlowFrameworkTests { @Test fun `flow loaded from checkpoint will respond to messages from before start`() { - node1.registerFlowFactory(ReceiveFlow::class) { SendFlow("Hello", it) } + node1.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } node2.services.startFlow(ReceiveFlow(node1.info.chooseIdentity()).nonTerminating()) // Prepare checkpointed receive flow // Make sure the add() has finished initial processing. node2.smm.executor.flush() @@ -260,13 +260,13 @@ class FlowFrameworkTests { fun `sending to multiple parties`() { val node3 = mockNet.createNode(node1.network.myAddress) mockNet.runNetwork() - node2.registerFlowFactory(SendFlow::class) { ReceiveFlow(it).nonTerminating() } - node3.registerFlowFactory(SendFlow::class) { ReceiveFlow(it).nonTerminating() } + node2.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() } + node3.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() } val payload = "Hello World" node1.services.startFlow(SendFlow(payload, node2.info.chooseIdentity(), node3.info.chooseIdentity())) mockNet.runNetwork() - val node2Flow = node2.getSingleFlow().first - val node3Flow = node3.getSingleFlow().first + val node2Flow = node2.getSingleFlow().first + val node3Flow = node3.getSingleFlow().first assertThat(node2Flow.receivedPayloads[0]).isEqualTo(payload) assertThat(node3Flow.receivedPayloads[0]).isEqualTo(payload) @@ -294,8 +294,8 @@ class FlowFrameworkTests { mockNet.runNetwork() val node2Payload = "Test 1" val node3Payload = "Test 2" - node2.registerFlowFactory(ReceiveFlow::class) { SendFlow(node2Payload, it) } - node3.registerFlowFactory(ReceiveFlow::class) { SendFlow(node3Payload, it) } + node2.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow(node2Payload, it) } + node3.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow(node3Payload, it) } val multiReceiveFlow = ReceiveFlow(node2.info.chooseIdentity(), node3.info.chooseIdentity()).nonTerminating() node1.services.startFlow(multiReceiveFlow) node1.internals.acceptableLiveFiberCountOnStop = 1 @@ -420,10 +420,11 @@ class FlowFrameworkTests { @Suspendable override fun call() { // Kick off the flow on the other side ... - send(otherParty, 1) + val session = initiateFlow(otherParty) + session.send(1) // ... then pause this one until it's received the session-end message from the other side receivedOtherFlowEnd.acquire() - sendAndReceive(otherParty, 2) + session.sendAndReceive(2) } } @@ -543,14 +544,14 @@ class FlowFrameworkTests { ) } - private class ConditionalExceptionFlow(val otherParty: Party, val sendPayload: Any) : FlowLogic() { + private class ConditionalExceptionFlow(val otherPartySession: FlowSession, val sendPayload: Any) : FlowLogic() { @Suspendable override fun call() { - val throwException = receive(otherParty).unwrap { it } + val throwException = otherPartySession.receive().unwrap { it } if (throwException) { throw MyFlowException("Throwing exception as requested") } - send(otherParty, sendPayload) + otherPartySession.send(sendPayload) } } @@ -559,7 +560,7 @@ class FlowFrameworkTests { @InitiatingFlow class AskForExceptionFlow(val otherParty: Party, val throwException: Boolean) : FlowLogic() { @Suspendable - override fun call(): String = sendAndReceive(otherParty, throwException).unwrap { it } + override fun call(): String = initiateFlow(otherParty).sendAndReceive(throwException).unwrap { it } } class RetryOnExceptionFlow(val otherParty: Party) : FlowLogic() { @@ -581,7 +582,7 @@ class FlowFrameworkTests { @Test fun `serialisation issue in counterparty`() { - node2.registerFlowFactory(ReceiveFlow::class) { SendFlow(NonSerialisableData(1), it) } + node2.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow(NonSerialisableData(1), it) } val result = node1.services.startFlow(ReceiveFlow(node2.info.chooseIdentity())).resultFuture mockNet.runNetwork() assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { @@ -651,7 +652,7 @@ class FlowFrameworkTests { @Test fun `customised client flow`() { - val receiveFlowFuture = node2.registerFlowFactory(SendFlow::class) { ReceiveFlow(it) } + val receiveFlowFuture = node2.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it) } node1.services.startFlow(CustomSendFlow("Hello", node2.info.chooseIdentity())).resultFuture mockNet.runNetwork() assertThat(receiveFlowFuture.getOrThrow().receivedPayloads).containsOnly("Hello") @@ -668,7 +669,7 @@ class FlowFrameworkTests { @Test fun `upgraded initiating flow`() { - node2.registerFlowFactory(UpgradedFlow::class, initiatedFlowVersion = 1) { SendFlow("Old initiated", it) } + node2.registerFlowFactory(UpgradedFlow::class, initiatedFlowVersion = 1) { InitiatedSendFlow("Old initiated", it) } val result = node1.services.startFlow(UpgradedFlow(node2.info.chooseIdentity())).resultFuture mockNet.runNetwork() assertThat(receivedSessionMessages).startsWith( @@ -684,13 +685,13 @@ class FlowFrameworkTests { fun `upgraded initiated flow`() { node2.registerFlowFactory(SendFlow::class, initiatedFlowVersion = 2) { UpgradedFlow(it) } val initiatingFlow = SendFlow("Old initiating", node2.info.chooseIdentity()) - node1.services.startFlow(initiatingFlow) + val flowInfo = node1.services.startFlow(initiatingFlow).resultFuture mockNet.runNetwork() assertThat(receivedSessionMessages).startsWith( node1 sent sessionInit(SendFlow::class, flowVersion = 1, payload = "Old initiating") to node2, node2 sent sessionConfirm(flowVersion = 2) to node1 ) - assertThat(initiatingFlow.getFlowInfo(node2.info.chooseIdentity()).flowVersion).isEqualTo(2) + assertThat(flowInfo.get().flowVersion).isEqualTo(2) } @Test @@ -736,6 +737,23 @@ class FlowFrameworkTests { assertThat(result.getOrThrow()).isEqualTo("HelloHello") } + @Test + fun `double initiateFlow throws`() { + val future = node1.services.startFlow(DoubleInitiatingFlow()).resultFuture + mockNet.runNetwork() + assertThatExceptionOfType(IllegalStateException::class.java) + .isThrownBy { future.getOrThrow() } + .withMessageContaining("Attempted to initiateFlow() twice") + } + + @InitiatingFlow + private class DoubleInitiatingFlow : FlowLogic() { + @Suspendable + override fun call() { + initiateFlow(serviceHub.myInfo.chooseIdentity()) + initiateFlow(serviceHub.myInfo.chooseIdentity()) + } + } //////////////////////////////////////////////////////////////////////////////////////////////////////////// //region Helpers @@ -754,16 +772,7 @@ class FlowFrameworkTests { return smm.findStateMachines(P::class.java).single() } - @Deprecated("Use registerFlowFactoryExpectingFlowSession() instead") private inline fun > StartedNode<*>.registerFlowFactory( - initiatingFlowClass: KClass>, - initiatedFlowVersion: Int = 1, - noinline flowFactory: (Party) -> P): CordaFuture

- { - return registerFlowFactoryExpectingFlowSession(initiatingFlowClass, initiatedFlowVersion, { flowFactory(it.counterparty) }) - } - - private inline fun > StartedNode<*>.registerFlowFactoryExpectingFlowSession( initiatingFlowClass: KClass>, initiatedFlowVersion: Int = 1, noinline flowFactory: (FlowSession) -> P): CordaFuture

@@ -858,13 +867,25 @@ class FlowFrameworkTests { } @InitiatingFlow - private open class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic() { + private open class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic() { init { require(otherParties.isNotEmpty()) } @Suspendable - override fun call() = otherParties.forEach { send(it, payload) } + override fun call(): FlowInfo { + val flowInfos = otherParties.map { + val session = initiateFlow(it) + session.send(payload) + session.getCounterpartyFlowInfo() + }.toList() + return flowInfos.first() + } + } + + private open class InitiatedSendFlow(val payload: Any, val otherPartySession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() = otherPartySession.send(payload) } private interface CustomInterface @@ -890,7 +911,7 @@ class FlowFrameworkTests { @Suspendable override fun call() { progressTracker.currentStep = START_STEP - receivedPayloads = otherParties.map { receive(it).unwrap { it } } + receivedPayloads = otherParties.map { initiateFlow(it).receive().unwrap { it } } progressTracker.currentStep = RECEIVED_STEP if (nonTerminating) { Fiber.park() @@ -903,26 +924,54 @@ class FlowFrameworkTests { } } - @InitiatingFlow - private class SendAndReceiveFlow(val otherParty: Party, val payload: Any) : FlowLogic() { - @Suspendable - override fun call(): Any = sendAndReceive(otherParty, payload).unwrap { it } - } + private class InitiatedReceiveFlow(val otherPartySession: FlowSession) : FlowLogic() { + object START_STEP : ProgressTracker.Step("Starting") + object RECEIVED_STEP : ProgressTracker.Step("Received") + + override val progressTracker: ProgressTracker = ProgressTracker(START_STEP, RECEIVED_STEP) + private var nonTerminating: Boolean = false + @Transient + var receivedPayloads: List = emptyList() - private class InlinedSendFlow(val payload: String, val otherParty: Party) : FlowLogic() { @Suspendable - override fun call() = send(otherParty, payload) + override fun call() { + progressTracker.currentStep = START_STEP + receivedPayloads = listOf(otherPartySession.receive().unwrap { it }) + progressTracker.currentStep = RECEIVED_STEP + if (nonTerminating) { + Fiber.park() + } + } + + fun nonTerminating(): InitiatedReceiveFlow { + nonTerminating = true + return this + } } @InitiatingFlow - private class PingPongFlow(val otherParty: Party, val payload: Long) : FlowLogic() { + private class SendAndReceiveFlow(val otherParty: Party, val payload: Any, val otherPartySession: FlowSession? = null) : FlowLogic() { + constructor(otherPartySession: FlowSession, payload: Any) : this(otherPartySession.counterparty, payload, otherPartySession) + @Suspendable + override fun call(): Any = (otherPartySession ?: initiateFlow(otherParty)).sendAndReceive(payload).unwrap { it } + } + + private class InlinedSendFlow(val payload: String, val otherPartySession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() = otherPartySession.send(payload) + } + + @InitiatingFlow + private class PingPongFlow(val otherParty: Party, val payload: Long, val otherPartySession: FlowSession? = null) : FlowLogic() { + constructor(otherPartySession: FlowSession, payload: Long) : this(otherPartySession.counterparty, payload, otherPartySession) @Transient var receivedPayload: Long? = null @Transient var receivedPayload2: Long? = null @Suspendable override fun call() { - receivedPayload = sendAndReceive(otherParty, payload).unwrap { it } - receivedPayload2 = sendAndReceive(otherParty, payload + 1).unwrap { it } + val session = otherPartySession ?: initiateFlow(otherParty) + receivedPayload = session.sendAndReceive(payload).unwrap { it } + receivedPayload2 = session.sendAndReceive(payload + 1).unwrap { it } } } @@ -950,17 +999,18 @@ class FlowFrameworkTests { class Waiter(val stx: SignedTransaction, val otherParty: Party) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - send(otherParty, stx) + val otherPartySession = initiateFlow(otherParty) + otherPartySession.send(stx) return waitForLedgerCommit(stx.id) } } - class Committer(val otherParty: Party, val throwException: (() -> Exception)? = null) : FlowLogic() { + class Committer(val otherPartySession: FlowSession, val throwException: (() -> Exception)? = null) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - val stx = receive(otherParty).unwrap { it } + val stx = otherPartySession.receive().unwrap { it } if (throwException != null) throw throwException.invoke() - return subFlow(FinalityFlow(stx, setOf(otherParty))).single() + return subFlow(FinalityFlow(stx, setOf(otherPartySession.counterparty))) } } } @@ -969,7 +1019,8 @@ class FlowFrameworkTests { private class VaultQueryFlow(val stx: SignedTransaction, val otherParty: Party) : FlowLogic>>() { @Suspendable override fun call(): List> { - send(otherParty, stx) + val otherPartySession = initiateFlow(otherParty) + otherPartySession.send(stx) // hold onto reference here to force checkpoint of vaultQueryService and thus // prove it is registered as a tokenizableService in the node val vaultQuerySvc = serviceHub.vaultQueryService @@ -979,27 +1030,29 @@ class FlowFrameworkTests { } @InitiatingFlow(version = 2) - private class UpgradedFlow(val otherParty: Party) : FlowLogic>() { + private class UpgradedFlow(val otherParty: Party, val otherPartySession: FlowSession? = null) : FlowLogic>() { + constructor(otherPartySession: FlowSession) : this(otherPartySession.counterparty, otherPartySession) @Suspendable override fun call(): Pair { - val received = receive(otherParty).unwrap { it } - val otherFlowVersion = getFlowInfo(otherParty).flowVersion + val otherPartySession = this.otherPartySession ?: initiateFlow(otherParty) + val received = otherPartySession.receive().unwrap { it } + val otherFlowVersion = otherPartySession.getCounterpartyFlowInfo().flowVersion return Pair(received, otherFlowVersion) } } - private class SingleInlinedSubFlow(val otherParty: Party) : FlowLogic() { + private class SingleInlinedSubFlow(val otherPartySession: FlowSession) : FlowLogic() { @Suspendable override fun call() { - val payload = receive(otherParty).unwrap { it } - subFlow(InlinedSendFlow(payload + payload, otherParty)) + val payload = otherPartySession.receive().unwrap { it } + subFlow(InlinedSendFlow(payload + payload, otherPartySession)) } } - private class DoubleInlinedSubFlow(val otherParty: Party) : FlowLogic() { + private class DoubleInlinedSubFlow(val otherPartySession: FlowSession) : FlowLogic() { @Suspendable override fun call() { - subFlow(SingleInlinedSubFlow(otherParty)) + subFlow(SingleInlinedSubFlow(otherPartySession)) } } diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index 76dbece304..630255cd71 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -106,7 +106,9 @@ private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash. } @StartableByRPC -class AttachmentDemoFlow(val otherSide: Party, val notary: Party, val hash: SecureHash.SHA256) : FlowLogic() { +class AttachmentDemoFlow(private val otherSide: Party, + private val notary: Party, + private val attachId: SecureHash.SHA256) : FlowLogic() { object SIGNING : ProgressTracker.Step("Signing transaction") @@ -116,16 +118,16 @@ class AttachmentDemoFlow(val otherSide: Party, val notary: Party, val hash: Secu override fun call(): SignedTransaction { // Create a trivial transaction with an output that describes the attachment, and the attachment itself val ptx = TransactionBuilder(notary) - .addOutputState(AttachmentContract.State(hash), ATTACHMENT_PROGRAM_ID) + .addOutputState(AttachmentContract.State(attachId), ATTACHMENT_PROGRAM_ID) .addCommand(AttachmentContract.Command, ourIdentity.owningKey) - .addAttachment(hash) + .addAttachment(attachId) progressTracker.currentStep = SIGNING // Send the transaction to the other recipient val stx = serviceHub.signInitialTransaction(ptx) - return subFlow(FinalityFlow(stx, setOf(otherSide))).single() + return subFlow(FinalityFlow(stx, setOf(otherSide))) } } diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt index f477701a0b..b7d931d99d 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt @@ -13,7 +13,7 @@ import kotlin.test.assertTrue class BankOfCordaHttpAPITest { @Test fun `issuer flow via Http`() { - driver(dsl = { + driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = { val bigCorpNodeFuture = startNode(providedName = BIGCORP_LEGAL_NAME) val nodeBankOfCordaFuture = startNode(providedName = BOC.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))) val (nodeBankOfCorda) = listOf(nodeBankOfCordaFuture, bigCorpNodeFuture).map { it.getOrThrow() } diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index 34c659fa58..fdd53846ff 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -18,7 +18,7 @@ import org.junit.Test class BankOfCordaRPCClientTest { @Test fun `issuer flow via RPC`() { - driver(dsl = { + driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = { val bocManager = User("bocManager", "password1", permissions = setOf( startFlowPermission())) val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet()) diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index ddf3d488ee..a28d299d24 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -4,11 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Command import net.corda.core.crypto.MerkleTreeException import net.corda.core.crypto.TransactionSignature -import net.corda.core.flows.FlowException -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.StartableByRPC -import net.corda.core.identity.Party +import net.corda.core.flows.* import net.corda.core.internal.ThreadBox import net.corda.core.node.ServiceHub import net.corda.core.node.services.CordaService @@ -46,17 +42,17 @@ import kotlin.collections.set object NodeInterestRates { // DOCSTART 2 @InitiatedBy(RatesFixFlow.FixSignFlow::class) - class FixSignHandler(private val otherParty: Party) : FlowLogic() { + class FixSignHandler(private val otherPartySession: FlowSession) : FlowLogic() { @Suspendable override fun call() { - val request = receive(otherParty).unwrap { it } + val request = otherPartySession.receive().unwrap { it } val oracle = serviceHub.cordaService(Oracle::class.java) - send(otherParty, oracle.sign(request.ftx)) + otherPartySession.send(oracle.sign(request.ftx)) } } @InitiatedBy(RatesFixFlow.FixQueryFlow::class) - class FixQueryHandler(private val otherParty: Party) : FlowLogic() { + class FixQueryHandler(private val otherPartySession: FlowSession) : FlowLogic() { object RECEIVED : ProgressTracker.Step("Received fix request") object SENDING : ProgressTracker.Step("Sending fix response") @@ -64,12 +60,12 @@ object NodeInterestRates { @Suspendable override fun call() { - val request = receive(otherParty).unwrap { it } + val request = otherPartySession.receive().unwrap { it } progressTracker.currentStep = RECEIVED val oracle = serviceHub.cordaService(Oracle::class.java) val answers = oracle.query(request.queries) progressTracker.currentStep = SENDING - send(otherParty, answers) + otherPartySession.send(answers) } } // DOCEND 2 diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt index f57bb5f14e..4e2f09331a 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt @@ -1,12 +1,7 @@ package net.corda.irs.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC -import net.corda.core.identity.AbstractParty -import net.corda.core.identity.Party +import net.corda.core.flows.* import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker import net.corda.finance.contracts.DealState @@ -50,22 +45,19 @@ object AutoOfferFlow { require(serviceHub.networkMapCache.notaryIdentities.isNotEmpty()) { "No notary nodes registered" } val notary = serviceHub.networkMapCache.notaryIdentities.first() // TODO We should pass the notary as a parameter to the flow, not leave it to random choice. // need to pick which ever party is not us - val otherParty = notUs(dealToBeOffered.participants).map { serviceHub.identityService.partyFromAnonymous(it) }.requireNoNulls().single() + val otherParty = serviceHub.excludeMe(serviceHub.groupAbstractPartyByWellKnownParty(dealToBeOffered.participants)).keys.single() progressTracker.currentStep = DEALING + val session = initiateFlow(otherParty) val instigator = Instigator( - otherParty, + session, AutoOffer(notary, dealToBeOffered), progressTracker.getChildProgressTracker(DEALING)!! ) val stx = subFlow(instigator) return stx } - - private fun notUs(parties: List): List { - return parties.filter { ourIdentity != it } - } } @InitiatedBy(Requester::class) - class AutoOfferAcceptor(otherParty: Party) : Acceptor(otherParty) + class AutoOfferAcceptor(otherSideSession: FlowSession) : Acceptor(otherSideSession) } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt index f020df2552..e44cc055cc 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt @@ -3,10 +3,7 @@ package net.corda.irs.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* import net.corda.core.crypto.TransactionSignature -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.SchedulableFlow +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.serialization.CordaSerializable @@ -28,7 +25,7 @@ object FixingFlow { * who does what in the flow. */ @InitiatedBy(FixingRoleDecider::class) - class Fixer(override val otherParty: Party) : TwoPartyDealFlow.Secondary() { + class Fixer(override val otherSideSession: FlowSession) : TwoPartyDealFlow.Secondary() { private lateinit var txState: TransactionState<*> private lateinit var deal: FixableDealState @@ -91,7 +88,7 @@ object FixingFlow { * is just the "side" of the flow run by the party with the floating leg as a way of deciding who * does what in the flow. */ - class Floater(override val otherParty: Party, + class Floater(override val otherSideSession: FlowSession, override val payload: FixingSession, override val progressTracker: ProgressTracker = TwoPartyDealFlow.Primary.tracker()) : TwoPartyDealFlow.Primary() { @Suppress("UNCHECKED_CAST") @@ -141,7 +138,8 @@ object FixingFlow { val fixing = FixingSession(ref, fixableDeal.oracle) val counterparty = serviceHub.identityService.partyFromAnonymous(parties[1]) ?: throw IllegalStateException("Cannot resolve floater party") // Start the Floater which will then kick-off the Fixer - subFlow(Floater(counterparty, fixing)) + val session = initiateFlow(counterparty) + subFlow(Floater(session, fixing)) } } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt index 9276444df7..b4646ff7be 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt @@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable @@ -97,8 +98,9 @@ open class RatesFixFlow(protected val tx: TransactionBuilder, class FixQueryFlow(val fixOf: FixOf, val oracle: Party) : FlowLogic() { @Suspendable override fun call(): Fix { + val oracleSession = initiateFlow(oracle) // TODO: add deadline to receive - val resp = sendAndReceive>(oracle, QueryRequest(listOf(fixOf))) + val resp = oracleSession.sendAndReceive>(QueryRequest(listOf(fixOf))) return resp.unwrap { val fix = it.first() @@ -114,9 +116,10 @@ open class RatesFixFlow(protected val tx: TransactionBuilder, val partialMerkleTx: FilteredTransaction) : FlowLogic() { @Suspendable override fun call(): TransactionSignature { - val resp = sendAndReceive(oracle, SignRequest(partialMerkleTx)) + val oracleSession = initiateFlow(oracle) + val resp = oracleSession.sendAndReceive(SignRequest(partialMerkleTx)) return resp.unwrap { sig -> - check(oracle.owningKey.isFulfilledBy(listOf(sig.by))) + check(oracleSession.counterparty.owningKey.isFulfilledBy(listOf(sig.by))) tx.toWireTransaction().checkSignature(sig) sig } diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt index 92f0bc52f8..1e36a3bc86 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt @@ -6,6 +6,8 @@ import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party +import net.corda.core.flows.* +import net.corda.core.node.NodeInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap @@ -21,10 +23,10 @@ object UpdateBusinessDayFlow { data class UpdateBusinessDayMessage(val date: LocalDate) @InitiatedBy(Broadcast::class) - private class UpdateBusinessDayHandler(val otherParty: Party) : FlowLogic() { + private class UpdateBusinessDayHandler(val otherPartySession: FlowSession) : FlowLogic() { @Suspendable override fun call() { - val message = receive(otherParty).unwrap { it } + val message = otherPartySession.receive().unwrap { it } (serviceHub.clock as TestClock).updateDate(message.date) } } @@ -63,7 +65,7 @@ object UpdateBusinessDayFlow { @Suspendable private fun doNextRecipient(recipient: Party) { - send(recipient, UpdateBusinessDayMessage(date)) + initiateFlow(recipient).send(UpdateBusinessDayMessage(date)) } } } \ No newline at end of file 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 6e3c665303..4642412d9b 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 @@ -6,6 +6,7 @@ import com.fasterxml.jackson.module.kotlin.readValue import net.corda.client.jackson.JacksonSupport import net.corda.core.contracts.StateAndRef import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatingFlow import net.corda.core.identity.Party @@ -143,11 +144,14 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten class StartDealFlow(val otherParty: Party, val payload: AutoOffer) : FlowLogic() { @Suspendable - override fun call(): SignedTransaction = subFlow(Instigator(otherParty, payload)) + override fun call(): SignedTransaction { + val session = initiateFlow(otherParty) + return subFlow(Instigator(session, payload)) + } } @InitiatedBy(StartDealFlow::class) - class AcceptDealFlow(otherParty: Party) : Acceptor(otherParty) + class AcceptDealFlow(otherSession: FlowSession) : Acceptor(otherSession) val acceptDealFlows: Observable = node2.internals.registerInitiatedFlow(AcceptDealFlow::class.java) diff --git a/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt index bb843910e1..8d9f7284d4 100644 --- a/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt +++ b/samples/network-visualiser/src/test/kotlin/net/corda/netmap/simulation/IRSSimulationTest.kt @@ -11,7 +11,9 @@ class IRSSimulationTest { LogHelper.setLevel("+messages") // FIXME: Don't manipulate static state in tests. val sim = IRSSimulation(false, false, null) val future = sim.start() - while (!future.isDone) sim.iterate() + while (!future.isDone) { + sim.iterate() + } future.getOrThrow() } } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/IRSTradeFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/IRSTradeFlow.kt index d3816e680e..0856383a0d 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/IRSTradeFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/IRSTradeFlow.kt @@ -1,10 +1,7 @@ package net.corda.vega.flows import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction @@ -33,28 +30,29 @@ object IRSTradeFlow { val offer = IRSState(swap, buyer, seller) logger.info("Handshake finished, sending IRS trade offer message") - val otherPartyAgreeFlag = sendAndReceive(otherParty, OfferMessage(notary, offer)).unwrap { it } + val session = initiateFlow(otherParty) + val otherPartyAgreeFlag = session.sendAndReceive(OfferMessage(notary, offer)).unwrap { it } require(otherPartyAgreeFlag) return subFlow(TwoPartyDealFlow.Instigator( - otherParty, + session, TwoPartyDealFlow.AutoOffer(notary, offer))) } } @InitiatedBy(Requester::class) - class Receiver(private val replyToParty: Party) : FlowLogic() { + class Receiver(private val replyToSession: FlowSession) : FlowLogic() { @Suspendable override fun call() { logger.info("IRSTradeFlow receiver started") logger.info("Handshake finished, awaiting IRS trade offer") - val offer = receive(replyToParty).unwrap { it } + val offer = replyToSession.receive().unwrap { it } // Automatically agree - in reality we'd vet the offer message require(serviceHub.networkMapCache.notaryIdentities.contains(offer.notary)) - send(replyToParty, true) - subFlow(TwoPartyDealFlow.Acceptor(replyToParty)) + replyToSession.send(true) + subFlow(TwoPartyDealFlow.Acceptor(replyToSession)) } } } diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt index b5c9c5ca79..852777bdfe 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/SimmFlow.kt @@ -12,6 +12,7 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.flows.* import net.corda.core.flows.AbstractStateReplacementFlow.Proposal +import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria @@ -57,6 +58,7 @@ object SimmFlow { : FlowLogic>() { constructor(otherParty: Party, valuationDate: LocalDate) : this(otherParty, valuationDate, null) lateinit var notary: Party + lateinit var otherPartySession: FlowSession @Suspendable override fun call(): RevisionedState { @@ -68,6 +70,7 @@ object SimmFlow { val trades = serviceHub.vaultQueryService.queryBy(criteria).states val portfolio = Portfolio(trades, valuationDate) + otherPartySession = initiateFlow(otherParty) if (existing == null) { agreePortfolio(portfolio) } else { @@ -86,18 +89,24 @@ object SimmFlow { val parties = Pair(ourIdentity, otherParty) val portfolioState = PortfolioState(portfolio.refs, parties, valuationDate) - send(otherParty, OfferMessage(notary, portfolioState, existing?.ref, valuationDate)) + otherPartySession.send(OfferMessage(notary, portfolioState, existing?.ref, valuationDate)) logger.info("Awaiting two party deal acceptor") - subFlow(TwoPartyDealFlow.Acceptor(otherParty)) + subFlow(TwoPartyDealFlow.Acceptor(otherPartySession)) } @Suspendable private fun updatePortfolio(portfolio: Portfolio, stateAndRef: StateAndRef) { // Receive is a hack to ensure other side is ready - sendAndReceive(otherParty, OfferMessage(notary, stateAndRef.state.data, existing?.ref, valuationDate)) + otherPartySession.sendAndReceive(OfferMessage(notary, stateAndRef.state.data, existing?.ref, valuationDate)) logger.info("Updating portfolio") val update = PortfolioState.Update(portfolio = portfolio.refs) - subFlow(StateRevisionFlow.Requester(stateAndRef, update)) + subFlow(StateRevisionFlowRequester(otherPartySession, stateAndRef, update)) + } + + private class StateRevisionFlowRequester(val session: FlowSession, stateAndRef: StateAndRef>, update: T) : StateRevisionFlow.Requester(stateAndRef, update) { + override fun getParticipantSessions(): List>> { + return listOf(session to listOf(session.counterparty)) + } } @Suspendable @@ -110,7 +119,7 @@ object SimmFlow { require(valuer != null) { "Valuer party must be known to this node" } val valuation = agreeValuation(portfolio, valuationDate, valuer!!) val update = PortfolioState.Update(valuation = valuation) - return subFlow(StateRevisionFlow.Requester(stateRef, update)).state.data + return subFlow(StateRevisionFlowRequester(otherPartySession, stateRef, update)).state.data } @Suspendable @@ -165,7 +174,7 @@ object SimmFlow { // TODO: In the real world, this would be tolerance aware for different types @Suspendable private inline fun agree(data: T): Boolean { - val valid = receive(otherParty).unwrap { + val valid = otherPartySession.receive().unwrap { logger.trace("Comparing --> $it") logger.trace("with -------> $data") if (it is InitialMarginTriple && data is InitialMarginTriple) { @@ -175,7 +184,7 @@ object SimmFlow { } } logger.trace("valid is $valid") - send(otherParty, valid) + otherPartySession.send(valid) return valid } } @@ -184,16 +193,16 @@ object SimmFlow { * Receives and validates a portfolio and comes to consensus over the portfolio initial margin using SIMM. */ @InitiatedBy(Requester::class) - class Receiver(val replyToParty: Party) : FlowLogic() { + class Receiver(val replyToSession: FlowSession) : FlowLogic() { lateinit var offer: OfferMessage @Suspendable override fun call() { - val criteria = LinearStateQueryCriteria(participants = listOf(replyToParty)) + val criteria = LinearStateQueryCriteria(participants = listOf(replyToSession.counterparty)) val trades = serviceHub.vaultQueryService.queryBy(criteria).states val portfolio = Portfolio(trades) logger.info("SimmFlow receiver started") - offer = receive(replyToParty).unwrap { it } + offer = replyToSession.receive().unwrap { it } if (offer.stateRef == null) { agreePortfolio(portfolio) } else { @@ -205,8 +214,8 @@ object SimmFlow { @Suspendable private fun agree(data: Any): Boolean { - send(replyToParty, data) - return receive(replyToParty).unwrap { it } + replyToSession.send(data) + return replyToSession.receive().unwrap { it } } /** @@ -287,17 +296,17 @@ object SimmFlow { require(offer.dealBeingOffered.portfolio == portfolio.refs) val seller = TwoPartyDealFlow.Instigator( - replyToParty, + replyToSession, TwoPartyDealFlow.AutoOffer(offer.notary, offer.dealBeingOffered)) - logger.info("Starting two party deal initiator with: ${replyToParty.name}") + logger.info("Starting two party deal initiator with: ${replyToSession.counterparty.name}") return subFlow(seller) } @Suspendable private fun updatePortfolio(portfolio: Portfolio) { logger.info("Handshake finished, awaiting Simm update") - send(replyToParty, Ack) // Hack to state that this party is ready. - subFlow(object : StateRevisionFlow.Receiver(replyToParty) { + replyToSession.send(Ack) // Hack to state that this party is ready. + subFlow(object : StateRevisionFlow.Receiver(replyToSession) { override fun verifyProposal(stx:SignedTransaction, proposal: Proposal) { super.verifyProposal(stx, proposal) if (proposal.modification.portfolio != portfolio.refs) throw StateReplacementException() @@ -310,7 +319,7 @@ object SimmFlow { val portfolio = serviceHub.vaultQueryService.queryBy(VaultQueryCriteria(stateRefs = stateRef.state.data.portfolio)).states.toPortfolio() val valuer = serviceHub.identityService.partyFromAnonymous(stateRef.state.data.valuer) ?: throw IllegalStateException("Unknown valuer party ${stateRef.state.data.valuer}") val valuation = agreeValuation(portfolio, offer.valuationDate, valuer) - subFlow(object : StateRevisionFlow.Receiver(replyToParty) { + subFlow(object : StateRevisionFlow.Receiver(replyToSession) { override fun verifyProposal(stx: SignedTransaction, proposal: Proposal) { super.verifyProposal(stx, proposal) if (proposal.modification.valuation != valuation) throw StateReplacementException() diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt index 10b2d4ee58..49918f85c3 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/flows/StateRevisionFlow.kt @@ -3,8 +3,8 @@ package net.corda.vega.flows import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.StateAndRef import net.corda.core.flows.AbstractStateReplacementFlow +import net.corda.core.flows.FlowSession import net.corda.core.flows.StateReplacementException -import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.seconds import net.corda.vega.contracts.RevisionedState @@ -14,7 +14,7 @@ import net.corda.vega.contracts.RevisionedState * on the update between two parties. */ object StateRevisionFlow { - class Requester(curStateRef: StateAndRef>, + open class Requester(curStateRef: StateAndRef>, updatedData: T) : AbstractStateReplacementFlow.Instigator, RevisionedState, T>(curStateRef, updatedData) { override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx { val state = originalState.state.data @@ -22,16 +22,12 @@ object StateRevisionFlow { tx.setTimeWindow(serviceHub.clock.instant(), 30.seconds) val privacySalt = PrivacySalt() tx.setPrivacySalt(privacySalt) - val stx = serviceHub.signInitialTransaction(tx) - val participantKeys = state.participants.map { it.owningKey } - // TODO: We need a much faster way of finding our key in the transaction - val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single() - return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey) + return AbstractStateReplacementFlow.UpgradeTx(stx) } } - open class Receiver(otherParty: Party) : AbstractStateReplacementFlow.Acceptor(otherParty) { + open class Receiver(initiatingSession: FlowSession) : AbstractStateReplacementFlow.Acceptor(initiatingSession) { override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal) { val proposedTx = stx.tx val state = proposal.stateRef diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index 68900d95fe..329e144b2b 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -9,14 +9,10 @@ import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CommercialPaperSchemaV1 import net.corda.node.services.FlowPermissions.Companion.startFlowPermission -import net.corda.nodeapi.ServiceInfo import net.corda.node.services.transactions.SimpleNotaryService +import net.corda.nodeapi.ServiceInfo import net.corda.nodeapi.User -import net.corda.testing.BOC -import net.corda.testing.DUMMY_BANK_A -import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.chooseIdentity +import net.corda.testing.* import net.corda.testing.driver.poll import net.corda.testing.node.NodeBasedTest import net.corda.traderdemo.flow.BuyerFlow diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt index 2c4cffbaf8..70cee50154 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/BuyerFlow.kt @@ -3,6 +3,7 @@ package net.corda.traderdemo.flow import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy import net.corda.core.identity.Party import net.corda.core.internal.Emoji @@ -16,7 +17,7 @@ import net.corda.traderdemo.TransactionGraphSearch import java.util.* @InitiatedBy(SellerFlow::class) -class BuyerFlow(val otherParty: Party) : FlowLogic() { +class BuyerFlow(private val otherSideSession: FlowSession) : FlowLogic() { object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset") @@ -27,11 +28,11 @@ class BuyerFlow(val otherParty: Party) : FlowLogic() { progressTracker.currentStep = STARTING_BUY // Receive the offered amount and automatically agree to it (in reality this would be a longer negotiation) - val amount = receive>(otherParty).unwrap { it } + val amount = otherSideSession.receive>().unwrap { it } require(serviceHub.networkMapCache.notaryIdentities.isNotEmpty()) { "No notary nodes registered" } val notary: Party = serviceHub.networkMapCache.notaryIdentities.first() val buyer = TwoPartyTradeFlow.Buyer( - otherParty, + otherSideSession, notary, amount, CommercialPaper.State::class.java) diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt index f89aafd091..297f033ca1 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt @@ -54,7 +54,7 @@ class CommercialPaperIssueFlow(private val amount: Amount, // Sign it as ourselves. val stx = serviceHub.signInitialTransaction(tx) - subFlow(FinalityFlow(stx)).single() + subFlow(FinalityFlow(stx)) } // Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works. @@ -62,10 +62,9 @@ class CommercialPaperIssueFlow(private val amount: Amount, val builder = TransactionBuilder(notary) CommercialPaper().generateMove(builder, issuance.tx.outRef(0), recipient) val stx = serviceHub.signInitialTransaction(builder) - subFlow(FinalityFlow(stx)).single() + subFlow(FinalityFlow(stx)) } return move } - } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt index f0f5f91bcb..890885227f 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt @@ -45,9 +45,10 @@ class SellerFlow(private val otherParty: Party, progressTracker.currentStep = TRADING // Send the offered amount. - send(otherParty, amount) + val session = initiateFlow(otherParty) + session.send(amount) val seller = TwoPartyTradeFlow.Seller( - otherParty, + session, commercialPaper, amount, cpOwner, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt index dd5c6c081a..0420e61b54 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -225,6 +225,7 @@ fun rpcDriver( initialiseSerialization: Boolean = true, networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, + extraCordappPackagesToScan: List = emptyList(), dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( @@ -236,7 +237,8 @@ fun rpcDriver( useTestClock = useTestClock, networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, - startNodesInProcess = startNodesInProcess + startNodesInProcess = startNodesInProcess, + extraCordappPackagesToScan = extraCordappPackagesToScan ) ), coerce = { it }, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index e9522358e1..6b1484e260 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -310,6 +310,7 @@ fun driver( initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy, startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, + extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: DriverDSLExposedInterface.() -> A ): A { return genericDriver( @@ -319,9 +320,10 @@ fun driver( systemProperties = systemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, + isDebug = isDebug, networkMapStartStrategy = networkMapStartStrategy, startNodesInProcess = startNodesInProcess, - isDebug = isDebug + extraCordappPackagesToScan = extraCordappPackagesToScan ), coerce = { it }, dsl = dsl, @@ -355,7 +357,8 @@ data class DriverParameters( val useTestClock: Boolean = false, val initialiseSerialization: Boolean = true, val networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), - val startNodesInProcess: Boolean = false + val startNodesInProcess: Boolean = false, + val extraCordappPackagesToScan: List = emptyList() ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) @@ -366,6 +369,7 @@ data class DriverParameters( fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) fun setNetworkMapStartStrategy(networkMapStartStrategy: NetworkMapStartStrategy) = copy(networkMapStartStrategy = networkMapStartStrategy) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) + fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) } /** @@ -579,14 +583,15 @@ class DriverDSL( val useTestClock: Boolean, val isDebug: Boolean, val networkMapStartStrategy: NetworkMapStartStrategy, - val startNodesInProcess: Boolean + val startNodesInProcess: Boolean, + val extraCordappPackagesToScan: List ) : DriverDSLInternalInterface { private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort() private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null override val shutdownManager get() = _shutdownManager!! - private val callerPackage = getCallerPackage() + private val packagesToScanString = (extraCordappPackagesToScan + getCallerPackage()).joinToString(",") class State { val processes = ArrayList>() @@ -786,7 +791,7 @@ class DriverDSL( _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) // We set this property so that in-process nodes find cordapps. Out-of-process nodes need this passed in when started. - System.setProperty("net.corda.node.cordapp.scan.package", callerPackage) + System.setProperty("net.corda.node.cordapp.scan.packages", packagesToScanString) if (networkMapStartStrategy.startDedicated) { startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes. } @@ -840,7 +845,7 @@ class DriverDSL( } } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, callerPackage) + val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, packagesToScanString) registerProcess(processFuture) return processFuture.flatMap { process -> val processDeathFuture = poll(executorService, "process death") { @@ -904,7 +909,7 @@ class DriverDSL( quasarJarPath: String, debugPort: Int?, overriddenSystemProperties: Map, - callerPackage: String + packagesToScanString: String ): CordaFuture { val processFuture = executorService.fork { log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}") @@ -914,7 +919,7 @@ class DriverDSL( val systemProperties = overriddenSystemProperties + mapOf( "name" to nodeConf.myLegalName, "visualvm.display.name" to "corda-${nodeConf.myLegalName}", - "net.corda.node.cordapp.scan.package" to callerPackage, + "net.corda.node.cordapp.scan.packages" to packagesToScanString, "java.io.tmpdir" to System.getProperty("java.io.tmpdir") // Inherit from parent process ) // See experimental/quasar-hook/README.md for how to generate. diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index fd18c1abcf..00fa22724f 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -76,6 +76,7 @@ fun verifierDriver( useTestClock: Boolean = false, networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, + extraCordappPackagesToScan: List = emptyList(), dsl: VerifierExposedDSLInterface.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( @@ -87,7 +88,8 @@ fun verifierDriver( useTestClock = useTestClock, networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, - startNodesInProcess = startNodesInProcess + startNodesInProcess = startNodesInProcess, + extraCordappPackagesToScan = extraCordappPackagesToScan ) ), coerce = { it },