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

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