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:
Shams Asari
2018-11-14 14:16:22 +00:00
committed by GitHub
parent 8e6d4b4b38
commit e8b6f5f2f2
76 changed files with 1251 additions and 469 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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