mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
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:
@ -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>()
|
||||
|
@ -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
|
||||
|
||||
/*------------------------------
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user