CORDA-577: FlowSession porting (#1530)

* Throw exception if a flow is initiated twice for the same Party

* Chunk of porting

* Need ReceiveTransactionFlow

(cherry picked from commit 774383e)

* Notaries compile

* TwoPartyTrade

* SimmFlow & StateRevisionFlow

(cherry picked from commit da602b1)

* TwoPArtyDealFlow regulator send

* installCoreFlow

* IRSTradeFlow
UpdateBusinessDayFlow
RatesFixFlow
NodeInterestRates

(cherry picked from commit 6c8d314)

* Added recordTransaction parameter to ReceiveTransactionFlow

* Some Tests, Flows

* Fixed typo in record tx param

* more things

* Fix CollectSignatures

* FlowFrameworkTests

(cherry picked from commit 2c50bc3)

* Fix TwoPartyTradeFlow

* CustomVaultQuery

(cherry picked from commit 48f88e8)

* FlowsInJavaTest

* WorkflowTransactionBuildTutorial

* PersistentNetworkMapCacheTest

* FlowCookBookJava

(cherry picked from commit 9b48114)

* Fix RatesFixFlow

* Fix TwoPartyDealFlow to get signature of initiating side

* Integration tests

(cherry picked from commit dbcd965)

* CordappSmokeTest

(cherry picked from commit d19cbd6)

* Inlined FinalityFlow

* Updated uses of FinalityFlow

* ContractUpgradeFlowTest passes

* CollectSignaturesFlow refactor

(cherry picked from commit 5e7b1a7)

* Check that we are not the recipient of cash

* Fix Simm demo

* WorkflowTransactionBuildTutorialTest

* Fix CashPaymentFlowTests

* ScheduledFlowTests

* FlowFrameworkTests

* Add cordappPackagesToScan Driver param

* FinalityFlowTests

* Fix LoaderTestFlow

* NodeMonitorModelTest

* BankOfCordaRPCClientTest

* rename to extraCordappPackagesToScan

* Fixed broken merge

* BankOfCordaHttpAPITest

* Fix CollectSignaturesFlow

* Fix annotation on DummyFlow to stop warning

* Fix TraderDemoTest

* Review feedback

* Doc improvements and minor changes

* Address some PR comments

* Looping regulators into the FinalityFlow broadcast rather than sending separately in TwoPartyDealFlow.

* Add Uninitiated FlowState

* Add test for double initiateFlow exception

* Some more s&r victims

* FlowSession utilities (#1562)

* Merge fix

* CollectSignatureFlow can handle several signing keys

* Actually handle several signing keys

* update kdoc

* Correct SignTransactionFlow error message

* Create deprecated flows package

* Add internal deprecated flows

* Reverted FinalityFlow to auto-broadcast all tx participants

* Move the deprecated packages into another PR
This commit is contained in:
Andras Slemmer 2017-09-21 12:12:25 +01:00 committed by josecoll
parent 78500205df
commit 33421bdd44
95 changed files with 956 additions and 1068 deletions

View File

@ -52,7 +52,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
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<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>(),

View File

@ -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<Party>,
class Send(val otherSideSessions: Set<FlowSession>,
val tx: WireTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
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<AbstractParty, PartyAndCertificate?> = identities
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap()
otherSides.forEach { otherSide ->
val requestedIdentities: List<AbstractParty> = sendAndReceive<List<AbstractParty>>(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<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(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<PartyAndCertificate?> = 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<Unit>() {
class Receive(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
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<List<AbstractParty>>(otherSide).unwrap { it }
val allIdentities = otherSideSession.receive<List<AbstractParty>>().unwrap { it }
val unknownIdentities = allIdentities.filter { serviceHub.identityService.partyFromAnonymous(it) == null }
progressTracker.currentStep = RECEIVING_CERTIFICATES
val missingIdentities = sendAndReceive<List<PartyAndCertificate>>(otherSide, unknownIdentities)
val missingIdentities = otherSideSession.sendAndReceive<List<PartyAndCertificate>>(unknownIdentities)
// Batch verify the identities we've received, so we know they're all correct before we start storing them in
// the identity service

View File

@ -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<LinkedHashMap<Party, AnonymousParty>>() {
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<Party, AnonymousParty>()
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<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
val otherSession = initiateFlow(otherParty)
val anonymousOtherSide = otherSession.sendAndReceive<PartyAndCertificate>(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
}

View File

@ -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<Unit>() {
constructor(otherSide: Party) : this(otherSide, false)
class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEnabled: Boolean) : FlowLogic<Unit>() {
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<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
otherSideSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, confidentialIdentity)
}
}
}

View File

@ -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<Boolean>() {
@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<Boolean>(otherSide).unwrap { it }
return session.receive<Boolean>().unwrap { it }
}
}
@InitiatedBy(IdentitySyncFlowTests.Initiator::class)
class Receive(val otherSide: Party): FlowLogic<Unit>() {
class Receive(val otherSideSession: FlowSession): FlowLogic<Unit>() {
@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)
}
}
}

View File

@ -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<PublicKey>, 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<T> {
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<PublicKey>, stx: SignedTransaction): List<TransactionSignature> {
// 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<Pair<FlowSession, List<AbstractParty>>> {
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<Pair<FlowSession, List<AbstractParty>>>, stx: SignedTransaction): List<TransactionSignature> {
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<AbstractParty>, 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<TransactionSignature>(party, proposal).unwrap {
check(party.owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" }
subFlow(SendTransactionFlow(session, stx))
return session.sendAndReceive<TransactionSignature>(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<in T>(val otherSide: Party,
abstract class Acceptor<in T>(val initiatingSession: FlowSession,
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
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<Proposal<T>> = receive(otherSide)
val maybeProposal: UntrustworthyData<Proposal<T>> = initiatingSession.receive()
maybeProposal.unwrap {
verifyProposal(stx, it)
}
@ -166,7 +162,7 @@ abstract class AbstractStateReplacementFlow {
progressTracker.currentStep = APPROVING
val mySignature = sign(stx)
val swapSignatures = sendAndReceive<List<TransactionSignature>>(otherSide, mySignature)
val swapSignatures = initiatingSession.sendAndReceive<List<TransactionSignature>>(mySignature)
// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.
val allSignatures = swapSignatures.unwrap { signatures ->

View File

@ -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<Party>) : FlowLogic<Unit>() {
@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))
}
}
}

View File

@ -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<FlowSession>,
val myOptionalKeys: Iterable<PublicKey>?,
override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic<SignedTransaction>() {
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, null, progressTracker)
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, sessionsToCollectFrom: Collection<FlowSession>, 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<PublicKey>): List<Pair<Party, PublicKey>> = 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<PublicKey>) : FlowLogic<List<TransactionSignature>>() {
constructor(partiallySignedTx: SignedTransaction, session: FlowSession, vararg signingKeys: PublicKey) :
this(partiallySignedTx, session, listOf(*signingKeys))
@Suspendable
override fun call(): List<TransactionSignature> {
// 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<TransactionSignature>(counterparty).unwrap {
require(signingKey.isFulfilledBy(it.by)) { "Not signed by the required signing key." }
it
session.send(signingKeys)
return session.receive<List<TransactionSignature>>().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<SignedTransaction>() {
* class Responder(val otherPartySession: FlowSession): FlowLogic<SignedTransaction>() {
* @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<SignedTransaction>() {
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<PublicKey>(otherParty).unwrap {
val signingKeys = otherSideSession.receive<List<PublicKey>>().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<PublicKey>) {
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}"
}
}
}

View File

@ -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<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
class Acceptor(otherSide: FlowSession) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(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)
}

View File

@ -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<SignedTransaction>,
@InitiatingFlow
class FinalityFlow(val transaction: SignedTransaction,
private val extraRecipients: Set<Party>,
override val progressTracker: ProgressTracker) : FlowLogic<List<SignedTransaction>>() {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : 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<SignedTransaction>() {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : 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<SignedTransaction>,
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<SignedTransaction> {
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<Pair<SignedTransaction, Set<Party>>> = 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<Party>) {
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<SignedTransaction>,
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<Party> {
// 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<Party> {
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<AbstractParty> {
return ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants }
}
private fun resolveDependenciesOf(signedTransactions: Iterable<SignedTransaction>): List<Pair<SignedTransaction, LedgerTransaction>> {
// 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<ContractState>? = 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
}
}

View File

@ -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.

View File

@ -137,11 +137,17 @@ abstract class FlowLogic<out T> {
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true)
}
@Suspendable
internal fun <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true)
}
@Suspendable
internal inline fun <reified R : Any> FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true)
}
/**
* Suspends until the specified [otherParty] sends us a message of type [R].
*

View File

@ -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

View File

@ -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<SignedTransaction>,
recipients: Set<Party>,
progressTracker: ProgressTracker) : FinalityFlow(transactions, recipients, progressTracker) {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(listOf(transaction), extraParticipants, tracker())
override fun lookupParties(ltx: LedgerTransaction): Set<Party> = emptySet()
}

View File

@ -43,7 +43,7 @@ class NotaryChangeFlow<out T : ContractState>(
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] */

View File

@ -65,16 +65,17 @@ class NotaryFlow {
}
val response = try {
val session = initiateFlow(notaryParty)
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
subFlow(SendTransactionWithRetry(notaryParty, stx))
receive<List<TransactionSignature>>(notaryParty)
subFlow(SendTransactionWithRetry(session, stx))
session.receive<List<TransactionSignature>>()
} 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<FetchDataFlow.Request> {
return sendAndReceiveWithRetry(otherSide, payload)
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
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<Void?>() {
abstract class Service(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic<Void?>() {
@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))
}
}
}

View File

@ -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<SignedTransaction>() {
class ReceiveTransactionFlow(private val otherSideSession: FlowSession,
private val checkSufficientSignatures: Boolean) : FlowLogic<SignedTransaction>() {
/** 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<SignedTransaction>(otherParty).unwrap {
subFlow(ResolveTransactionsFlow(it, otherParty))
return otherSideSession.receive<SignedTransaction>().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<T>(otherParty))` in java.
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherParty: Party) : FlowLogic<@JvmSuppressWildcards List<StateAndRef<T>>>() {
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherSideSession: FlowSession) : FlowLogic<@JvmSuppressWildcards List<StateAndRef<T>>>() {
@Suspendable
override fun call(): List<StateAndRef<T>> {
return receive<List<StateAndRef<T>>>(otherParty).unwrap {
subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherParty))
return otherSideSession.receive<List<StateAndRef<T>>>().unwrap {
subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherSideSession))
it
}
}

View File

@ -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<StateAndRef<*>>) : DataVendingFlow(otherSide, stateAndRefs)
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic<Void?>() {
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
@Suspendable
protected open fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any) = sendAndReceive<FetchDataFlow.Request>(otherSide, payload)
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(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

View File

@ -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<T : NamedByHash, in W : Any>(
protected val requests: Set<SecureHash>,
protected val otherSide: Party,
protected val otherSideSession: FlowSession,
protected val dataType: DataType) : FlowLogic<FetchDataFlow.Result<T>>() {
@CordaSerializable
@ -72,7 +72,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
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<T : NamedByHash, in W : Any>(
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<List<W>>(otherSide, Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it }
maybeItems += otherSideSession.sendAndReceive<List<W>>(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<T : NamedByHash, in W : Any>(
* attachments are saved to local storage automatically.
*/
class FetchAttachmentsFlow(requests: Set<SecureHash>,
otherSide: Party) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
otherSide: FlowSession) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid)
@ -171,7 +171,7 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
* 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<SecureHash>, otherSide: Party) :
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide, DataType.TRANSACTION) {
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)

