mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
CORDA-2005: FinalityFlow has been made into an inlined flow to resolve issue with FinalityHandler (#4050)
FinalityHandler is insecure in that it is open to receive any transaction from any party. Any CorDapp targeting platform version 4 or above is required use the new c'tors which take in FlowSession objects to the counterpart flow. This flow must subcall ReceiveFinalityFlow to receive and record the finalised transaction. Old CorDapps (with target platform version < 4) will continue to work as previously. However if there are no old CorDapps loaded then the node will disable FinalityHandler.
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
package net.corda.docs;
|
||||
package net.corda.docs.java.tutorial.test;
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient;
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
@ -18,7 +18,9 @@ import net.corda.testing.node.User;
|
||||
import org.junit.Test;
|
||||
import rx.Observable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Currency;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
@ -38,7 +40,7 @@ public class JavaIntegrationTestingTutorial {
|
||||
// START 1
|
||||
driver(new DriverParameters()
|
||||
.withStartNodesInProcess(true)
|
||||
.withExtraCordappPackagesToScan(Arrays.asList("net.corda.finance.contracts.asset", "net.corda.finance.schemas")), dsl -> {
|
||||
.withExtraCordappPackagesToScan(singletonList("net.corda.finance")), dsl -> {
|
||||
|
||||
User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList(
|
||||
startFlow(CashIssueAndPaymentFlow.class),
|
@ -1,4 +1,4 @@
|
||||
package net.corda.docs.java;
|
||||
package net.corda.docs.java.tutorial.test;
|
||||
|
||||
import kotlin.Unit;
|
||||
import net.corda.client.rpc.CordaRPCClient;
|
@ -1,4 +1,4 @@
|
||||
package net.corda.docs
|
||||
package net.corda.docs.kotlin.tutorial.test
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.Amount
|
||||
@ -29,10 +29,7 @@ class KotlinIntegrationTestingTutorial {
|
||||
@Test
|
||||
fun `alice bob cash exchange example`() {
|
||||
// START 1
|
||||
driver(DriverParameters(
|
||||
startNodesInProcess = true,
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")
|
||||
)) {
|
||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||
startFlow<CashIssueAndPaymentFlow>(),
|
||||
invokeRpc("vaultTrackBy")
|
@ -1,4 +1,4 @@
|
||||
package net.corda.docs
|
||||
package net.corda.docs.kotlin.tutorial.test
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.messaging.startFlow
|
@ -0,0 +1,134 @@
|
||||
package net.corda.docs.java;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public class FinalityFlowMigration {
|
||||
public static SignedTransaction dummyTransactionWithParticipant(Party party) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// DOCSTART SimpleFlowUsingOldApi
|
||||
public static class SimpleFlowUsingOldApi extends FlowLogic<SignedTransaction> {
|
||||
private final Party counterparty;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public SignedTransaction call() throws FlowException {
|
||||
SignedTransaction stx = dummyTransactionWithParticipant(counterparty);
|
||||
return subFlow(new FinalityFlow(stx));
|
||||
}
|
||||
// DOCEND SimpleFlowUsingOldApi
|
||||
|
||||
public SimpleFlowUsingOldApi(Party counterparty) {
|
||||
this.counterparty = counterparty;
|
||||
}
|
||||
}
|
||||
|
||||
// DOCSTART SimpleFlowUsingNewApi
|
||||
// Notice how the flow *must* now be an initiating flow even when it wasn't before.
|
||||
@InitiatingFlow
|
||||
public static class SimpleFlowUsingNewApi extends FlowLogic<SignedTransaction> {
|
||||
private final Party counterparty;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public SignedTransaction call() throws FlowException {
|
||||
SignedTransaction stx = dummyTransactionWithParticipant(counterparty);
|
||||
// For each non-local participant in the transaction we must initiate a flow session with them.
|
||||
FlowSession session = initiateFlow(counterparty);
|
||||
return subFlow(new FinalityFlow(stx, session));
|
||||
}
|
||||
// DOCEND SimpleFlowUsingNewApi
|
||||
|
||||
public SimpleFlowUsingNewApi(Party counterparty) {
|
||||
this.counterparty = counterparty;
|
||||
}
|
||||
}
|
||||
// DOCSTART SimpleNewResponderFlow
|
||||
// All participants will run this flow to receive and record the finalised transaction into their vault.
|
||||
@InitiatedBy(SimpleFlowUsingNewApi.class)
|
||||
public static class SimpleNewResponderFlow extends FlowLogic<Void> {
|
||||
private final FlowSession otherSide;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
subFlow(new ReceiveFinalityFlow(otherSide));
|
||||
return null;
|
||||
}
|
||||
// DOCEND SimpleNewResponderFlow
|
||||
|
||||
public SimpleNewResponderFlow(FlowSession otherSide) {
|
||||
this.otherSide = otherSide;
|
||||
}
|
||||
}
|
||||
|
||||
// DOCSTART ExistingInitiatingFlow
|
||||
// Assuming the previous version of the flow was 1 (the default if none is specified), we increment the version number to 2
|
||||
// to allow for backwards compatibility with nodes running the old CorDapp.
|
||||
@InitiatingFlow(version = 2)
|
||||
public static class ExistingInitiatingFlow extends FlowLogic<SignedTransaction> {
|
||||
private final Party counterparty;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public SignedTransaction call() throws FlowException {
|
||||
SignedTransaction partiallySignedTx = dummyTransactionWithParticipant(counterparty);
|
||||
FlowSession session = initiateFlow(counterparty);
|
||||
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(partiallySignedTx, singletonList(session)));
|
||||
// Determine which version of the flow that other side is using.
|
||||
if (session.getCounterpartyFlowInfo().getFlowVersion() == 1) {
|
||||
// Use the old API if the other side is using the previous version of the flow.
|
||||
return subFlow(new FinalityFlow(fullySignedTx));
|
||||
} else {
|
||||
// Otherwise they're at least on version 2 and so we can send the finalised transaction on the existing session.
|
||||
return subFlow(new FinalityFlow(fullySignedTx, session));
|
||||
}
|
||||
}
|
||||
// DOCEND ExistingInitiatingFlow
|
||||
|
||||
public ExistingInitiatingFlow(Party counterparty) {
|
||||
this.counterparty = counterparty;
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(ExistingInitiatingFlow.class)
|
||||
public static class ExistingResponderFlow extends FlowLogic<Void> {
|
||||
private final FlowSession otherSide;
|
||||
|
||||
public ExistingResponderFlow(FlowSession otherSide) {
|
||||
this.otherSide = otherSide;
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
SignedTransaction txWeJustSigned = subFlow(new SignTransactionFlow(otherSide) {
|
||||
@Suspendable
|
||||
@Override
|
||||
protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException {
|
||||
// Do checks here
|
||||
}
|
||||
});
|
||||
// DOCSTART ExistingResponderFlow
|
||||
if (otherSide.getCounterpartyFlowInfo().getFlowVersion() >= 2) {
|
||||
// The other side is not using the old CorDapp so call ReceiveFinalityFlow to record the finalised transaction.
|
||||
// If SignTransactionFlow is used then we can verify the tranaction we receive for recording is the same one
|
||||
// that was just signed.
|
||||
subFlow(new ReceiveFinalityFlow(otherSide, txWeJustSigned.getId()));
|
||||
} else {
|
||||
// Otherwise the other side is running the old CorDapp and so we don't need to do anything further. The node
|
||||
// will automatically record the finalised transaction using the old insecure mechanism.
|
||||
}
|
||||
// DOCEND ExistingResponderFlow
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -28,8 +28,8 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static java.util.Collections.*;
|
||||
@ -578,13 +578,13 @@ public class FlowCookbook {
|
||||
// We notarise the transaction and get it recorded in the vault of
|
||||
// the participants of all the transaction's states.
|
||||
// DOCSTART 09
|
||||
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker()));
|
||||
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, singleton(counterpartySession), FINALISATION.childProgressTracker()));
|
||||
// DOCEND 09
|
||||
// We can also choose to send it to additional parties who aren't one
|
||||
// of the state's participants.
|
||||
// DOCSTART 10
|
||||
Set<Party> additionalParties = singleton(regulator);
|
||||
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker()));
|
||||
List<FlowSession> partySessions = Arrays.asList(counterpartySession, initiateFlow(regulator));
|
||||
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, partySessions, FINALISATION.childProgressTracker()));
|
||||
// DOCEND 10
|
||||
|
||||
// DOCSTART FlowSession porting
|
||||
@ -673,7 +673,7 @@ public class FlowCookbook {
|
||||
}
|
||||
}
|
||||
|
||||
subFlow(new SignTxFlow(counterpartySession, SignTransactionFlow.tracker()));
|
||||
SecureHash idOfTxWeSigned = subFlow(new SignTxFlow(counterpartySession, SignTransactionFlow.tracker())).getId();
|
||||
// DOCEND 16
|
||||
|
||||
/*------------------------------
|
||||
@ -681,9 +681,12 @@ public class FlowCookbook {
|
||||
------------------------------*/
|
||||
progressTracker.setCurrentStep(FINALISATION);
|
||||
|
||||
// Nothing to do here! As long as some other party calls
|
||||
// ``FinalityFlow``, the recording of the transaction on our node
|
||||
// we be handled automatically.
|
||||
// As the final step the responder waits to receive the notarised transaction from the sending party
|
||||
// Since it knows the ID of the transaction it just signed, the transaction ID is specified to ensure the correct
|
||||
// transaction is received and recorded.
|
||||
// DOCSTART ReceiveFinalityFlow
|
||||
subFlow(new ReceiveFinalityFlow(counterpartySession, idOfTxWeSigned));
|
||||
// DOCEND ReceiveFinalityFlow
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -2,16 +2,12 @@ package net.corda.docs.java.tutorial.helloworld;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.template.TemplateContract;
|
||||
import net.corda.core.flows.FlowException;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.InitiatingFlow;
|
||||
import net.corda.core.flows.StartableByRPC;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.Command;
|
||||
import net.corda.core.flows.FinalityFlow;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.transactions.TransactionBuilder;
|
||||
@ -59,8 +55,11 @@ public class IOUFlow extends FlowLogic<Void> {
|
||||
// Signing the transaction.
|
||||
SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(new FinalityFlow(signedTx));
|
||||
// Creating a session with the other party.
|
||||
FlowSession otherPartySession = initiateFlow(otherParty);
|
||||
|
||||
// We finalise the transaction and then send it to the counterparty.
|
||||
subFlow(new FinalityFlow(signedTx, otherPartySession));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import net.corda.core.flows.*;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// DOCSTART 01
|
||||
// Replace Responder's definition with:
|
||||
@InitiatedBy(IOUFlow.class)
|
||||
public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
private final FlowSession otherPartySession;
|
||||
|
||||
public IOUFlowResponder(FlowSession otherPartySession) {
|
||||
this.otherPartySession = otherPartySession;
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
subFlow(new ReceiveFinalityFlow(otherPartySession));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// DOCEND 01
|
@ -69,7 +69,7 @@ public class IOUFlow extends FlowLogic<Void> {
|
||||
signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.tracker()));
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(new FinalityFlow(fullySignedTx));
|
||||
subFlow(new FinalityFlow(fullySignedTx, otherPartySession));
|
||||
|
||||
return null;
|
||||
// DOCEND 02
|
||||
|
@ -1,15 +1,14 @@
|
||||
package net.corda.docs.java.tutorial.twoparty;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.crypto.SecureHash;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
// Define IOUFlowResponder:
|
||||
@InitiatedBy(IOUFlow.class)
|
||||
public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
@ -22,9 +21,10 @@ public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
@Suspendable
|
||||
@Override
|
||||
public Void call() throws FlowException {
|
||||
// DOCSTART 01
|
||||
class SignTxFlow extends SignTransactionFlow {
|
||||
private SignTxFlow(FlowSession otherPartySession, ProgressTracker progressTracker) {
|
||||
super(otherPartySession, progressTracker);
|
||||
private SignTxFlow(FlowSession otherPartySession) {
|
||||
super(otherPartySession);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -39,9 +39,11 @@ public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
subFlow(new SignTxFlow(otherPartySession, SignTransactionFlow.Companion.tracker()));
|
||||
SecureHash expectedTxId = subFlow(new SignTxFlow(otherPartySession)).getId();
|
||||
|
||||
subFlow(new ReceiveFinalityFlow(otherPartySession, expectedTxId));
|
||||
|
||||
return null;
|
||||
// DOCEND 01
|
||||
}
|
||||
}
|
||||
// DOCEND 01
|
@ -0,0 +1,91 @@
|
||||
@file:Suppress("DEPRECATION", "unused", "UNUSED_PARAMETER")
|
||||
|
||||
package net.corda.docs.kotlin
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
private fun dummyTransactionWithParticipant(party: Party): SignedTransaction = TODO()
|
||||
|
||||
// DOCSTART SimpleFlowUsingOldApi
|
||||
class SimpleFlowUsingOldApi(private val counterparty: Party) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val stx = dummyTransactionWithParticipant(counterparty)
|
||||
return subFlow(FinalityFlow(stx))
|
||||
}
|
||||
}
|
||||
// DOCEND SimpleFlowUsingOldApi
|
||||
|
||||
// DOCSTART SimpleFlowUsingNewApi
|
||||
// Notice how the flow *must* now be an initiating flow even when it wasn't before.
|
||||
@InitiatingFlow
|
||||
class SimpleFlowUsingNewApi(private val counterparty: Party) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val stx = dummyTransactionWithParticipant(counterparty)
|
||||
// For each non-local participant in the transaction we must initiate a flow session with them.
|
||||
val session = initiateFlow(counterparty)
|
||||
return subFlow(FinalityFlow(stx, session))
|
||||
}
|
||||
}
|
||||
// DOCEND SimpleFlowUsingNewApi
|
||||
|
||||
// DOCSTART SimpleNewResponderFlow
|
||||
// All participants will run this flow to receive and record the finalised transaction into their vault.
|
||||
@InitiatedBy(SimpleFlowUsingNewApi::class)
|
||||
class SimpleNewResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
subFlow(ReceiveFinalityFlow(otherSide))
|
||||
}
|
||||
}
|
||||
// DOCEND SimpleNewResponderFlow
|
||||
|
||||
// DOCSTART ExistingInitiatingFlow
|
||||
// Assuming the previous version of the flow was 1 (the default if none is specified), we increment the version number to 2
|
||||
// to allow for backwards compatibility with nodes running the old CorDapp.
|
||||
@InitiatingFlow(version = 2)
|
||||
class ExistingInitiatingFlow(private val counterparty: Party) : FlowLogic<SignedTransaction>() {
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
val partiallySignedTx = dummyTransactionWithParticipant(counterparty)
|
||||
val session = initiateFlow(counterparty)
|
||||
val fullySignedTx = subFlow(CollectSignaturesFlow(partiallySignedTx, listOf(session)))
|
||||
// Determine which version of the flow that other side is using.
|
||||
return if (session.getCounterpartyFlowInfo().flowVersion == 1) {
|
||||
// Use the old API if the other side is using the previous version of the flow.
|
||||
subFlow(FinalityFlow(fullySignedTx))
|
||||
} else {
|
||||
// Otherwise they're at least on version 2 and so we can send the finalised transaction on the existing session.
|
||||
subFlow(FinalityFlow(fullySignedTx, session))
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND ExistingInitiatingFlow
|
||||
|
||||
@InitiatedBy(ExistingInitiatingFlow::class)
|
||||
class ExistingResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val txWeJustSigned = subFlow(object : SignTransactionFlow(otherSide) {
|
||||
@Suspendable
|
||||
override fun checkTransaction(stx: SignedTransaction) {
|
||||
// Do checks here
|
||||
}
|
||||
})
|
||||
// DOCSTART ExistingResponderFlow
|
||||
if (otherSide.getCounterpartyFlowInfo().flowVersion >= 2) {
|
||||
// The other side is not using the old CorDapp so call ReceiveFinalityFlow to record the finalised transaction.
|
||||
// If SignTransactionFlow is used then we can verify the tranaction we receive for recording is the same one
|
||||
// that was just signed.
|
||||
subFlow(ReceiveFinalityFlow(otherSide, expectedTxId = txWeJustSigned.id))
|
||||
} else {
|
||||
// Otherwise the other side is running the old CorDapp and so we don't need to do anything further. The node
|
||||
// will automatically record the finalised transaction using the old insecure mechanism.
|
||||
}
|
||||
// DOCEND ExistingResponderFlow
|
||||
}
|
||||
}
|
@ -568,13 +568,13 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
|
||||
// We notarise the transaction and get it recorded in the vault of
|
||||
// the participants of all the transaction's states.
|
||||
// DOCSTART 09
|
||||
val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker()))
|
||||
val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, listOf(counterpartySession), FINALISATION.childProgressTracker()))
|
||||
// DOCEND 09
|
||||
// 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(fullySignedTx, additionalParties, FINALISATION.childProgressTracker()))
|
||||
val partySessions: List<FlowSession> = listOf(counterpartySession, initiateFlow(regulator))
|
||||
val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, partySessions, FINALISATION.childProgressTracker()))
|
||||
// DOCEND 10
|
||||
|
||||
// DOCSTART FlowSession porting
|
||||
@ -650,7 +650,7 @@ class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
}
|
||||
}
|
||||
|
||||
subFlow(signTransactionFlow)
|
||||
val idOfTxWeSigned = subFlow(signTransactionFlow).id
|
||||
// DOCEND 16
|
||||
|
||||
/**-----------------------------
|
||||
@ -658,8 +658,11 @@ class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
-----------------------------**/
|
||||
progressTracker.currentStep = FINALISATION
|
||||
|
||||
// Nothing to do here! As long as some other party calls
|
||||
// ``FinalityFlow``, the recording of the transaction on our node
|
||||
// we be handled automatically.
|
||||
// As the final step the responder waits to receive the notarised transaction from the sending party
|
||||
// Since it knows the ID of the transaction it just signed, the transaction ID is specified to ensure the correct
|
||||
// transaction is received and recorded.
|
||||
// DOCSTART ReceiveFinalityFlow
|
||||
subFlow(ReceiveFinalityFlow(counterpartySession, expectedTxId = idOfTxWeSigned))
|
||||
// DOCEND ReceiveFinalityFlow
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class ForeignExchangeFlow(private val tradeId: String,
|
||||
}
|
||||
|
||||
// Initiate the standard protocol to notarise and distribute to the involved parties.
|
||||
subFlow(FinalityFlow(allPartySignedTx, setOf(counterparty)))
|
||||
subFlow(FinalityFlow(allPartySignedTx, counterpartySession))
|
||||
|
||||
return allPartySignedTx.id
|
||||
}
|
||||
@ -239,7 +239,8 @@ class ForeignExchangeRemoteFlow(private val source: FlowSession) : FlowLogic<Uni
|
||||
|
||||
// send the other side our signature.
|
||||
source.send(ourSignature)
|
||||
// N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction
|
||||
// and broadcasting the result to us.
|
||||
|
||||
// and then finally stored the finalised transaction into our vault
|
||||
subFlow(ReceiveFinalityFlow(source))
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,11 @@ class IOUFlow(val iouValue: Int,
|
||||
// We sign the transaction.
|
||||
val signedTx = serviceHub.signInitialTransaction(txBuilder)
|
||||
|
||||
// We finalise the transaction.
|
||||
subFlow(FinalityFlow(signedTx))
|
||||
// Creating a session with the other party.
|
||||
val otherPartySession = initiateFlow(otherParty)
|
||||
|
||||
// We finalise the transaction and then send it to the counterparty.
|
||||
subFlow(FinalityFlow(signedTx, otherPartySession))
|
||||
}
|
||||
}
|
||||
// DOCEND 01
|
||||
|
@ -0,0 +1,20 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.corda.docs.kotlin.tutorial.helloworld
|
||||
|
||||
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.ReceiveFinalityFlow
|
||||
|
||||
// DOCSTART 01
|
||||
// Replace Responder's definition with:
|
||||
@InitiatedBy(IOUFlow::class)
|
||||
class IOUFlowResponder(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
subFlow(ReceiveFinalityFlow(otherPartySession))
|
||||
}
|
||||
}
|
||||
// DOCEND 01
|
@ -52,7 +52,7 @@ class IOUFlow(val iouValue: Int,
|
||||
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(otherPartySession), CollectSignaturesFlow.tracker()))
|
||||
|
||||
// Finalising the transaction.
|
||||
subFlow(FinalityFlow(fullySignedTx))
|
||||
subFlow(FinalityFlow(fullySignedTx, otherPartySession))
|
||||
// DOCEND 02
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ import net.corda.core.transactions.SignedTransaction
|
||||
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
|
||||
// DOCSTART 01
|
||||
val signTransactionFlow = object : SignTransactionFlow(otherPartySession) {
|
||||
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
||||
val output = stx.tx.outputs.single().data
|
||||
"This must be an IOU transaction." using (output is IOUState)
|
||||
@ -26,7 +27,9 @@ class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
}
|
||||
}
|
||||
|
||||
subFlow(signTransactionFlow)
|
||||
val expectedTxId = subFlow(signTransactionFlow).id
|
||||
|
||||
subFlow(ReceiveFinalityFlow(otherPartySession, expectedTxId))
|
||||
// DOCEND 01
|
||||
}
|
||||
}
|
||||
// DOCEND 01
|
||||
|
@ -1,6 +1,6 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.corda.docs.kotlin
|
||||
package net.corda.docs.kotlin.txbuild
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
@ -25,7 +25,7 @@ enum class WorkflowState {
|
||||
REJECTED
|
||||
}
|
||||
|
||||
const val TRADE_APPROVAL_PROGRAM_ID = "net.corda.docs.kotlin.TradeApprovalContract"
|
||||
const val TRADE_APPROVAL_PROGRAM_ID = "net.corda.docs.kotlin.txbuild.TradeApprovalContract"
|
||||
|
||||
/**
|
||||
* Minimal contract to encode a simple workflow with one initial state and two possible eventual states.
|
||||
@ -93,6 +93,7 @@ data class TradeApprovalContract(val blank: Unit? = null) : Contract {
|
||||
* The protocol then sends a copy to the other node. We don't require the other party to sign
|
||||
* as their approval/rejection is to follow.
|
||||
*/
|
||||
@InitiatingFlow
|
||||
class SubmitTradeApprovalFlow(private val tradeId: String,
|
||||
private val counterparty: Party) : FlowLogic<StateAndRef<TradeApprovalContract.State>>() {
|
||||
@Suspendable
|
||||
@ -109,12 +110,20 @@ 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(counterparty)))
|
||||
subFlow(FinalityFlow(signedTx, initiateFlow(counterparty)))
|
||||
// Return the initial state
|
||||
return signedTx.tx.outRef(0)
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(SubmitTradeApprovalFlow::class)
|
||||
class SubmitTradeApprovalResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
subFlow(ReceiveFinalityFlow(otherSide))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple flow to complete a proposal submitted by another party and ensure both nodes
|
||||
* end up with a fully signed copy of the state either as APPROVED, or REJECTED
|
||||
@ -174,8 +183,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 session = initiateFlow(newState.source)
|
||||
val allPartySignedTx = session.sendAndReceive<TransactionSignature>(selfSignedTx).unwrap {
|
||||
val sourceSession = initiateFlow(newState.source)
|
||||
val allPartySignedTx = sourceSession.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 +198,7 @@ class SubmitCompletionFlow(private val ref: StateRef, private val verdict: Workf
|
||||
}
|
||||
// DOCSTART 4
|
||||
// Notarise and distribute the completed transaction.
|
||||
subFlow(FinalityFlow(allPartySignedTx, setOf(newState.source)))
|
||||
subFlow(FinalityFlow(allPartySignedTx, sourceSession))
|
||||
// DOCEND 4
|
||||
// Return back the details of the completed state/transaction.
|
||||
return allPartySignedTx.tx.outRef(0)
|
||||
@ -233,7 +242,7 @@ class RecordCompletionFlow(private val sourceSession: FlowSession) : FlowLogic<U
|
||||
val ourSignature = serviceHub.createSignature(completeTx)
|
||||
// Send our signature to the other party.
|
||||
sourceSession.send(ourSignature)
|
||||
// N.B. The FinalityProtocol will be responsible for Notarising the SignedTransaction
|
||||
// and broadcasting the result to us.
|
||||
|
||||
subFlow(ReceiveFinalityFlow(sourceSession))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.corda.docs.kotlin
|
||||
package net.corda.docs.kotlin.vault
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Amount
|
||||
@ -68,7 +68,6 @@ object CustomVaultQuery {
|
||||
* This is a slightly modified version of the IssuerFlow, which uses a 3rd party custom query to
|
||||
* retrieve a list of currencies and top up amounts to be used in the issuance.
|
||||
*/
|
||||
|
||||
object TopupIssuerFlow {
|
||||
@CordaSerializable
|
||||
data class TopupRequest(val issueToParty: Party,
|
@ -1,9 +1,10 @@
|
||||
package net.corda.docs.kotlin
|
||||
package net.corda.docs.kotlin.txbuild
|
||||
|
||||
import net.corda.core.contracts.LinearState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
@ -33,7 +34,7 @@ class WorkflowTransactionBuildTutorialTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs"))
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf(javaClass.packageName))
|
||||
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
|
@ -1,13 +1,14 @@
|
||||
package net.corda.docs.kotlin
|
||||
package net.corda.docs.kotlin.vault
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.docs.java.tutorial.helloworld.IOUFlow
|
||||
import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow
|
||||
import net.corda.finance.*
|
||||
import net.corda.finance.contracts.getCashBalances
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
@ -17,7 +18,7 @@ import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.StartedMockNode
|
||||
import org.assertj.core.api.Assertions.assertThatCode
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
@ -30,7 +31,7 @@ class CustomVaultQueryTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", "net.corda.docs", "com.template"))
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", IOUFlow::class.packageName, javaClass.packageName, "com.template"))
|
||||
nodeA = mockNet.createPartyNode()
|
||||
nodeB = mockNet.createPartyNode()
|
||||
notary = mockNet.defaultNotaryIdentity
|
||||
@ -43,7 +44,6 @@ class CustomVaultQueryTest {
|
||||
|
||||
@Test
|
||||
fun `query by max recorded time`() {
|
||||
|
||||
nodeA.startFlow(IOUFlow(1000, nodeB.info.singleIdentity())).getOrThrow()
|
||||
nodeA.startFlow(IOUFlow(500, nodeB.info.singleIdentity())).getOrThrow()
|
||||
|
||||
@ -69,9 +69,9 @@ class CustomVaultQueryTest {
|
||||
topUpCurrencies()
|
||||
val (cashBalancesAfterTopup, _) = getBalances()
|
||||
|
||||
Assert.assertEquals(cashBalancesOriginal[GBP]?.times(2), cashBalancesAfterTopup[GBP])
|
||||
Assert.assertEquals(cashBalancesOriginal[USD]?.times(2) , cashBalancesAfterTopup[USD])
|
||||
Assert.assertEquals(cashBalancesOriginal[CHF]?.times( 2), cashBalancesAfterTopup[CHF])
|
||||
assertEquals(cashBalancesOriginal[GBP]?.times(2), cashBalancesAfterTopup[GBP])
|
||||
assertEquals(cashBalancesOriginal[USD]?.times(2) , cashBalancesAfterTopup[USD])
|
||||
assertEquals(cashBalancesOriginal[CHF]?.times( 2), cashBalancesAfterTopup[CHF])
|
||||
}
|
||||
|
||||
private fun issueCashForCurrency(amountToIssue: Amount<Currency>) {
|
||||
@ -86,7 +86,8 @@ class CustomVaultQueryTest {
|
||||
nodeA.info.singleIdentity(),
|
||||
OpaqueBytes.of(0x01),
|
||||
nodeA.info.singleIdentity(),
|
||||
notary)).getOrThrow()
|
||||
notary)
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
|
Reference in New Issue
Block a user