View File

@ -272,3 +272,5 @@ annotation class VisibleForTesting
@Suppress("UNCHECKED_CAST")
fun <T, U : T> uncheckedCast(obj: T) = obj as U
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }

View File

@ -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<SecureHash>,
private val otherSide: Party) : FlowLogic<List<SignedTransaction>>() {
private val otherSide: FlowSession) : FlowLogic<List<SignedTransaction>>() {
/**
* 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<SecureHash>,
// 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)

View File

@ -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<PublicKey>, ignoreUnrecognisedParties: Boolean): Map<Party, List<PublicKey>> =
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<PublicKey>): Map<Party, List<PublicKey>> = 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<AbstractParty>, ignoreUnrecognisedParties: Boolean): Map<Party, List<AbstractParty>> {
val partyToPublicKey: Iterable<Pair<Party, AbstractParty>> = 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<AbstractParty>): Map<Party, List<AbstractParty>> {
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 <T> excludeMe(map: Map<Party, T>): Map<Party, T> = 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 <T> excludeNotary(map: Map<Party, T>, stx: SignedTransaction): Map<Party, T> = map.filterKeys { it != stx.notary }
}

View File

@ -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<Void?>
abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?>
}
/**

View File

@ -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<PublicKey>): List<String>
private fun getMissingSignatures(): Set<PublicKey> {
/**
* Return the [PublicKey]s for which we still need signatures.
*/
fun getMissingSigners(): Set<PublicKey> {
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?)

View File

@ -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<String> {
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;
}
}

View File

@ -162,12 +162,15 @@ class AttachmentTests {
@InitiatingFlow
private class InitiatingFetchAttachmentsFlow(val otherSide: Party, val hashes: Set<SecureHash>) : FlowLogic<FetchDataFlow.Result<Attachment>>() {
@Suspendable
override fun call(): FetchDataFlow.Result<Attachment> = subFlow(FetchAttachmentsFlow(hashes, otherSide))
override fun call(): FetchDataFlow.Result<Attachment> {
val session = initiateFlow(otherSide)
return subFlow(FetchAttachmentsFlow(hashes, session))
}
}
@InitiatedBy(InitiatingFetchAttachmentsFlow::class)
private class FetchAttachmentsResponse(val otherSide: Party) : FlowLogic<Void?>() {
private class FetchAttachmentsResponse(val otherSideSession: FlowSession) : FlowLogic<Void?>() {
@Suspendable
override fun call() = subFlow(TestDataVendingFlow(otherSide))
override fun call() = subFlow(TestDataVendingFlow(otherSideSession))
}
}

View File

@ -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<MockNetwork.MockNode>
lateinit var c: StartedNode<MockNetwork.MockNode>
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<SignedTransaction>() {
class Initiator(private val state: DummyContract.MultiOwnerState, private val otherParty: Party) : FlowLogic<SignedTransaction>() {
@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<Party, AnonymousParty>) : FlowLogic<SignedTransaction>() {
class Responder(private val initiatingSession: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val state = receive<DummyContract.MultiOwnerState>(otherParty).unwrap { it }
val state = initiatingSession.receive<DummyContract.MultiOwnerState>().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<SignedTransaction>() {
class Initiator(private val state: DummyContract.MultiOwnerState) : FlowLogic<SignedTransaction>() {
@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<SignedTransaction>() {
@Suspendable override fun call(): SignedTransaction {
val flow = object : SignTransactionFlow(otherParty) {
class Responder(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
@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<IllegalArgumentException>("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)

View File

@ -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<Party>) : FlowLogic<List<SignedTransaction>>() {
class FinalityInvoker(private val transaction: SignedTransaction,
private val extraRecipients: Set<Party>) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): List<SignedTransaction> = subFlow(FinalityFlow(transaction, extraRecipients))
override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients))
}
}

View File

@ -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)

View File

@ -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<MockNetwork.MockNode>
lateinit var nodeB: StartedNode<MockNetwork.MockNode>
lateinit var nodeC: StartedNode<MockNetwork.MockNode>
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)
}
}

View File

@ -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<FetchDataFlow.Request> {
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
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)
}
}
}

View File

@ -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<List<SignedTransaction>>() {
constructor(txHashes: Set<SecureHash>, 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<List<SignedTransaction>>() {
constructor(txHashes: Set<SecureHash>, 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<SignedTransaction> {
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<Void?>() {
private class TestResponseFlow(val otherSideSession: FlowSession) : FlowLogic<Void?>() {
@Suspendable
override fun call() = subFlow(TestDataVendingFlow(otherSide))
override fun call() = subFlow(TestDataVendingFlow(otherSideSession))
}
}

View File

@ -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<Unit>() {
private class ServerLogic(private val clientSession: FlowSession, private val sendData: Boolean) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
if (sendData) {
subFlow(TestDataVendingFlow(client))
subFlow(TestDataVendingFlow(clientSession))
}
receive<String>(client).unwrap { assertEquals("ping one", it) }
sendAndReceive<String>(client, "pong").unwrap { assertEquals("ping two", it) }
clientSession.receive<String>().unwrap { assertEquals("ping one", it) }
clientSession.sendAndReceive<String>("pong").unwrap { assertEquals("ping two", it) }
}
}
@ -100,9 +101,9 @@ class AttachmentSerializationTest {
internal val server = server.info.chooseIdentity()
@Suspendable
internal fun communicate() {
sendAndReceive<String>(server, "ping one").unwrap { assertEquals("pong", it) }
send(server, "ping two")
internal fun communicate(serverSession: FlowSession) {
serverSession.sendAndReceive<String>("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)

View File

@ -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<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()

View File

@ -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<Integer> packet1 = receive(Integer.class, counterparty);
UntrustworthyData<Integer> 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<Boolean> packet2 = sendAndReceive(Boolean.class, counterparty, "You can send and receive any class!");
UntrustworthyData<Boolean> 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<Object> packet3 = receive(Object.class, regulator);
FlowSession regulatorSession = initiateFlow(regulator);
regulatorSession.send(new Object());
UntrustworthyData<Object> 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<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(counterparty));
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<DummyState>(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<Party> additionalParties = ImmutableSet.of(regulator);
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(ImmutableList.of(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).get(0);
Set<Party> 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<Void> {
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
/*------------------------------

View File

@ -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<Void?> = MyValidatingNotaryFlow(otherParty, this)
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = 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

View File

@ -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<AbstractCashFlow.Result> {
val topupRequest = TopupRequest(issueToParty, issueToPartyRef, notaryParty)
return sendAndReceive<List<AbstractCashFlow.Result>>(issuerBankParty, topupRequest).unwrap { it }
return initiateFlow(issuerBankParty).sendAndReceive<List<AbstractCashFlow.Result>>(topupRequest).unwrap { it }
}
}
@InitiatedBy(TopupIssuanceRequester::class)
class TopupIssuer(val otherParty: Party) : FlowLogic<List<SignedTransaction>>() {
class TopupIssuer(val otherPartySession: FlowSession) : FlowLogic<List<SignedTransaction>>() {
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<SignedTransaction> {
progressTracker.currentStep = AWAITING_REQUEST
val topupRequest = receive<TopupRequest>(otherParty).unwrap {
val topupRequest = otherPartySession.receive<TopupRequest>().unwrap {
it
}
@ -122,7 +119,7 @@ object TopupIssuerFlow {
return@map txn.stx
}
send(otherParty, txns)
otherPartySession.send(txns)
return txns
}
// DOCEND TopupIssuer

View File

@ -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<Unit>() {
class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty: Party, val regulator: Party) : FlowLogic<Unit>() {
/**---------------------------------
* 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<Int> = receive<Int>(counterparty)
val packet1: UntrustworthyData<Int> = counterpartySession.receive<Int>()
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<Boolean> = sendAndReceive<Boolean>(counterparty, "You can send and receive any class!")
val packet2: UntrustworthyData<Boolean> = counterpartySession.sendAndReceive<Boolean>("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<Any> = receive<Any>(regulator)
val regulatorSession = initiateFlow(regulator)
regulatorSession.send(Any())
val packet3: UntrustworthyData<Any> = regulatorSession.receive<Any>()
// 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<DummyState>(counterparty))
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(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<Party> = 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<Unit>() {
class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
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<Any>(counterparty).unwrap { data -> data }
val string: String = sendAndReceive<String>(counterparty, 99).unwrap { data -> data }
send(counterparty, true)
val any: Any = counterpartySession.receive<Any>().unwrap { data -> data }
val string: String = counterpartySession.sendAndReceive<String>(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<DummyState>().single()

View File

@ -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<Issued<Currency>>,
val quoteCurrencyAmount: Amount<Issued<Currency>>,
val baseCurrencyBuyer: Party,
val baseCurrencySeller: Party) : FlowLogic<SecureHash>() {
class ForeignExchangeFlow(private val tradeId: String,
private val baseCurrencyAmount: Amount<Issued<Currency>>,
private val quoteCurrencyAmount: Amount<Issued<Currency>>,
private val counterparty: Party,
private val weAreBaseCurrencySeller: Boolean) : FlowLogic<SecureHash>() {
@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<Cash.State>(remoteRequestWithNotary.owner))
val theirOutputStates = receive<List<Cash.State>>(remoteRequestWithNotary.owner).unwrap {
val counterpartySession = initiateFlow(counterparty)
counterpartySession.send(remoteRequestWithNotary)
val theirInputStates = subFlow(ReceiveStateAndRefFlow<Cash.State>(counterpartySession))
val theirOutputStates = counterpartySession.receive<List<Cash.State>>().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<TransactionSignature>(remoteRequestWithNotary.owner).unwrap {
// Allow counterparty to access our data to resolve the transaction.
subFlow(SendTransactionFlow(counterpartySession, signedTransaction))
val allPartySignedTx = counterpartySession.receive<TransactionSignature>().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<Unit>() {
class ForeignExchangeRemoteFlow(private val source: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// Initial receive from remote party
val request = receive<FxRequest>(source).unwrap {
val request = source.receive<FxRequest>().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<Unit>() {
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<Unit>() {
// 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<Unit>() {
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.
}

View File

@ -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<TransactionSignature>(newState.source, selfSignedTx).unwrap {
val session = initiateFlow(newState.source)
val allPartySignedTx = session.sendAndReceive<TransactionSignature>(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<Unit>() {
class RecordCompletionFlow(private val sourceSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// DOCSTART 3
// First we receive the verdict transaction signed by their single key
val completeTx = receive<SignedTransaction>(source).unwrap {
val completeTx = sourceSession.receive<SignedTransaction>().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<Unit>() {
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<Unit>() {
// 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.
}

View File

@ -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

View File

@ -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

View File

@ -27,9 +27,9 @@ abstract class AbstractCashFlow<out T>(override val progressTracker: ProgressTra
}
@Suspendable
protected fun finaliseTx(participants: Set<Party>, tx: SignedTransaction, message: String) {
protected fun finaliseTx(tx: SignedTransaction, extraParticipants: Set<Party>, message: String): SignedTransaction {
try {
subFlow(FinalityFlow(tx, participants))
return subFlow(FinalityFlow(tx, extraParticipants))
} catch (e: NotaryException) {
throw CashException(message, e)
}

View File

@ -44,9 +44,10 @@ class CashExitFlow(private val amount: Amount<Currency>,
@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<Currency>,
// 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

View File

@ -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<Currency>,
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)
}

View File

@ -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<Currency>, val recipient: Party, val anonymous: Boolean, val issuerConstraint: Set<Party> = emptySet()) : AbstractRequest(amount)
}
class PaymentRequest(amount: Amount<Currency>,
val recipient: Party,
val anonymous: Boolean,
val issuerConstraint: Set<Party> = emptySet()) : AbstractRequest(amount)
}

View File

@ -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<SecureHash>(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<U>(override val progressTracker: ProgressTracker = Secondary.tracker(),
val regulators: List<Party> = emptyList()) : FlowLogic<SignedTransaction>() {
val regulators: Set<Party> = emptySet()) : FlowLogic<SignedTransaction>() {
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<U> {
progressTracker.currentStep = RECEIVING
// Wait for a trade request to come in on our pre-provided session ID.
val handshake = receive<Handshake<U>>(otherParty)
val handshake = otherSideSession.receive<Handshake<U>>()
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<AutoOffer>() {
override fun validateHandshake(handshake: Handshake<AutoOffer>): Handshake<AutoOffer> {

View File

@ -55,10 +55,10 @@ object TwoPartyTradeFlow {
val payToIdentity: PartyAndCertificate
)
open class Seller(val otherParty: Party,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>,
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<OwnableState>,
private val price: Amount<Currency>,
private val myParty: PartyAndCertificate, // TODO Left because in tests it's used to pass anonymous party.
override val progressTracker: ProgressTracker = Seller.tracker()) : FlowLogic<SignedTransaction>() {
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<ContractState> = (stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data })
val states: Iterable<ContractState> = 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<Currency>,
private val typeToBuy: Class<out OwnableState>,
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
constructor(otherParty: Party, notary: Party, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>) :
this(otherParty, notary, acceptablePrice, typeToBuy, true)
constructor(otherSideSession: FlowSession, notary: Party, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>) :
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<StateAndRef<OwnableState>, SellerTradeInfo> {
val assetForSale = subFlow(ReceiveStateAndRefFlow<OwnableState>(otherParty)).single()
return assetForSale to receive<SellerTradeInfo>(otherParty).unwrap {
val assetForSale = subFlow(ReceiveStateAndRefFlow<OwnableState>(sellerSession)).single()
return assetForSale to sellerSession.receive<SellerTradeInfo>().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)

View File

@ -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

View File

@ -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()
}

View File

@ -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<String>() {
@Suspendable
override fun call(): String = receive<String>(otherParty).unwrap { it }
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
}
@InitiatedBy(ReceiveFlow::class)
open class SendClassFlow(val otherParty: Party) : FlowLogic<Unit>() {
open class SendClassFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@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)
}

View File

@ -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<Int, Int> {
// Execute receive() outside of the Pair constructor to avoid Kotlin/Quasar instrumentation bug.
val alicePlatformVersionAccordingToBob = receive<Int>(initiatedParty).unwrap { it }
val session = initiateFlow(initiatedParty)
val alicePlatformVersionAccordingToBob = session.receive<Int>().unwrap { it }
return Pair(
alicePlatformVersionAccordingToBob,
getFlowInfo(initiatedParty).flowVersion
session.getCounterpartyFlowInfo().flowVersion
)
}
}
private class PretendInitiatedCoreFlow(val initiatingParty: Party) : FlowLogic<Unit>() {
private class PretendInitiatedCoreFlow(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() = send(initiatingParty, getFlowInfo(initiatingParty).flowVersion)
override fun call() = otherSideSession.send(otherSideSession.getCounterpartyFlowInfo().flowVersion)
}
}

View File

@ -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<Unit>(bob)
val bobSession = initiateFlow(bob)
subFlow(SendTransactionFlow(bobSession, stx))
bobSession.receive<Unit>()
}
}
@InitiatedBy(SendLargeTransactionFlow::class) @Suppress("UNUSED")
class ReceiveLargeTransactionFlow(private val counterParty: Party) : FlowLogic<Unit>() {
class ReceiveLargeTransactionFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@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)
}
}

View File

@ -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<Unit>() {
@Suspendable
override fun call() = send(otherParty, payload)
override fun call() = initiateFlow(otherParty).send(payload)
}
@InitiatedBy(SendFlow::class)
private class ReceiveFlow(val otherParty: Party) : FlowLogic<Any>() {
private class ReceiveFlow(val otherPartySession: FlowSession) : FlowLogic<Any>() {
@Suspendable
override fun call() = receive<Any>(otherParty).unwrap { it }
override fun call() = otherPartySession.receive<Any>().unwrap { it }
}
}

View File

@ -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<SignedTransactio
val signedTx = serviceHub.signInitialTransaction(txBuilder)
progressTracker.currentStep = FINALISING_TRANSACTION
return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker())).single()
return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker()))
}
}

View File

@ -36,8 +36,8 @@ import net.corda.core.utilities.debug
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProvider
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.NotifyTransactionHandler
import net.corda.node.services.api.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
@ -360,15 +360,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
* compatibility [flowFactory] provides a second parameter which is the platform version of the initiating party.
* @suppress
*/
@Deprecated("Use installCoreFlowExpectingFlowSession() instead")
@VisibleForTesting
fun installCoreFlow(clientFlowClass: KClass<out FlowLogic<*>>, flowFactory: (Party) -> FlowLogic<*>) {
log.warn(deprecatedFlowConstructorMessage(clientFlowClass.java))
installCoreFlowExpectingFlowSession(clientFlowClass, { flowSession -> flowFactory(flowSession.counterparty) })
}
@VisibleForTesting
fun installCoreFlowExpectingFlowSession(clientFlowClass: KClass<out FlowLogic<*>>, flowFactory: (FlowSession) -> FlowLogic<*>) {
fun installCoreFlow(clientFlowClass: KClass<out FlowLogic<*>>, 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)
}

View File

@ -57,19 +57,22 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
* @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)
}

View File

@ -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<Unit>() {
class FinalityHandler(private val sender: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val stx = subFlow(ReceiveTransactionFlow(otherParty))
val stx = subFlow(ReceiveTransactionFlow(sender))
serviceHub.recordTransactions(stx)
}
}
class NotaryChangeHandler(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Party>(otherSide) {
class NotaryChangeHandler(otherSideSession: FlowSession) : AbstractStateReplacementFlow.Acceptor<Party>(otherSideSession) {
/**
* Check the notary change proposal.
*

View File

@ -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<ReceivedSessionMessage<*>>()
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

View File

@ -164,6 +164,16 @@ class FlowStateMachineImpl<R>(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<R>(override val id: StateMachineRunId,
logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." }
val session = getConfirmedSessionIfPresent(otherParty, sessionFlow)
val receivedSessionData: ReceivedSessionMessage<SessionData> = 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<R>(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<R>(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<R>(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()

View File

@ -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<Void?> = ServiceFlow(otherParty, this)
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = ServiceFlow(otherPartySession, this)
private class ServiceFlow(val otherSide: Party, val service: BFTNonValidatingNotaryService) : FlowLogic<Void?>() {
private class ServiceFlow(val otherSideSession: FlowSession, val service: BFTNonValidatingNotaryService) : FlowLogic<Void?>() {
@Suspendable
override fun call(): Void? {
val stx = receive<FilteredTransaction>(otherSide).unwrap { it }
val stx = otherSideSession.receive<FilteredTransaction>().unwrap { it }
val signatures = commit(stx)
send(otherSide, signatures)
otherSideSession.send(signatures)
return null
}
private fun commit(stx: FilteredTransaction): List<DigitalSignature> {
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 -> {

View File

@ -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<Any>(otherSide).unwrap {
val parts = otherSideSession.receive<Any>().unwrap {
when (it) {
is FilteredTransaction -> {
it.verify()

View File

@ -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()

View File

@ -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()

View File

@ -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() {}

View File

@ -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)

View File

@ -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() {}

View File

@ -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<FlowInfo, FlowInfo> {
// 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<FlowInfo>(otherParty).unwrap { it }
val session = initiateFlow(otherParty)
val sessionInitContext = session.receive<FlowInfo>().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<Unit>() {
class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@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)
}
}
}

View File

@ -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<Unit>() {
override fun call() { }
}
@InitiatedBy(DummyFlow::class)
class LoaderTestFlow : FlowLogic<Unit>() {
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
override fun call() { }
}

View File

@ -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<SignedTransaction>() {
class BuyerAcceptor(private val sellerSession: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val (notary, price, anonymous) = receive<TestTx>(seller).unwrap {
val (notary, price, anonymous) = sellerSession.receive<TestTx>().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))
}
}

View File

@ -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<AbstractParty> = listOf(source, destination)
override val participants: List<Party> get() = listOf(source, destination)
}
class InsertInitialStateFlow(private val destination: Party) : FlowLogic<Unit>() {
@ -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)))
}
}

View File

@ -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<String>(otherParty, "Hi!").unwrap { it }
val session = initiateFlow(otherParty)
return session.sendAndReceive<String>("Hi!").unwrap { it }
}
}
@InitiatedBy(SendFlow::class)
private class SendBackFlow(val otherParty: Party) : FlowLogic<Unit>() {
private class SendBackFlow(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
@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!")
}
}
}

View File

@ -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<Cash.State>().states.isEmpty())
registerNode.sendNotifyTx(tx, vaultServiceNode)
// Check the transaction is in the receiving node
val actual = vaultServiceNode.services.vaultQueryService.queryBy<Cash.State>().states.singleOrNull()
val expected = tx.tx.outRef<Cash.State>(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<Cash.State>().states.isEmpty())
registerNode.sendNotifyTx(tx, vaultServiceNode)
// Check the transaction is not in the receiving node
assertThat(vaultServiceNode.services.vaultQueryService.queryBy<Cash.State>().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<Void?>() {
@Suspendable
override fun call() = subFlow(SendTransactionFlow(otherParty, stx))
}
@InitiatedBy(NotifyTxFlow::class)
private class InitiateNotifyTxFlow(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() = subFlow(NotifyTransactionHandler(otherParty))
}
}

View File

@ -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`() {

View File

@ -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<ReceiveFlow>(node1)
val restoredFlow = node2.restartAndGetRestoredFlow<InitiatedReceiveFlow>(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<ReceiveFlow>().first
val node3Flow = node3.getSingleFlow<ReceiveFlow>().first
val node2Flow = node2.getSingleFlow<InitiatedReceiveFlow>().first
val node3Flow = node3.getSingleFlow<InitiatedReceiveFlow>().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<Int>(otherParty, 2)
session.sendAndReceive<Int>(2)
}
}
@ -543,14 +544,14 @@ class FlowFrameworkTests {
)
}
private class ConditionalExceptionFlow(val otherParty: Party, val sendPayload: Any) : FlowLogic<Unit>() {
private class ConditionalExceptionFlow(val otherPartySession: FlowSession, val sendPayload: Any) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val throwException = receive<Boolean>(otherParty).unwrap { it }
val throwException = otherPartySession.receive<Boolean>().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<String>() {
@Suspendable
override fun call(): String = sendAndReceive<String>(otherParty, throwException).unwrap { it }
override fun call(): String = initiateFlow(otherParty).sendAndReceive<String>(throwException).unwrap { it }
}
class RetryOnExceptionFlow(val otherParty: Party) : FlowLogic<String>() {
@ -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<Unit>() {
@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 <reified P : FlowLogic<*>> StartedNode<*>.registerFlowFactory(
initiatingFlowClass: KClass<out FlowLogic<*>>,
initiatedFlowVersion: Int = 1,
noinline flowFactory: (Party) -> P): CordaFuture<P>
{
return registerFlowFactoryExpectingFlowSession(initiatingFlowClass, initiatedFlowVersion, { flowFactory(it.counterparty) })
}
private inline fun <reified P : FlowLogic<*>> StartedNode<*>.registerFlowFactoryExpectingFlowSession(
initiatingFlowClass: KClass<out FlowLogic<*>>,
initiatedFlowVersion: Int = 1,
noinline flowFactory: (FlowSession) -> P): CordaFuture<P>
@ -858,13 +867,25 @@ class FlowFrameworkTests {
}
@InitiatingFlow
private open class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic<Unit>() {
private open class SendFlow(val payload: Any, vararg val otherParties: Party) : FlowLogic<FlowInfo>() {
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<Unit>() {
@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<String>(it).unwrap { it } }
receivedPayloads = otherParties.map { initiateFlow(it).receive<String>().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<Any>() {
@Suspendable
override fun call(): Any = sendAndReceive<Any>(otherParty, payload).unwrap { it }
}
private class InitiatedReceiveFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
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<String> = emptyList()
private class InlinedSendFlow(val payload: String, val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() = send(otherParty, payload)
override fun call() {
progressTracker.currentStep = START_STEP
receivedPayloads = listOf(otherPartySession.receive<String>().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<Unit>() {
private class SendAndReceiveFlow(val otherParty: Party, val payload: Any, val otherPartySession: FlowSession? = null) : FlowLogic<Any>() {
constructor(otherPartySession: FlowSession, payload: Any) : this(otherPartySession.counterparty, payload, otherPartySession)
@Suspendable
override fun call(): Any = (otherPartySession ?: initiateFlow(otherParty)).sendAndReceive<Any>(payload).unwrap { it }
}
private class InlinedSendFlow(val payload: String, val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() = otherPartySession.send(payload)
}
@InitiatingFlow
private class PingPongFlow(val otherParty: Party, val payload: Long, val otherPartySession: FlowSession? = null) : FlowLogic<Unit>() {
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<Long>(otherParty, payload).unwrap { it }
receivedPayload2 = sendAndReceive<Long>(otherParty, payload + 1).unwrap { it }
val session = otherPartySession ?: initiateFlow(otherParty)
receivedPayload = session.sendAndReceive<Long>(payload).unwrap { it }
receivedPayload2 = session.sendAndReceive<Long>(payload + 1).unwrap { it }
}
}
@ -950,17 +999,18 @@ class FlowFrameworkTests {
class Waiter(val stx: SignedTransaction, val otherParty: Party) : FlowLogic<SignedTransaction>() {
@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<SignedTransaction>() {
class Committer(val otherPartySession: FlowSession, val throwException: (() -> Exception)? = null) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val stx = receive<SignedTransaction>(otherParty).unwrap { it }
val stx = otherPartySession.receive<SignedTransaction>().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<List<StateAndRef<ContractState>>>() {
@Suspendable
override fun call(): List<StateAndRef<ContractState>> {
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<Pair<Any, Int>>() {
private class UpgradedFlow(val otherParty: Party, val otherPartySession: FlowSession? = null) : FlowLogic<Pair<Any, Int>>() {
constructor(otherPartySession: FlowSession) : this(otherPartySession.counterparty, otherPartySession)
@Suspendable
override fun call(): Pair<Any, Int> {
val received = receive<Any>(otherParty).unwrap { it }
val otherFlowVersion = getFlowInfo(otherParty).flowVersion
val otherPartySession = this.otherPartySession ?: initiateFlow(otherParty)
val received = otherPartySession.receive<Any>().unwrap { it }
val otherFlowVersion = otherPartySession.getCounterpartyFlowInfo().flowVersion
return Pair(received, otherFlowVersion)
}
}
private class SingleInlinedSubFlow(val otherParty: Party) : FlowLogic<Unit>() {
private class SingleInlinedSubFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val payload = receive<String>(otherParty).unwrap { it }
subFlow(InlinedSendFlow(payload + payload, otherParty))
val payload = otherPartySession.receive<String>().unwrap { it }
subFlow(InlinedSendFlow(payload + payload, otherPartySession))
}
}
private class DoubleInlinedSubFlow(val otherParty: Party) : FlowLogic<Unit>() {
private class DoubleInlinedSubFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(SingleInlinedSubFlow(otherParty))
subFlow(SingleInlinedSubFlow(otherPartySession))
}
}

View File

@ -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<SignedTransaction>() {
class AttachmentDemoFlow(private val otherSide: Party,
private val notary: Party,
private val attachId: SecureHash.SHA256) : FlowLogic<SignedTransaction>() {
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)))
}
}

View File

@ -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() }

View File

@ -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<CashIssueAndPaymentFlow>()))
val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet())

View File

@ -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<Unit>() {
class FixSignHandler(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val request = receive<RatesFixFlow.SignRequest>(otherParty).unwrap { it }
val request = otherPartySession.receive<RatesFixFlow.SignRequest>().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<Unit>() {
class FixQueryHandler(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
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<RatesFixFlow.QueryRequest>(otherParty).unwrap { it }
val request = otherPartySession.receive<RatesFixFlow.QueryRequest>().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

View File

@ -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 <T : AbstractParty> notUs(parties: List<T>): List<T> {
return parties.filter { ourIdentity != it }
}
}
@InitiatedBy(Requester::class)
class AutoOfferAcceptor(otherParty: Party) : Acceptor(otherParty)
class AutoOfferAcceptor(otherSideSession: FlowSession) : Acceptor(otherSideSession)
}

View File

@ -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<FixingSession>() {
class Fixer(override val otherSideSession: FlowSession) : TwoPartyDealFlow.Secondary<FixingSession>() {
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))
}
}
}

View File

@ -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<Fix>() {
@Suspendable
override fun call(): Fix {
val oracleSession = initiateFlow(oracle)
// TODO: add deadline to receive
val resp = sendAndReceive<ArrayList<Fix>>(oracle, QueryRequest(listOf(fixOf)))
val resp = oracleSession.sendAndReceive<ArrayList<Fix>>(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<TransactionSignature>() {
@Suspendable
override fun call(): TransactionSignature {
val resp = sendAndReceive<TransactionSignature>(oracle, SignRequest(partialMerkleTx))
val oracleSession = initiateFlow(oracle)
val resp = oracleSession.sendAndReceive<TransactionSignature>(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
}

View File

@ -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<Unit>() {
private class UpdateBusinessDayHandler(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val message = receive<UpdateBusinessDayMessage>(otherParty).unwrap { it }
val message = otherPartySession.receive<UpdateBusinessDayMessage>().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))
}
}
}

View File

@ -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<SignedTransaction>() {
@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<AcceptDealFlow> = node2.internals.registerInitiatedFlow(AcceptDealFlow::class.java)

View File

@ -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()
}
}

View File

@ -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<Boolean>(otherParty, OfferMessage(notary, offer)).unwrap { it }
val session = initiateFlow(otherParty)
val otherPartyAgreeFlag = session.sendAndReceive<Boolean>(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<Unit>() {
class Receiver(private val replyToSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
logger.info("IRSTradeFlow receiver started")
logger.info("Handshake finished, awaiting IRS trade offer")
val offer = receive<OfferMessage>(replyToParty).unwrap { it }
val offer = replyToSession.receive<OfferMessage>().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))
}
}
}

View File

@ -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<RevisionedState<PortfolioState.Update>>() {
constructor(otherParty: Party, valuationDate: LocalDate) : this(otherParty, valuationDate, null)
lateinit var notary: Party
lateinit var otherPartySession: FlowSession
@Suspendable
override fun call(): RevisionedState<PortfolioState.Update> {
@ -68,6 +70,7 @@ object SimmFlow {
val trades = serviceHub.vaultQueryService.queryBy<IRSState>(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<PortfolioState>) {
// Receive is a hack to ensure other side is ready
sendAndReceive<Ack>(otherParty, OfferMessage(notary, stateAndRef.state.data, existing?.ref, valuationDate))
otherPartySession.sendAndReceive<Ack>(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<T>(val session: FlowSession, stateAndRef: StateAndRef<RevisionedState<T>>, update: T) : StateRevisionFlow.Requester<T>(stateAndRef, update) {
override fun getParticipantSessions(): List<Pair<FlowSession, List<AbstractParty>>> {
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 <reified T : Any> agree(data: T): Boolean {
val valid = receive<T>(otherParty).unwrap {
val valid = otherPartySession.receive<T>().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<Unit>() {
class Receiver(val replyToSession: FlowSession) : FlowLogic<Unit>() {
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<IRSState>(criteria).states
val portfolio = Portfolio(trades)
logger.info("SimmFlow receiver started")
offer = receive<OfferMessage>(replyToParty).unwrap { it }
offer = replyToSession.receive<OfferMessage>().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<Boolean>(replyToParty).unwrap { it }
replyToSession.send(data)
return replyToSession.receive<Boolean>().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<PortfolioState.Update>(replyToParty) {
replyToSession.send(Ack) // Hack to state that this party is ready.
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToSession) {
override fun verifyProposal(stx:SignedTransaction, proposal: Proposal<PortfolioState.Update>) {
super.verifyProposal(stx, proposal)
if (proposal.modification.portfolio != portfolio.refs) throw StateReplacementException()
@ -310,7 +319,7 @@ object SimmFlow {
val portfolio = serviceHub.vaultQueryService.queryBy<IRSState>(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<PortfolioState.Update>(replyToParty) {
subFlow(object : StateRevisionFlow.Receiver<PortfolioState.Update>(replyToSession) {
override fun verifyProposal(stx: SignedTransaction, proposal: Proposal<PortfolioState.Update>) {
super.verifyProposal(stx, proposal)
if (proposal.modification.valuation != valuation) throw StateReplacementException()

View File

@ -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<T>(curStateRef: StateAndRef<RevisionedState<T>>,
open class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
updatedData: T) : AbstractStateReplacementFlow.Instigator<RevisionedState<T>, RevisionedState<T>, 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<in T>(otherParty: Party) : AbstractStateReplacementFlow.Acceptor<T>(otherParty) {
open class Receiver<in T>(initiatingSession: FlowSession) : AbstractStateReplacementFlow.Acceptor<T>(initiatingSession) {
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<T>) {
val proposedTx = stx.tx
val state = proposal.stateRef

View File

@ -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

View File

@ -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<Unit>() {
class BuyerFlow(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset")
@ -27,11 +28,11 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
progressTracker.currentStep = STARTING_BUY
// Receive the offered amount and automatically agree to it (in reality this would be a longer negotiation)
val amount = receive<Amount<Currency>>(otherParty).unwrap { it }
val amount = otherSideSession.receive<Amount<Currency>>().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)

View File

@ -54,7 +54,7 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
// 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<Currency>,
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
}
}

View File

@ -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,

View File

@ -225,6 +225,7 @@ fun <A> rpcDriver(
initialiseSerialization: Boolean = true,
networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false),
startNodesInProcess: Boolean = false,
extraCordappPackagesToScan: List<String> = emptyList(),
dsl: RPCDriverExposedDSLInterface.() -> A
) = genericDriver(
driverDsl = RPCDriverDSL(
@ -236,7 +237,8 @@ fun <A> rpcDriver(
useTestClock = useTestClock,
networkMapStartStrategy = networkMapStartStrategy,
isDebug = isDebug,
startNodesInProcess = startNodesInProcess
startNodesInProcess = startNodesInProcess,
extraCordappPackagesToScan = extraCordappPackagesToScan
)
),
coerce = { it },

View File

@ -310,6 +310,7 @@ fun <A> driver(
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy,
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
dsl: DriverDSLExposedInterface.() -> A
): A {
return genericDriver(
@ -319,9 +320,10 @@ fun <A> 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<String> = 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<String>) = 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<String>
) : 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<CordaFuture<Process>>()
@ -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<String, String>,
callerPackage: String
packagesToScanString: String
): CordaFuture<Process> {
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.

View File

@ -76,6 +76,7 @@ fun <A> verifierDriver(
useTestClock: Boolean = false,
networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false),
startNodesInProcess: Boolean = false,
extraCordappPackagesToScan: List<String> = emptyList(),
dsl: VerifierExposedDSLInterface.() -> A
) = genericDriver(
driverDsl = VerifierDriverDSL(
@ -87,7 +88,8 @@ fun <A> verifierDriver(
useTestClock = useTestClock,
networkMapStartStrategy = networkMapStartStrategy,
isDebug = isDebug,
startNodesInProcess = startNodesInProcess
startNodesInProcess = startNodesInProcess,
extraCordappPackagesToScan = extraCordappPackagesToScan
)
),
coerce = { it },